Linux支持的最大TCP连接数 & Socket IO 常见异常

目录

Client最大TCP连接数

TCP的端口是否可被共用?

重启 TCP 服务进程时,为什么会有“Address in use”的报错信息?

java.net.BindException: Address already in use: JVM_Bind

java.net.ConnectException: Connection refused: connect | java.net.ConnectException: Connection timed out: connect

java.net.SocketException: Socket is closed

 java.net.SocketException: Socket input is already shutdown  |  Socket output is already shutdown

java.net.SocketException: Software caused connection abort: socket write error

java.net.SocketException: Cannot send after socket shutdown: socket write error

 java.net.SocketTimeoutException: Read timed out

java.net.SocketException:  Connection reset  | Connection reset by peer: socket write error

Netty设置连接队列长度


Client最大TCP连接数

TCP端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535

client每次发起TCP连接请求时,除非绑定端口,通常会让系统随机选一个空闲的本地端口,该端口是独占的,不和其他TCP连接共享。

而对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万没啥问题(C10K问题)。

Linux支持的最大TCP连接数 & Socket IO 常见异常_第1张图片 ulimit -a (自定义设置)

TCP的端口是否可被共用?

协议 + (源IP地址,源端口,目的IP地址,目的端口)才确定唯一通道。

  • TCP 和 UDP 在内核中是两个完全独立的模块,当服务端收到数据包后会依据不同协议转发。所以它两可以使用同一端口!
  • TCP 连接是由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,那么只要四元组中任意一个元素发生了变化就表示不同的 TCP 连接。
    所以如果客户端已使用端口 8080 与服务端 A 建立了连接,那么客户端要与服务端 B 建立连接,还是可以使用端口 8080 的。一般而言,客户端在执行#connect 函数时从内核里随机独占式选择一个端口,然后向服务端发起 SYN 报文;客户端不建议使用 bind 函数,应该交由 客户端connect 函数来自主选择。
  • 开启 net.ipv4.tcp_tw_reuse  内核参数(客户端(连接发起方) 在调用 connect() 函数时才起作用),就会判断TCP四元组的连接状态如果处于 TIME_WAIT 状态,且该状态持续时间超过了 1 秒,那么就会重用该连接。 如果没开启,则内核遍历寻找可用端口至到端口资源耗尽,connect()函数报错返回错误。

重启 TCP 服务进程时,为什么会有“Address in use”的报错信息?

当我们重启 TCP 服务进程的时候,服务器端发起了关闭连接操作会经过四次挥手;而对于主动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL。

TIME_WAIT 状态的连接使用的 IP+PORT 仍然被认为是被占用的,相同机器上不能够在该 IP+PORT 组合上进行绑定,所以报错。

可以在调用 bind 前,对 socket 设置 SO_REUSEADDR 属性来解决这个问题。它的作用有

  1. 如果当前启动进程绑定的 IP+PORT 有存在处于TIME_WAIT 状态的连接占用,但是新启动的进程使用了 SO_REUSEADDR 选项,那么该进程就可以绑定成功。
  2. 绑定的 IP地址 + 端口时,只要 IP 地址不是正好(exactly)相同,那么允许绑定。(这也解决了ip是 ‘0.0.0.0’ 无法与其他ip绑定同一端口的问题 )

java.net.BindException: Address already in use: JVM_Bind

该异常发生在服务器端启动  new ServerSocket(port)(port是一个0,65536的整型值)操作时:

同一个端口只能被一个ServerSocket绑定监听,否则报错:

   Exception in thread "main" java.net.BindException: Address already in use: JVM_Bind
	at java.net.DualStackPlainSocketImpl.bind0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketBind(Unknown Source)
	at java.net.AbstractPlainSocketImpl.bind(Unknown Source)
	at java.net.PlainSocketImpl.bind(Unknown Source)
	at java.net.ServerSocket.bind(Unknown Source)
	at java.net.ServerSocket.(Unknown Source)
	at java.net.ServerSocket.(Unknown Source)
	at com.noob.learn.bio.BioServer.main(BioServer.java:37)

准确的说:

如果两个 TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use” 。 需要注意的是,如果ip是 ‘0.0.0.0’ 也会报错,因为它代表任意地址。 

