java socket构造_浅谈 Java Socket 构造函数参数 backlog

ServerSocket API

API:java.net.ServerSocket 1.0

ServerSocket(int port, int backlog)

创建一个监听端口的服务器套接字

ServerSocket() 1.4

创建一个未绑定的服务器套接字

void bind(SocketAddress endpoint, int backlog) 1.4

把服务器套接字绑定到指定套接字地址上

注意

bind 方法常常在调用无参构造函数 ServerSocket() 之后使用。如果 bind 和其他有参构造函数一起使用,会产生报错。以下错误示范:

ServerSocket serverSocket = new ServerSocket(8081, 2); // 使用有参构造函数,创建一个监听端口的服务器套接字

serverSocket.bind(new InetSocketAddress(8081), 1); // 这里再调用bind方法,重复绑定,产生异常!

如下图所示:

43058b0976ef45f34f24cdd5b758e6c2.png

backlog 参数

backlog 参数是套接字上请求的最大挂起连接数。它的确切语义是特定于实现的。

backlog 是请求的 incoming 连接队列的最大长度。

创建 ServerSocket 并绑定端口:

Socket 服务端代码

public class SocketServer {

public static void main(String[] args) throws IOException {

ServerSocket serverSocket = new ServerSocket();

serverSocket.bind(new InetSocketAddress(8080), 1);

System.in.read(); // 不让服务端关闭

}

}

这里使用 telnet 127.0.0.1 8080 打开两个 Windows Telnet 客户端,根据 WireShark 的抓包结果如下:

b79b8a724518e948cf049da52bf0637a.png

端口号为 5608 的第一个 Telnet 客户端,经过三次握手,顺利地和服务器建立的连接并保持了连接

端口号为 5620 的第二个 Telnet 客户端,首先发送了第一次握手报文(SYN),但是服务器因为设置了 backlog 为 2,因此直接给客户端返回 (RST) 报文。客户端尝试重传 (报文内容和第一次握手时的报文一模一样),尝试2次后收到的仍然是RST 报文,就不了了之。

如果改用 Java 客户端,代码如下:

public class Clients {

public static void main(String[] args) throws IOException {

Socket[] clients = new Socket[2];

for (int i = 1; i <= clients.length; i++) {

clients[i-1] = new Socket("127.0.0.1", 8080);

System.out.println("client connection:" + i);

}

}

}

控制台发生报错:

0346f794486f6871186e041146fe8392.png

第一个客户端 Socket 创建成功,但是第二个客户端的 Socket 被拒绝连接。

因此,在这种情况下,能够成功创建客户端套接字的个数,刚好就是创建 ServerSocket 时候指定的 backlog 的数量。

用 accept 返回 Socket 对象

API:java.net.ServerSocket 1.0

Socket accept()

等待连接。该方法阻塞(即使之空闲)当前线程直到建立连接为知。该方法返回一个 Socket 对象,程序可以通过这个对象与连接中的客户端进行通信。

我们改造一下 ServerSocket,在 while 循环调用 ServerSocket#accept 方法。

public class SocketServer {

public static void main(String[] args) throws IOException {

ServerSocket serverSocket = new ServerSocket();

serverSocket.bind(new InetSocketAddress(8080), 1);

int acceptCount = 0;

while (true) {

Socket clientSocket = serverSocket.accept();

InetSocketAddress remote = (InetSocketAddress) clientSocket.getRemoteSocketAddress();

System.out.println(remote.getPort());

++acceptCount;

System.out.println("当前客户端连接数:" + acceptCount);

}

}

}

客户端我们也改一下,变成并发量 100 的连接请求

public class Clients {

public static void main(String[] args) throws IOException {

Socket[] clients = new Socket[100];

for (int i = 1; i <= clients.length; i++) {

final int index = i;

new Thread(new Runnable() {

@Override

public void run() {

try {

clients[index-1] = new Socket("127.0.0.1", 8080);

System.out.println("client connection:" + index);

} catch (IOException e) {

e.printStackTrace();

}

}

}, String.valueOf(i)).start();

}

System.in.read();

}

}

经过实验,backlog=1 时, 一次运行结果如下:

6c39c3d77676a763b9d05ae1d1f0ae42.png

多次执行,拒绝连接的数量存在波动。

给服务端加上阻塞

上一个实验中,我们使用 accept 来返回 Socket 对象。我们把套接字从 sync_queue 转移到 accept_queue,这样就可以接收更多的连接了。

702fdb9f668384b12dfd496c4f5a2022.png

但是,如果我们用 sleep 来模拟接收到连接后的收发消息,业务处理的延迟,实验结果又会不同。

带延迟的 SocketServer

public class SocketServer {

public static void main(String[] args) throws IOException, InterruptedException {

ServerSocket serverSocket = new ServerSocket();

serverSocket.bind(new InetSocketAddress(8080), 1);

int acceptCount = 0;

while (true) {

Socket clientSocket = serverSocket.accept();

InetSocketAddress remote = (InetSocketAddress) clientSocket.getRemoteSocketAddress();

System.out.println(remote.getPort());

++acceptCount;

System.out.println("当前客户端连接数:" + acceptCount);

Thread.sleep(2000); // 加入延迟时间

}

}

}

同步客户端

public class SyncClients {

private static Socket[] clients = new Socket[100];

public static void main(String[] args) throws Exception {

for (int i = 1; i <= clients.length; i++) {

clients[i-1] = new Socket("127.0.0.1", 8080);

System.out.println("client connection:" + i);

}

}

}

同步连接客户端,套接字是一个接着一个连接的。先完成一组三次握手,再进行第二组三次握手,以此类推。

第一次连接从 sync_queue 转移到 accept_queue,

第二次连接进入到 sync_quque,

第三次连接因为 backlog=1 的缘故,被拒绝连接了,客户端抛出异常。

结果如图所示:

eb61a2b5de4e7de6416031cc084b5acc.png

异步客户端

public class Clients {

static Socket[] clients = new Socket[3];

public static void main(String[] args) throws IOException {

for (int i = 0; i < clients.length; i++) {

final int index = i;

new Thread(new Runnable() {

@Override

public void run() {

try {

clients[index] = new Socket("127.0.0.1", 8080);

System.out.println("client connection:" + index);

Thread.sleep(10000);

} catch (IOException | InterruptedException e) {

e.printStackTrace();

}

}

}, String.valueOf(i)).start();

}

System.in.read();

}

}

测试结果并没有像我预料的那样,第三次连接失败

04e0225c64cc969c03875e804339e8a2.png

这里我先留个坑,跟网上查询的 sync_queue 理论有些不符合的样子。如果有熟悉底层的大佬可以指点一二。

小贴士

如果 SyncClients 中没有加入 System.in.read() 代码,客户端程序会停止运行,客户端主动给服务器端发送 RST 报文重置连接。

参考博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值