关于java socket

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 }
时间: 2024-08-23 09:23:42

关于java socket的相关文章

java socket编程实例代码讲解

1.所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 操作java socket时用到的最多的三个方法为: accept():主要用于服务器端产生"阻塞",等待客户端的链接请求,并且返回一个客户端的Socket实例: getInputStream():方法主要用来获得网络连接输入,同时返回一个InputStream对象实例: getOutputStream

java socket通信-传输文件图片--传输图片

ClientTcpSend.java   客户端发送类 package com.yjf.test; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.net.InetSocketAddress; import java.net.Socket; public class ClientTcpSend { public static String clien

java socket 的参数选项解读(转)

java socket中有很多参数可以选择,这篇博客的目的是沉淀出这些参数的语义和用法,供自己以后查阅. 1.java socket参数选项总览 在JDK1.6中有如下参数选项: 1 public final static int TCP_NODELAY = 0x0001; 2 3 public final static int SO_BINDADDR = 0x000F; 4 5 public final static int SO_REUSEADDR = 0x04; 6 7 public fi

Java Socket编程

对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信.这样就有两个Socket了,客户端和服务端各一个. 客户端写服务端读 服务端代码 public class Server { public s

交通银行 Java Socket 服务启动 管理 WINDOWS 版

按照交通银行提供的无界面启动方法试验了很多次,都没有成功,所以自己动手用C# 知识写了一个. 小工具可以判断 交通银行 JAVA SOCKET 服务是否启动,并可以启动/关闭服务 主要代码如下: 判断服务是否启动 引用 :using System.Management; SelectQuery selectQuery = new SelectQuery(“select * from Win32_Process where Name = ‘java.exe’”); object cmdLine =

java socket服务器端搭建

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; public class ServerBase extends Thread { public

Java Socket编程readLine返回null,read返回-1的条件

客户端正常关闭socket的时候,服务器端的readLine()方法会返回null,或者read()方法会返回-1 Java Socket编程readLine返回null,read返回-1的条件,布布扣,bubuko.com

JAVA通信系列一:Java Socket技术总结

本文是学习java Socket整理的资料,供参考. 1       Socket通信原理 1.1     ISO七层模型 1.2     TCP/IP五层模型 应用层相当于OSI中的会话层,表示层,应用层. 区别参考:http://blog.chinaunix.net/uid-22166872-id-3716751.html 1.3     TCP报文 (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2)确认序号:Ack序号,占32

java socket 基于UDP/IP 协议

Java  socket 基于UDP/IP协议应用 服务器端:  1.创建DatagramSocket,指定端口号 2.创建DatagramPacket 3.接收客户端发送的数据 4.读取数据 客户端: 1.  定义发送信息: 服务器的IP 端口号  发送的内容 2.  创建DatagramPacket,包含将要发送的信息 3.  创建DatagramSocket 4.  发送数据 服务器端 import java.io.IOException; import java.net.Datagram

[JAVA]Socket 图片流的传输

import java.io.DataInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { System.out.