java socket 用法_java Socket用法详解(上)。

在客户/服务器通信模式中, 客户端需要主动创建与服务器连接的 Socket(套接字), 服务器端收到了客户端的连接请求, 也会创建与客户连接的 Socket. Socket可看做是通信连接两端的收发器, 服务器与客户端都通过 Socket 来收发数据.

这篇文章首先介绍Socket类的各个构造方法, 以及成员方法的用法, 接着介绍 Socket的一些选项的作用, 这些选项可控制客户建立与服务器的连接, 以及接收和发送数据的行为.

一. 构造Socket

Socket的构造方法有以下几种重载形式:

Socket()

Socket(InetAddress address, int port) throws UnknowHostException, IOException

Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException

Socket(String host, int port) throws UnknowHostException, IOException

Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

除了第一个不带参数的构造方法以外, 其他构造方法都会试图建立与服务器的连接, 如果连接成功, 就返回 Socket对象; 如果因为某些原因连接失败, 就会抛出IOException .

1.1 使用无参数构造方法, 设定等待建立连接的超时时间

Socket socket = new Socket();

SocketAddress remoteAddr = new InetSocketAddress("localhost",8000);

socket.connect(remoteAddr, 60000); //等待建立连接的超时时间为1分钟

以上代码用于连接到本地机器上的监听8000端口的服务器程序, 等待连接的最长时间为1分钟. 如果在1分钟内连接成功则connet()方法顺利返回; 如果在1分钟内出现某种异常, 则抛出该异常; 如果超过1分钟后, 即没有连接成功, 也没有出现其他异常, 那么会抛出 SocketTimeoutException. Socket 类的 connect(SocketAddress endpoint, int timeout) 方法负责连接服务器, 参数endpoint 指定服务器的地址, 参数timeout 设定超时数据, 以毫秒为单位. 如果参数timeout 设为0, 表示永远不会超时, 默认是不会超时的.

1.2 设定服务器的地址

除了第一个不带参数的构造方法, 其他构造方法都需要在参数中设定服务器的地址, 包括服务器的IP地址或主机名, 以及端口:

Socket(InetAddress address, int port)              //第一个参数address 表示主机的IP地址

Socket(String host, int port)                              //第一个参数host 表示主机的名字

InetAddress 类表示服务器的IP地址, InetAddress 类提供了一系列静态工厂方法, 用于构造自身的实例, 例如:

//返回本地主机的IP地址

InetAddress addr1 = InetAddress.getLocalHost();

//返回代表 "222.34.5.7"的 IP地址

InetAddress addr2 = InetAddress.getByName("222.34.5.7");

//返回域名为"www.javathinker.org"的 IP地址

InetAddress addr3 = InetAddress.getByName("www.javathinker.org");

1.3 设定客户端的地址

在一个Socket 对象中, 即包含远程服务器的IP 地址和端口信息, 也包含本地客户端的IP 地址和端口信息. 默认情况下, 客户端的IP 地址来自于客户程序所在的主机, 客户端的端口则由操作系统随机分配. Socket类还有两个构造方法允许显式地设置客户端的IP 地址和端口:

//参数localAddr 和 localPort 用来设置客户端的IP 地址和端口

Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException

Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

如果一个主机同时属于两个以上的网络, 它就可能拥有两个以上的IP 地址. 例如, 一个主机在Internet 网络中的IP 地址为 "222.67.1.34", 在一个局域网中的IP 地址为 "112.5.4.3". 假定这个主机上的客户程序希望和同一个局域网的一个服务器程序(地址为:"112.5.4.45: 8000")通信, 客户端可按照如下方式构造Socket 对象:

InetAddress remoteAddr1 = InetAddress.getByName("112.5.4.45");

InetAddress localAddr1 = InetAddress.getByName("112.5.4.3");

Socket socket1 = new Socket(remoteAddr1, 8000, localAddr1, 2345);   //客户端使用端口2345

1.4 客户连接服务器时可能抛出的异常

当Socket 的构造方法请求连接服务器时, 可能会抛出下面的异常.

UnKnownHostException: 如果无法识别主机的名字或IP 地址, 就会抛出这种异常.

ConnectException: 如果没有服务器进程监听指定的端口, 或者服务器进程拒绝连接, 就会抛出这种异常.

SocketTimeoutException: 如果等待连接超时, 就会抛出这种异常.

BindException: 如果无法把Socket 对象与指定的本地IP 地址或端口绑定, 就会抛出这种异常.

以上4中异常都是IOException的直接或间接子类.      如图2-1所示.

IOException------- UnknownHostException

|---- InterruptedIOException ----------- SocketTimeoutException

|---- SocketException              ----------- BindException

|---------- ConnectException

图2-1 客户端连接服务器时可能抛出的异常

二. 获取Socket 的信息

在一个Socket 对象中同时包含了远程服务器的IP 地址和端口信息, 以及客户本地的IP 地址和端口信息. 此外, 从Socket 对象中还可以获得输出流和输入流, 分别用于向服务器发送数据, 以及接收从服务器端发来的数据. 以下方法用于获取Socket的有关信息.

getInetAddress(): 获得远程服务器的IP 地址.

