标签:
Server端:
Client端:
经过测试发现,测试程序在Windows上和在Linux上的表现不同。
在eclipse中先启动Server,然后启动第1个Client C1,此时C1正常连接到Server并返回:
Server端也打印出了相应的信息:
启动第2个Client C2,此时C2一直等待Server的“hi”回应,Server端的排队数为1;
启动第3个Client C3,此时C3一直等待Server的“hi”回应,Server端的排队数为2;
启动第4个Client C4,此时C4一直等待Server的“hi”回应,Server端的排队数为3;
启动第5个Client C5,此时C5一直等待Server的“hi”回应,Server端的排队数为4;
启动第6个Client C6,此时C6一直等待Server的“hi”回应,Server端的排队数为5;
等待大约100s之后,C5和C6都返回了报错信息:(Client端均没有设置socket.setSoTimeout(5000);)
而C2、C3和C4依然处于等待状态,没有任何输出:
此时Server端的排队数为3,比Server端的设置(ServerSocket serverSocket = new ServerSocket(1287,2))的排队数2多1。事实上,经过进一步的测试,Server端设置的排队数为1或3时,Server端的实际排队数都比所设置的值多1。
在cmd中先启动Server,然后启动第1个Client C1,此时C1正常连接到Server并返回:
Server端也打印出了相应的信息:
启动第2个Client C2,此时C2一直等待Server的“hi”回应,Server端的排队数为1;
启动第3个Client C3,此时C3一直等待Server的“hi”回应,Server端的排队数为2;
启动第4个Client C4,此时C4立刻返回了报错信息:(Client端均没有设置socket.setSoTimeout(5000);)
而C2、C3依然处于等待状态,没有任何输出:
此时Server端的排队数为2,与Server端的设置(ServerSocket serverSocket = new ServerSocket(1287,2))的排队数2相等。事实上,经过进一步的测试,Server端设置的排队数为1或3时,Server端的实际排队数都与所设置的值相等。
(1). Server端初始化ServerSocket并监听端口
ServerSocket serverSocket = new ServerSocket(1287,2);
Socket socket = serverSocket.accept(); // 此时若无Client端连接上来,即Server端的等待队列为空,那么Server端在此阻塞
(2). Client端初始化Socket,并连接Server
Socket socket = new Socket ();
socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);
(socket.setSoTimeout(5000);)
(3). Server端accept()调用从等待队列中取出Client,并返回
Socket socket = serverSocket.accept();
(4). Server端通过返回的Socket与Client端交互
根据以上现象猜测,所谓“排队队列”应该是在TCP层控制的,具体如下图:
【解释图中的要素】
连接队列(connection队列):所有的客户端TCP连接请求,首先进入连接队列,进行三次握手,三次握手成功标志着连接建立(established),连接建立后客户端请求被从队列中取出并进入ACCEPT队列。三次握手建立连接的过程一般很快,局域网中1ms以内。
connectiontimeout是指从进入连接队列、进行三次握手、建立连接到从队列中被取出这段时间的超时时间,一般这个不会超时。connectiontimeout可以在客户端通过socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);来设置。
ACCEPT队列:连接建立后,客户端请求被放入ACCEPT队列,等待服务端应用调用serverSocket.accept(),从而将客户端请求从ACCEPT队列中取出并与其交互。当然,若服务端应用调用Socket socket = serverSocket.accept();时ACCPET队列为空,那么服务端应用在此阻塞,直到ACCEPT队列不为空。
ACCEPT队列的最大排队数backlog控制着这个队列最多能存放多少个客户端请求,当客户端请求个数大于backlog时将拒绝为多余的客户端服务。服务端应用可以通过调用ServerSocket serverSocket = new ServerSocket(1287,2);来设置backlog。
在ACCEPT队列中,每个客户端请求都有一个SO_TIMEOUT的参数,用来控制该客户端在这段时间内的超时——从进入ACCEPT队列开始到从中取出、服务端accept()返回、服务端向socket写入回复数据、客户端收到服务端的回复。如果客户端在SO_TIMEOUT时间内没有收到来自服务端的回复,则客户端在is.readLine()报Read timed out异常,如下图。在客户端可以通过调用socket.setSoTimeout(5000);来设置SO_TIMEOUT。
port-PID映射表:客户端请求从ACCEPT队列中被取出后,需要交给服务端应用,因此需要知道应该交给哪个服务端应用,它的PID是多少,所以需要一个类似port-PID映射表的映射关系。当服务端应用(PID=1234)启动时,调用ServerSocket serverSocket = new ServerSocket(1287,2);时,意味着该服务绑定了1287这个端口,此时应在port-PID映射表中添加一条记录“port:1287, PID=1234”。这样,当客户端请求被从port1287的ACCEPT队列中取出时,就知道应该交给PID为1234的服务端应用。
【描述C1-C6连接Server的过程】
在服务端,当应用层应用1调用ServerSocket serverSocket = new ServerSocket(1287,2);时,TCP层在port-PID映射表中加入一条记录“port1,PID1”以示绑定;并且TCP层为port1开辟新的空间,用来存储port、连接队列和ACCEPT队列,并设置ACCPET队列的backlog。然后应用1调用Socket socket = serverSocket.accept();来监听ACCEPT队列。
当C1调用socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);发起请求时,C1会路由到服务端机器,从物理层、链路层、IP层到达TCP层。到达后首先进入连接队列1,设置connectiontimeout,进行三次握手,成功后进入ACCEPT队列,并调用socket.setSoTimeout(5000);设置SO_TIMEOUT。由于应用1正阻塞在accept(),所以C1被从排队队列中取出,然后查询port-PID映射表,决定PID1,从而将C1发送给PID1进程应用1,然后应用1的accpet()返回并生成Socket来与C1交互。
当C2调用socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);时,C2在连接队列中进行三次握手后进入ACCEPT队列,并调用socket.setSoTimeout(5000);设置SO_TIMEOUT。由于此时应用1没有调用accept(),所以C2在排队队列中等待。C3、C4、C5、C6重复此过程。若排队队列中的元素个数大于最大排队数backlog1,那么TCP拒绝为多余的Client服务。
【解释测试现象】
因此,下面可以解释测试现象(ps. 在测试中Client端均没有设置socket.setSoTimeout(5000);)
l 若设置backlog1 = 2,那么,在Linux环境下C5和C6会被拒绝,ACCPET队列长度为3,比backlog1大1;而在windows环境下C4、C5和C6会被拒绝,ACCEPT队列长度为2,等于backlog1。这可能是由于不同的操作系统对TCP的参数设置不同导致。
l 在不同的操作系统下,连接被拒绝时的报错Exception也不同。windows下报connnection refused异常,异常位置在socket.connect(***);,这是由于连接被拒绝,或者说没有连接上;而Linux下报connection reset异常,异常位置在is.readLine()(这个异常一般是由于一端socket被关闭,而另一端仍在进行读写操作引起的),这是可能是由于服务端断开多于backlog+1的连接而客户端仍在readLine()导致。这可能是由于不同操作系统对TCP的实现不同导致。
l 在测试过程中Client端只设置了connectiontimeout,没有设置SO_TIMEOUT。在Linux环境下C5和C6会在约100s后被拒绝,而在windows环境下C4、C5和C6会立刻被拒绝。这个应该和connectiontimeout无关。由于客户端都没有设置SO_TIMEOUT,所以估计跟这个参数也无关。应该除了connectiontimeout和SO_TIMEOUT以外还有一个操作系统默认的参数来控制是100s还是立刻,目前尚不清楚。
l 如果在Client端设置了SO_TIMEOUT,例如为5s,那么在Linux上的现象是C2及其之后的Client会在连接上服务端等待其回复5s后报Read timed out,异常位置在is.readLine();在windows上的现象是C4及其之后的Client依然立即报Connection refused异常,异常位置在socket.connect(***),而C2和C3是在连接上服务端等待其回复5s后报Read timed out异常,异常位置在is.readLine()。
如果在Client端设置了SO_TIMEOUT,例如为110s,那么在Linux上的现象是C5及其之后的Client会在100s后报Connection reset异常,异常位置在is.readLine(),而C2、C3和C4会在110s后报Read timed out异常,异常位置在is.readLine();在windows上的现象是C4及其之后的Client依然立即报Connection refused异常,异常位置在socket.connect(***),而C2和C3是在连接上服务端等待其回复110s后报Read timed out异常,异常位置在is.readLine()。
这就是SO_TIMEOUT这个参数的含义。两个操作系统在这一点上的理解是一致的。
测试使用的代码:
Client.java
1 package socket; 2 3 import java.io.*; 4 import java.net.*; 5 import java.util.Date; 6 7 public class Client { 8 9 /** 10 * @param args 11 */ 12 public static void main(String[] args) { 13 // TODO Auto-generated method stub 14 try { 15 System.out.println(new Date()); 16 InetAddress remoteAddress = InetAddress.getByName("127.0.0.1"); 17 // InetAddress localAddress = InetAddress.getByName("127.0.0.1"); 18 // Socket socket = new Socket(remoteAddress, 1287, localAddress, 1288); 19 // Socket socket = new Socket(remoteAddress, 1287); 20 Socket socket = new Socket (); 21 socket.connect(new InetSocketAddress(remoteAddress,1287), 1000); 22 socket.setSoTimeout(5000); 23 System.out.println(new Date()); 24 PrintWriter os = new PrintWriter(socket.getOutputStream()); 25 BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream())); 26 String msg = "hello"; 27 os.println(msg); 28 os.flush(); 29 System.out.println("Client: " + msg); 30 // Thread.sleep(1000); 31 System.out.println("Server: " + is.readLine()); 32 System.out.println(new Date()); 33 Thread.sleep(1000000); 34 System.out.println("end!"); 35 os.close(); 36 is.close(); 37 socket.close(); 38 } catch (UnknownHostException e) { 39 // TODO Auto-generated catch block 40 System.out.println(new Date()); 41 e.printStackTrace(); 42 } catch (IOException e) { 43 // TODO Auto-generated catch block 44 System.out.println(new Date()); 45 e.printStackTrace(); 46 } 47 catch (InterruptedException e) { 48 // TODO Auto-generated catch block 49 System.out.println(new Date()); 50 e.printStackTrace(); 51 } 52 53 } 54 55 }
Server.java
1 package socket; 2 3 import java.io.*; 4 import java.net.*; 5 6 public class Server { 7 8 /** 9 * @param args 10 */ 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 try { 14 ServerSocket serverSocket = new ServerSocket(1287,2); 15 Socket socket = serverSocket.accept(); 16 17 PrintWriter os = new PrintWriter(socket.getOutputStream()); 18 BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream())); 19 while (true){ 20 System.out.println("Client: " + is.readLine()); 21 String msg = "hi"; 22 os.println(msg); 23 os.flush(); 24 System.out.println("Server: " + msg); 25 } 26 // os.close(); 27 // is.close(); 28 // socket.close(); 29 // serverSocket.close(); 30 } catch (IOException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 } 34 // catch (InterruptedException e) { 35 // // TODO Auto-generated catch block 36 // e.printStackTrace(); 37 // } 38 39 } 40 41 }
标签:
原文地址:http://www.cnblogs.com/softidea/p/4774275.html