Java网络编程从入门到精通(14):多种多样的建立网络连接的方式

本文涉及的产品
.cn 域名,1个 12个月
简介:
在上一篇文章中我们讨论了Socket 类的基本用法,并给出的例子中使用Socket 类连接服务器时使用了一种最简单的连接方式,也就是通过IP 和端口号来连接服务器。而为了使连接服务器的方式更灵活,Socket 类不仅可以通过自身的构造方法连接服务器,而且也可以通过connect 方法来连接数据库。
一、 通过构造方法连接服务器
    我们 可以通过6 个重载构造函数以不同的方式来连接服务器。这6 个重载的构造函数可以分为两类:

1. 
自动选择IP

    这种方式是最常用的。所谓自动选择
IP ,是指当本机有多块网卡或者在一个网卡上绑定了多个IP 时,Socket 类会自动为我们选择一个可用的IP 。在上述6 个构造方法中有4 个是使用这种方法来连接服务器的。
<!--[if !supportLists]--> (1)    <!--[endif]--> public Socket(String host, int port)

这是最常用的构造方法,在前面的例子中就是使用的这个构造方法。在使用时只需要提供一个字符串类型的 IP 或域名以及一个整型的端口号即可。在这个 构造方法中可能会抛出两个错误: UnknownHostException IOException 。发生第一个错误的原因是我们提供的 host 并不存在或不合法,而其它的错误被归为 IO 错误。因此,这个构造方法的完整定义是:

public  Socket(String host,  int  port)  throws  UnknownHostException, IOException
(2)  public Socket(InetAddress inetaddress, int port)

这个构造方法和第一种构造方法类似 只是将字符串形式的 host 改为 InetAddress 对象类型了。在这个构造方法中之所以要使用 InetAddress 类主要是因为考虑到在程序中可能需要使用 Socket 类多次连接同一个 IP 或域名,这样使用 InetAddress 类的效率比较高。另外,在使用字符串类型的 host 连接服务器时,可能会发生两个错误,但使用 InetAddress 对象来描述 host ,只会发生 IOException 错误,这是因为当你将 IP 或域名传给 InetAddress 时, InetAddress 会自动检查这个 IP 或域名,如果这个 IP 或域名无效,那么 InetAddress 就会抛出 UnknownHostException 错误,而不会由 Socket 类的构造方法抛出。因此,这个构造方法的完整定义是:
public  Socket(InetAddress inetaddress,  int  port)  throws  IOException
(3)  public Socket(String host, int port,  boolean stream )

这个构造方法和第一种构造方法差不多,只是多了一个boolean 类型的stream 参数。如果这个stream true ,那么这个构造方法和第一种构造方法完全一样。如果stream false ,则使用UDP 协议建立一个UDP 连接(UDP 将在下面的章节详细讨论,在这里只要知道它和TCP 最大的区别是UDP 是面向无连接的,而TCP 是面向有连接的),也许是当初Sun 的开发人员在编写Socket 类时还未考虑编写处理UDP 连接的DatagramSocket 类,所以才将建立UDP 连接的功能加入到Socket 类中,不过Sun 在后来的JDK 中加入了DatagramSocket 类,所以,这个构造方法就没什么用了,因此,Sun 将其设为了Deprecated 标记,也就是说,这个构造方法在以后的JDK 版本中可以会被删除。其于以上原因,在使用Java 编写网络程序时,尽量不要使用这个构造方法来建立UDP 连接。

(4)  public Socket(InetAddress inetaddress, int port, boolean flag)

这个构造方法和第三种构造方法的 flag 标记的含义一样,也是不建议使用的。

    下面的代码演示
上述 4 种构造方法的使用:

package  mysocket;

import  java.net. * ;
import  java.io. * ;