java.net.ConnectException: Connection refused: connect | java.net.ConnectException: Connection timed out: connect

该异常发生在客户端进行 new Socket(ip, port)操作时: ip | port有错  或 服务端没启动... 。

java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.(Socket.java:434)
	at java.net.Socket.(Socket.java:211)
	at com.noob.bio.BioClient.main(BioClient.java:32)
java.net.ConnectException: Connection timed out: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.(Socket.java:434)
	at java.net.Socket.(Socket.java:211)
	at com.noob.bio.BioClient.main(BioClient.java:32)

java.net.SocketException: Socket is closed

该异常在客户端和服务器均可能发生。异常的原因是: 一方主动关闭了连接后(调用了Socket#close方法)再对连接进行读写操作。

java.net.SocketException: socket closed
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
	at java.io.FilterInputStream.read(FilterInputStream.java:107)
	at com.noob.bio.RequestHandler.handleWithoutLineBreak(RequestHandler.java:85)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:50)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

 java.net.SocketException: Socket input is already shutdown  |  Socket output is already shutdown

在关闭输入流Socket#shutdownInput() 后, 再次关闭:

java.net.SocketException: Socket input is already shutdown
	at java.net.Socket.shutdownInput(Socket.java:1525)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:58)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

 在关闭输入流Socket#shutdownOutput() 后, 再次关闭 :

java.net.SocketException: Socket output is already shutdown
	at java.net.Socket.shutdownOutput(Socket.java:1555)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:59)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

java.net.SocketException: Software caused connection abort: socket write error

软件原因导致连接中断 。

关闭数据接收Socket#shutdownInput() ,此时#read不再能读到客户端上送报文, 但同时也导致写出异常

java.net.SocketException: Socket input is already shutdown
	at java.net.Socket.shutdownInput(Socket.java:1525)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:58)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)
java.net.SocketException: Software caused connection abort: socket write error
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:143)
	at com.noob.bio.RequestHandler.keepOut(RequestHandler.java:70)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:53)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

本例以8082主动发起shutdownInput ,直接将Tcp关闭了

Linux支持的最大TCP连接数 & Socket IO 常见异常_第2张图片

主动发出了RST :

java.net.SocketException: Cannot send after socket shutdown: socket write error

关闭数据写出 socket#shutdownOutput:

java.net.SocketException: Socket output is already shutdown
	at java.net.Socket.shutdownOutput(Socket.java:1555)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:59)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)
java.net.SocketException: Cannot send after socket shutdown: socket write error
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:143)
	at com.noob.bio.RequestHandler.outwrite(RequestHandler.java:136)
	at com.noob.bio.RequestHandler.handleWithoutLineBreak(RequestHandler.java:86)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:50)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

此时: 依然可以接受到客户端的上送数据

 服务端主动shutdownOutput之后:进入到FIN_WAIT_2 状态,(主动发起了FIN, 并收到了客户端的ACK);  客户端进入到了 CLOSE_WAIT 状态。

Linux支持的最大TCP连接数 & Socket IO 常见异常_第3张图片

 主动发出了FIN :

 java.net.SocketTimeoutException: Read timed out

Socket#setSoTimeout 设置 客户端和服务连接建立后据传输过程中数据包之间间隔的最大时间 ;

InputStream#read() 会阻塞超时,该选项必须在进入阻塞操作之前启用才能生效!应用在ServerSocket.accept()、SocketInputStream.read() 、DatagramSocket.receive()这几种阻塞场景。

只要服务端不要在捕获该Exception后就直接关闭sokcet,依然可以拿socketInputStream处理 ,Socket仍然有效。

eg. 设置1秒超时,如果每隔0.8秒传输一次数据,传输10次,总共8秒,这样是不超时的。而如果任意两个数据包之间的时间超过了1秒,则超时SocketTimeoutException 。

java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
	at java.io.FilterInputStream.read(FilterInputStream.java:107)
	at com.noob.bio.RequestHandler.handleWithoutLineBreak(RequestHandler.java:85)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:50)
	at java.lang.Thread.run(Thread.java:748)