getPort(): 获得远程服务器的端口.

getLocalAddress(): 获得客户本地的IP 地址.

getLocalPort(): 获得客户本地的端口.

getInputStream(): 获得输入流. 如果Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownInput() 方法关闭输入流, 那么此方法会抛出IOException.

getOutputStream(): 获得输出流, 如果Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownOutput() 方法关闭输出流, 那么此方法会抛出IOException.

这里有个HTTPClient 类的例子, 代码我是写好了, 也测试过了, 因为篇幅原因就不贴了. 这个HTTPClient 类用于访问网页 www.javathinker.org/index.jsp. 该网页位于一个主机名(也叫域名)为 www.javathinker.org 的远程HTTP服务器上, 它监听 80 端口. 在HTTPClient 类中, 先创建了一个连接到该HTTP服务器的Socket对象, 然后发送符合HTTP 协议的请求, 接着接收从HTTP 服务器上发回的响应结果.

三. 关闭Socket

当客户与服务器的通信结束, 应该及时关闭Socket , 以释放Socket 占用的包括端口在内的各种资源. Socket 的 close() 方法负责关闭Socket. 当一个Socket对象被关闭, 就不能再通过它的输入流和输出流进行I/O操作, 否则会导致IOException.

为了确保关闭Socket 的操作总是被执行, 强烈建议把这个操作放在finally 代码块中:

Socket socket = null;

try{

socket = new Socket(www.javathinker.org,80);

//执行接收和发送数据的操作

..........

}catch(IOException e){

e.printStackTrace();

}finally{

try{

if(socket != null) socket.close();

}catch(IOException e){e.printStackTrace();}

}

Socket 类提供了3 个状态测试方法.

isClosed(): 如果Socket已经连接到远程主机, 并且还没有关闭, 则返回true , 否则返回false .

isConnected(): 如果Socket曾经连接到远程主机, 则返回true , 否则返回false .

isBound(): 如果Socket已经与一个本地端口绑定, 则返回true , 否则返回false .

如果要判断一个Socket 对象当前是否处于连接状态, 可采用以下方式:

boolean isConnected = socket.isConnected() && !socket.isClosed();

四. 半关闭Socket

进程A 与进程B 通过Socket 通信, 假定进程A 输出数据, 进程B 读入数据. 进程A 如何告诉进程B 所有数据已经输出完毕? 下文略......

五. 设置Socket 的选项

Socket 有以下几个选项.

TCP_NODELAY: 表示立即发送数据.

SO_RESUSEADDR: 表示是否允许重用Socket 所绑定的本地地址.

SO_TIMEOUT: 表示接收数据时的等待超时数据.

SO_LINGER: 表示当执行Socket 的 close()方法时, 是否立即关闭底层的Socket.

SO_SNFBUF: 表示发送数据的缓冲区的大小.

SO_RCVBUF: 表示接收数据的缓冲区的大小.

SO_KEEPALIVE: 表示对于长时间处于空闲状态的Socket , 是否要自动把它关闭.

OOBINLINE: 表示是否支持发送一个字节的TCP 紧急数据.

5.1 TCP_NODELAY 选项

设置该选项: public void setTcpNoDelay(boolean on) throws SocketException

读取该选项: public boolean getTcpNoDelay() throws SocketException

默认情况下, 发送数据采用Negale 算法. Negale 算法是指发送方发送的数据不会立即发出, 而是先放在缓冲区, 等缓存区满了再发出. 发送完一批数据后, 会等待接收方对这批数据的回应, 然后再发送下一批数据. Negale 算法适用于发送方需要发送大批量数据, 并且接收方会及时作出回应的场合, 这种算法通过减少传输数据的次数来提高通信效率.

如果发送方持续地发送小批量的数据, 并且接收方不一定会立即发送响应数据, 那么Negale 算法会使发送方运行很慢. 对于GUI 程序, 如网络游戏程序(服务器需要实时跟踪客户端鼠标的移动), 这个问题尤其突出. 客户端鼠标位置改动的信息需要实时发送到服务器上, 由于Negale 算法采用缓冲, 大大减低了实时响应速度, 导致客户程序运行很慢.

TCP_NODELAY 的默认值为 false, 表示采用 Negale 算法. 如果调用setTcpNoDelay(true)方法, 就会关闭 Socket的缓冲, 确保数据及时发送:

if(!socket.getTcpNoDelay()) socket.setTcpNoDelay(true);

如果Socket 的底层实现不支持TCP_NODELAY 选项, 那么getTcpNoDelay() 和 setTcpNoDelay 方法会抛出 SocketException.

5.2 SO_RESUSEADDR 选项

设置该选项: public void setResuseAddress(boolean on) throws SocketException 读取该选项: public boolean getResuseAddress() throws SocketException 当接收方通过Socket 的close() 方法关闭Socket 时, 如果网络上还有发送到这个Socket 的数据, 那么底层的Socket 不会立即释放本地端口, 而是会等待一段时间, 确保接收到了网络上发送过来的延迟数据, 然后再释放端口. Socket接收到延迟数据后, 不会对这些数据作任何处理. Socket 接收延迟数据的目的是, 确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值