14 out.write(data);
15 int totalBytesRcvd = 0;
16 int bytesRcvd;
17 //4. 从服务器端读取应答数据,直到本次命令的所有数据都返回。如果read返回-1,表示服务器关闭了该套接字。
18 while (totalBytesRcvd < data.length) {
19 if ((bytesRcvd = in.read(data,totalBytesRcvd,data.length-totalBytesRcvd)) == -1)
20 throw new SocketException("Connection closed prematurely.");
21 totalBytesRcvd += bytesRcvd;
22 }
23 System.out.println("Received: " + new String(data));
24 //5. 关闭该Socket对象。
25 socket.close();
26 }
27 }
从目前来看,由于还没有合适的服务器监听指定的IP和端口,该示例程序暂时不能正常运行。后面会给出与之相对应的服务器程序,这样我们就可以同时启动这两个示例程序,然后再观察他们的输出结果。
这里需要给出解释的是为什么我们需要用多个read调用,以确保数据被完全读入呢?因为客户端在write的时候,如果网络状况不好,服务器极有可能是通过多次读取才接收到全部数据的,即便是本例中的10个字节,这种情况理论上也是存在的。鉴于此,客户端将通过多次read,以保证数据被全部读取。
3. TCP套接字--服务器:
现在我们将注意力转向服务器。服务器端的工作是建立一个通信终端,并被动的等待客户端的连接。典型的TCP服务器执行如下两个步骤:
1) 创建一个ServerSocket实例并指定本地端口。次套接字的功能是侦听该指定端口收到的连接;
2) 重复执行如下三步:
2.1)调用ServerSocket的accept()方法以获取下一个客户端连接。基于新建立的客户端连接,创建一个Socket的实例,并由accept()方法返回;
2.2)使用所返回的Socket实例的InputStream和OutputStream与客户端进行通信;
2.3)通信完成后,使用Socket类的close()方法关闭该客户端套接字连接。
下面的Socket Server示例代码,可用于处理来自上一个例子中客户端的数据请求。
1 public class MyTest {
2 private static final int BUFSIZE = 32;
3 public static void main(String[] args) throws UnknownHostException, IOException {
4 int port = 5050;
6 //1. 通过制定IP和Port,构造服务器的监听Socket对象。需要说明的是,在ServerSocket构造后
7 //该Socket对象即已经针对该IP和Port处于bind和listen状态了。
8 ServerSocket ssock = new ServerSocket(port,20,saddr);
9 int recvMsgSize;
10 byte[] receiveBuf = new byte[BUFSIZE];
11 while (true) {
12 //2. accept()将阻塞在这里等待客户端的连接。直到有新的客户端连接进来的时候
13 //该函数才正常返回,返回的Socket对象就是之后和客户端进行通信的Socket对象。
14 Socket csock = ssock.accept();
15 //这里可以通过accept返回的Socket对象获取当前正在连接的客户端的IP地址。
16 SocketAddress caddr = csock.getRemoteSocketAddress();
17 System.out.println("Handling client at " + caddr);
18 //3. 这里和客户端的代码一致,通过Socket返回的输入和输出流对象来进行数据传输。
19 InputStream in = csock.getInputStream();
20 OutputStream out = csock.getOutputStream();
21 //4. 这里和客户端的读取机制大体相同,如果read返回-1,表示客户端主动关闭了。
22 while ((recvMsgSize = in.read(receiveBuf)) != -1) {
23 out.write(receiveBuf,0,recvMsgSize);
24 }
25 //发送完毕后服务器主动关闭客户端Socket对象。
26 csock.close();
27 }
28 }
29 }
需要说明的是,这两个客户端和服务器的例子都是非常简单的,其目的主要还是用于介绍Java中网络编程的一些基础知识。至于真实的网络客户端和网络服务器的设计和实现,一般而言,需要针对实际的需求予以一定程度上的定制,以便优化服务程序的运行效率。即便如此,在网络服务器的设计中还是存在一定的通用技巧的,如异步处理、线程池、任务队列、心跳检测等很多和并发相关的知识。鉴于这些知识和技巧并不是本篇的重点,如果展开讨论,足以再开出单独一篇。需要补充的是,Java中在NIO中同样提供了一些基于异步的Socket通讯模型,我们会在本篇的后面予以介绍。
在C++中,不同的操作系统提供了不同机制的异步通讯模型,以便提高整体通讯效率,如Win32的完成端口,Linux的epoll等。事实上,对于没有提供类似特化通讯机制的操作系统,完全可以通过select机制在Java中实现非阻塞的Socket通讯。
4. 数据发送和接收:
在数据传输过程有一个非常引人注目的一