10:26:25.579 [Thread-0] INFO com.noob.bio.RequestHandler - 接收到客户端/127.0.0.1:61985的信息: 客户端/127.0.0.1:61985的慰问2
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
10:26:25.579 [Thread-0] INFO com.noob.bio.RequestHandler - 发出信息->>>>服务端的感谢1, 因为: 客户端/127.0.0.1:61985的慰问2
                                                                                                                            

java.net.SocketException:  Connection reset  | Connection reset by peer: socket write error

  • 一端返回了RST指令时(eg. Socket被主动关闭或异常退出),此时另一端还在往Socket发送数据,发送的第一个数据包引发该异常: “Connection reset by peer”。
java.net.SocketException: Connection reset by peer: socket write error
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:143)
	at com.noob.bio.RequestHandler.outwrite(RequestHandler.java:138)
	at com.noob.bio.RequestHandler.handleWithoutLineBreak(RequestHandler.java:86)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:50)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)
  • 一端返回了RST时,此时另一端正在从Socket里读数据则会提示 “Connection reset”;
// 客户端异常退出时,服务端read()抛出异常
java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:154)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
	at java.io.FilterInputStream.read(FilterInputStream.java:107)
	at com.noob.bio.RequestHandler.handleWithoutLineBreak(RequestHandler.java:85)
	at com.noob.bio.RequestHandler.run(RequestHandler.java:50)
	at com.noob.bio.BioServer.requestHandler(BioServer.java:73)
	at com.noob.bio.BioServer.main(BioServer.java:65)

那什么时候服务端会发送RST指令报文呢?

  1. 当收到TCP报文,但是发现该报文不是已建立的TCP连接列表可处理的,则其直接向对端发送reset报文RST;
  2. 服务器的TCP全连接队列(accept queue)(队列里连接状态为ESTABLISHED,长度是: min(/proc/sys/net/ipv4/tcp_max_syn_backlog[半连接队列大小],/proc/sys/net/core/somaxconn[全连接队列长度]))溢出。
    1. tcp_abort_on_overflow  = 1 时 :  向Client回复RST,表示连接无法建立;
    2. tcp_abort_on_overflow  = 0 时 : 直接丢弃Client发过来的ack , 也不回应; 这样就会导致在Client中的连接一直处在ESTABLISHED状态却又无法正常工作。
  3. 防火墙的问题...
  •  开启了syncookies的话( TCP参数 net.ipv4.tcp_syncookies  = 1  ),将连接信息编码在ISN(initialsequencenumber)中返回给客户端,这时服务端不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回的ISN还原连接信息,以完成连接的建立;避免了半连接队列被攻击SYN包填满,半连接队列(sync queue)(队列里连接状态为SYN_RECV)就相当于是无限大的; 
  •  没开的话: 半连接队列的长度将为 /proc/sys/net/ipv4/tcp_max_syn_backlog 。此时对半连接填满时的处理策略是:server将丢弃请求连接的SYN, 不回复SYN+ACK, 这样就会造成Client端收不到握手响应,始终处在SYN_SENT状态。经过几次重传后,客户端 #connect() 调用失败。

Linux支持的最大TCP连接数 & Socket IO 常见异常_第4张图片

Netty设置连接队列长度

默认设置见方法: NetUtil#SOMAXCONN

也可以通过Option方法自定义backlog的大小 :

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
        .channel(NioServerSocketChannel.class)        // NIO
        .option(ChannelOption.SO_BACKLOG,128) // 临时存放已完成三次握手的请求的队列的最大长度。 如果大于队列的最大长度,请求会被拒绝
        .childOption(ChannelOption.SO_KEEPALIVE,true) // 长连接
        .handler(new ChannelInitializer() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception { }
        });

原生Socket则是在创建ServerSocket时指定,最终由方法 ServerSocket#bind -> SocketImpl#listen 绑定后开启监听时使用。

Linux支持的最大TCP连接数 & Socket IO 常见异常_第5张图片

 Linux支持的最大TCP连接数 & Socket IO 常见异常_第6张图片

Linux支持的最大TCP连接数 & Socket IO 常见异常_第7张图片 在创建Socket|ServerSocket后设置

你可能感兴趣的:(http,jvm,java,服务器)