public   class  MoreConnection
{
    
private   static   void  closeSocket(Socket socket)
    {
        
if  (socket  !=   null )
            
try
            {
                socket.close();
            }
            
catch  (Exception e) { }
    }

    
public   static   void  main(String[] args)
    {
        Socket socket1 
=   null , socket2  =   null , socket3  =   null , socket4  =   null ;
        
try
        {
            
//  如果将www.ptpress.com.cn改成其它不存在的域名,将抛出UnknownHostException错误
            
//  测试public Socket(String host, int port)
            socket1  =   new  Socket( " www.ptpress.com.cn " 80 );
            System.out.println(
" socket1连接成功! " );
            
//  测试public Socket(InetAddress inetaddress, int port)
            socket2  =   new  Socket(InetAddress.getByName( " www.ptpress.com.cn " ),  80 );
            System.out.println(
" socket2连接成功! " );

            
//  下面的两种建立连接的方式并不建议使用
            
//  测试public Socket(String host, int port, boolean stream)
            socket3  =   new  Socket( " www.ptpress.com.cn " 80 false );
            System.out.println(
" socket3连接成功! " );
            
//  测试public Socket(InetAddress inetaddress, int i, boolean flag)
            socket4  =   new  Socket(InetAddress.getByName( " www.ptpress.com.cn " ),  80 false );
            System.out.println(
" socket4连接成功! " );
        }
        
catch  (UnknownHostException e)
        {
            System.out.println(
" UnknownHostException 被抛出! " );
        }
        
catch  (IOException e)
        {
            System.out.println(
" IOException 被抛出! " );
        }
        
finally
        {
            closeSocket(socket1);
            closeSocket(socket2);
            closeSocket(socket3);
            closeSocket(socket4);
        }
    }
}
在上面代码中 的最后通过finally 关闭了被打开的Socket 连接,这是一个好习惯。因为只有在将关闭Socket 连接的代码写在finally 里,无论是否出错,都会执行这些代码。但要注意,在关闭Socket 连接之前,必须检查Socket 对象是否为null ,这是因为错误很可能在建立连接时发生,这样Socket 对象就没有建立成功,也就用不着关闭了。
1.        手动绑定 IP
当本机有多个IP 时(这些IP 可能是多块网卡上的,也可能是一块网卡上绑定的多个IP ), 在连接服务器时需要由客户端确定需要使用哪个IP 。这样就必须使用Socket 类的另外两个构方法来处理。下面让我们来看看这两个构造方法是如何来使用特定的IP 来连接服务器的。
public Socket(String host, int port, InetAddress inetaddress, int localPort)
这个构造方法的参数分为两部分,第一部分为前两个参数: host port ,它们分别表示要连接的服务器的 IP 和端口号。第二部分为后两个参数: inetaddress localPort 。其中 inetaddress 则表示要使用的本地的 IP ,而 localPort 则表示要绑定的本地端口号。这个 localPort 这以设置为本机的任何未被绑定的端口号。如果将 localPort 的值设为 0 java 将在 1024 65,535 之间随即选择一个未绑定的端口号。因此,在一般情况下将 localPort 设为 0
public Socket(InetAddress inetaddress, int port, InetAddress inetaddress1, int localPort)
这个构造方法和第一个构造方法基本相同,只是将第一个参数 host 换成了 inetaddress 。其它的使用方法和第一个构造方法类似。
下面的代码 中将使用这两个构造方法来做一个实验。我们假设有两台计算机: PC1 PC2 PC1 PC2 各有一块网卡。 PC1 绑定有两个 IP 192.168.18.252 200.200.200.200 PC2 绑定有一个 IP 200.200.200.4 PC1 PC2 的子网掩码都是 255.255.255.0 。而 PC1 的默认网关为: 192.168.28.254 。下面的代码 需要在 PC1 上运行。

package  mysocket;

import  java.net. * ;

public   class  MoreConnection1
{
    
public   static   void  main(String[] args)
    {
        
try
        {
            InetAddress localAddress1 
=  InetAddress.getByName( " 200.200.200.200 " );
            InetAddress localAddress2 
=  InetAddress.getByName( " 192.168.18.252 " );
             //  如果将localAddress1改成localAddress2,socket1无法连接成功
            Socket socket1  =   new  Socket( " 200.200.200.4 " 80 , localAddress1,  0 );
            System.out.println(
" socket1连接成功! " );
            Socket socket2 
=   new  Socket( " www.ptpress.com.cn " 80 , localAddress2,  0 );
            System.out.println(
" socket2连接成功! " );
            
//  下面的语句将抛出一个IOException错误
            Socket socket3  =   new  Socket( " www.ptpress.com.cn " 80 , localAddress1,  0 );
            System.out.println(
" socket3连接成功! " );
            socket1.close();
            socket2.close();
            socket3.close();
        }
        
catch  (Exception e)
        {
            System.out.println(e.getMessage());
        }
    }
}
运行上面代码 的输出结果如下:
socket1连接成功 !
socket2连接成功
!
Connection timed out: connect
从上面的输出结果可以看出 socket1 socket2 已经连接成功 socket3 并未连接成功。从例程 4-8 可以看出, socket1 在连接时使用 localAddress1 绑定到了 200.200.200.200 上,而 PC2 IP 200.200.200.4 ,因此, socket1 所使用的 IP PC2 IP 在同一个网段,所以 socket1 可以连接成功。如果将 localAddress1 改成 localAddress2 后, socket1 将无法连接成功。另外两个 Socket 连接 socket2 socket3 是通过 Internet 连接 www.ptpress.com.cn 。它们所不同的是 socket2 绑定的是 192.168.18.252 ,而 socket3 绑定的是 200.200.200.200 。它们执行的结果是 socket2 可以连接成功,而 socket3 连接失败。这是因为 socket2 所绑定的 IP PC1 的默认网关 192.168.18.254 在同一个网段,因此, socket2 可以连接到 Internet 。而 socket3 所绑定的 IP PC1 IP 不在同一个网段,因此, socket3 将无法连接到 Internet
二、 通过 connect 方法连接服务器
Socket 类不仅可以通过构造方法直接连接服务器,而且还可以建立未连接的 Socket 对象,并通过 connect 方法来连接服务器。 Socket 类的 connect 方法有两个重载形式:
1. public void connect(SocketAddress endpoint) throws IOException
Socket 类的 connect 方法和它的构造方法在描述服务器信息 IP 和端口 上有一些差异。在 connect 方法中并未象构造方法中以字符串形式的 host 和整数形式的 port 作为参数,而是直接将 IP 和端口封装在了 SocketAddress 类的子类 InetSocketAddress 中。可按如下形式使用这个 connect 方法:
Socket socket  =   new  Socket();
socket.connect(
new  InetSocketAddress(host, port));
2.  public void connect(SocketAddress endpoint, int timeout) throws IOException
     这个 connect 方法和第一个 connect 类似,只是多了一个 timeout 参数。这个参数表示连接的超时时间,单位是毫秒。使用 timeout 设为 0 ,则使用默认的超时时间。
