码迷,mamicode.com
首页 > 编程语言 > 详细

关于java socket(转)

时间:2015-08-31 23:38:05      阅读:221      评论:0      收藏:0      [点我收藏+]

标签:

1. 关于new Socket()中参数的理解

Server端:

  • 调用ServerSocket serverSocket = new ServerSocket(1287,2);后Server端打开了指定的端口1287,并绑定了PID 5449。

技术分享

Client端:

  • 调用Socket socket = new Socket(remoteAddress, 1287);后,Client端会将Client的PID绑定到一个随机未使用的端口上;

技术分享

  • 调用Socket socket = new Socket(remoteAddress, 1287, localAddress, 1288);后,Client端会将Client的PID绑定到指定的端口1288上;

技术分享

2. 关于backlog,connectiontimeout,SO_TIMEOUT参数的理解

2.1. 测试过程及现象

经过测试发现,测试程序在Windows上和在Linux上的表现不同。

2.1.1. Linux平台上(Server和Client都在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))的排队数21。事实上,经过进一步的测试,Server端设置的排队数为1或3时,Server端的实际排队数都比所设置的值多1。

2.1.2. Windows平台上(Server和Client都在Windows上)

在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端的实际排队数都与所设置的值相等。

2.2. Client连接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端交互

2.3. 猜测结论

根据以上现象猜测,所谓“排队队列”应该是在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,所以估计跟这个参数也无关。应该除了connectiontimeoutSO_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这个参数的含义。两个操作系统在这一点上的理解是一致的。

3. 附件

测试使用的代码:

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 }
技术分享

 

关于java socket(转)

标签:

原文地址:http://www.cnblogs.com/softidea/p/4774275.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!