最近遇到一个问题,就是向服务器发送报文。(发送一个报文建立一次TCP连接,报文是并发发送的)、发送了10几个报文之后,Java的客户端开始抛出:
java.net.SocketTimeoutException: connect timed out
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(UnknownSource)
atjava.net.PlainSocketImpl.connectToAddress(Unknown Source)
at java.net.PlainSocketImpl.connect(UnknownSource)
at java.net.SocksSocketImpl.connect(UnknownSource)
at java.net.Socket.connect(Unknown Source)
at Client.sendMessage(Client.java:43)
at Client.run(Client.java:28)
at java.lang.Thread.run(Unknown Source)。
剩下的报文无法再正常发送了。
以下是我客户端代码的简化版。
public class Client extends Thread{
public static void main (String args[]) {
int n = 20;
for(int i=0;i<n;i++) {
new Thread(new Client()).start();
}
}
@Override
public void run() {
String ip = "192.168.0.12";
int port = 9136;
sendMessage(ip, port);
}
public void sendMessage(String ip, int port) {
Socket clientSocket = new Socket();
SocketAddress serverAddress = new InetSocketAddress(ip, port);
Writer writer = null;
/*
* 指定服务器的IP地址和通讯端口
*/
int timeout = 50;
try {
System.out.println("client connect to server");
clientSocket.connect(serverAddress,timeout);
System.out.println("Connect succeed");
writer = new OutputStreamWriter(clientSocket.getOutputStream());
writer.write("hello server");
writer.flush();
writer.close();
clientSocket.close();
System.out.println("connection closed");
}catch(SocketTimeoutException e) {
e.printStackTrace();
System.out.println("connect to server failed!!!");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(writer!=null) {
writer.close();
}
if(clientSocket!=null) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
<p>在最开始我怀疑是客户端代码的问题:</p><p> clientSocket.connect(serverAddress,timeout);把函数中timeout的值设大了之后,报文书能增长到30几个左右,但是后边的的TCP连接还是抛出了上文中的异常。</p><p>经过对服务器端口进行抓包分析。发现抓包发现服务器端有收到客服端发送的TCP三次握手的SYN报文,但是后边的连接的SYN报文。服务器端好像就是置之不理的感觉。就是不回复SYN_ACK。所以导致client端的socket抛出<u><span style="color:navy;">SocketTimeoutException</span><span style="color:navy;">。</span></u></p><p> 当我在网上搜索为什么服务器不回复SYN报文SYN_ACK时候。搜到以下一片文章:</p><p><a target=_blank href="http://serverfault.com/questions/235965/why-would-a-server-not-send-a-syn-ack-packet-in-response-to-a-syn-packet%EF%BC%8C%E5%AF%B9linux%E7%9A%84%E7%B3%BB%E7%BB%9F%E5%8F%82%E6%95%B0%E5%81%9A%E4%BA%86%E4%BF%AE%E6%94%B9%EF%BC%8C%E9%97%AE%E9%A2%98%E4%BB%8D%E7%84%B6%E6%B2%A1%E6%9C%89%E5%BE%97%E5%88%B0%E8%A7%A3%E5%86%B3%E3%80%82"><span style="color:#2A5685;">http://serverfault.com/questions/235965/why-would-a-server-not-send-a-syn-ack-packet-in-response-to-a-syn-packet</span></a></p><p>根据文章中,对服务的 tcpwindows scaling 和tcptimestamps参数进行了修改。发现还是不起作用。</p><p> 到底是什么限制了tcp连接:</p><p>怀疑是服务器的原因。服务器在并发的时候处理不过来。所以拒绝新来的来接。做了以下工作:</p><p>在客户端并发发送报文的时候,用top命令才看服务器的CPU占用率。根本没过1%。排除服务器处理不过来的原因。</p><p>查看此时TCP端口的暂用情况:对9136端口的占用ESTABLISH的10几个,SYN_RECV的2个作用。占用情况也不是很高。</p><p> 最后能想到的原因就是服务器程序的原因了。</p><p>在对服务器程序代码的检查中发现back_log这样一个参数:被设置成了5.</p><p>Back_log的定义为:</p><p><span style="background:#F0F3FA;">该参数规定了</span><span style="background:#F0F3FA;">TCP</span><span style="background:#F0F3FA;">层可以容纳的连接请求队列长度(应用层尚未进行处理),以下是</span><span style="background:#F0F3FA;">back_log</span><span style="background:#F0F3FA;">的限制说明。</span></p><p align="center"></p><p>摘自:http://www.2cto.com/os/201207/139524.html</p><p>由于同时并发数有30几个TCP链接,back_log被设置成5了。当ESTABLISHED队列和SYN_RECV队列之和超过了5时。后边新来的连接被没有多余的空间可以放置。所以直接被丢弃了。由于我的客户端程序clientSocket.connect(serverAddress,timeout)中的timeout时间被设置成50ms。50ms内没有收到服务器的SYN_ACK时候,抛出timeoutexception。如果把timeout参数设置成8s.情况又不一样。第一次发送的SYN报文被丢弃了。客户端会在间隔1s、2s、4s的时候再次尝试。可能恰好back_log队列已满,这些SYN报文也可能被丢弃。但是增大了服务器接收的概率。</p><p>当然最根本的原因还是back_log的参数设置不够大。</p><p>总结:</p><p>传入连接指示(对连接的请求)的最大队列长度被设置为 <code>backlog</code> 参数。如果队列满时收到连接指示,则拒绝该连接。<span style="color:red;">并且不会给出任何回复</span>,不回送rst,只是单纯的丢弃了客户端的SYN报文。</p><p>服务器要支持较大的并发量。Backlog参数的设置不能太小。</p>