在使用 Socket 类的构造方法连接服务器时可以直接通过构造方法绑定本地 IP ,而 connect 方法可以通过 Socket 类的 bind 方法来绑定本地 IP 。例程 4-9 演示如何使用 connect 方法和 bind 方法。

package  mysocket;

import  java.net. * ;

public   class  MoreConnection2
{
    
public   static   void  main(String[] args)
    {
        
try
        {
            Socket socket1 
=   new  Socket();
            Socket socket2 
=   new  Socket();
            Socket socket3 
=   new  Socket();
            socket1.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ));
            socket1.close();
            System.out.println(
" socket1连接成功! " );             
            
/*
               将socket2绑定到192.168.18.252将产生一个IOException错误  
            socket2.bind(new InetSocketAddress("192.168.18.252", 0));
            
*/
            socket2.bind(
new  InetSocketAddress( " 200.200.200.200 " 0 ));
            socket2.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ));
             
            socket2.close();
            System.out.println(
" socket2连接成功! " );

            socket3.bind(
new  InetSocketAddress( " 192.168.18.252 " 0 ));
            socket3.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ),  2000 );            
            socket3.close();
            System.out.println(
" socket3连接成功! " );
        }
        
catch  (Exception e)
        {
            System.out.println(e.getMessage());
        }
    }
}
上面的代码 的输出结果为:

socket1连接成功 !
socket2连接成功
!
Connection timed out: connect
在上面代码 中的socket3 连接服务器时为其设置了超时时间(2000 毫秒),因此,socket3 在非常短的时间就抛出了IOException 错误。




本文转自 androidguy 51CTO博客,原文链接:http://blog.51cto.com/androidguy/214451,如需转载请自行联系原作者

相关文章
|
3天前
|
JSON Dart 前端开发
鸿蒙应用开发从入门到入行 - 篇7:http网络请求
在本篇文章里,您将掌握鸿蒙开发工具DevEco的基本使用、ArkUI里的基础组件,并通过制作一个简单界面掌握使用
30 8
|
25天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
46 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
24天前
|
机器学习/深度学习 资源调度 算法
图卷积网络入门:数学基础与架构设计
本文系统地阐述了图卷积网络的架构原理。通过简化数学表述并聚焦于矩阵运算的核心概念,详细解析了GCN的工作机制。
66 3
图卷积网络入门:数学基础与架构设计
|
14天前
|
Web App开发 网络协议 安全
网络编程懒人入门(十六):手把手教你使用网络编程抓包神器Wireshark
Wireshark是一款开源和跨平台的抓包工具。它通过调用操作系统底层的API,直接捕获网卡上的数据包,因此捕获的数据包详细、功能强大。但Wireshark本身稍显复杂,本文将以用抓包实例,手把手带你一步步用好Wireshark,并真正理解抓到的数据包的各项含义。
64 2
|
21天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
21天前
|
机器学习/深度学习 人工智能 算法
深度学习入门:用Python构建你的第一个神经网络
在人工智能的海洋中,深度学习是那艘能够带你远航的船。本文将作为你的航标,引导你搭建第一个神经网络模型,让你领略深度学习的魅力。通过简单直观的语言和实例,我们将一起探索隐藏在数据背后的模式,体验从零开始创造智能系统的快感。准备好了吗?让我们启航吧!
52 3
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
3天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
18 3