目录
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设置连接队列长度
TCP端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535。
client每次发起TCP连接请求时,除非绑定端口,通常会让系统随机选一个空闲的本地端口,该端口是独占的,不和其他TCP连接共享。
而对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万没啥问题(C10K问题)。
ulimit -a (自定义设置)协议 + (源IP地址,源端口,目的IP地址,目的端口)才确定唯一通道。
当我们重启 TCP 服务进程的时候,服务器端发起了关闭连接操作会经过四次挥手;而对于主动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL。
TIME_WAIT 状态的连接使用的 IP+PORT 仍然被认为是被占用的,相同机器上不能够在该 IP+PORT 组合上进行绑定,所以报错。
可以在调用 bind 前,对 socket 设置 SO_REUSEADDR 属性来解决这个问题。它的作用有
该异常发生在服务器端启动 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’ 也会报错,因为它代表任意地址。
该异常发生在客户端进行 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)
该异常在客户端和服务器均可能发生。异常的原因是: 一方主动关闭了连接后(调用了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)
在关闭输入流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)
软件原因导致连接中断 。
关闭数据接收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关闭了 :
主动发出了RST :
关闭数据写出 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 状态。
主动发出了FIN :
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 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)
// 客户端异常退出时,服务端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指令报文呢?
- 开启了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() 调用失败。
默认设置见方法: 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 绑定后开启监听时使用。
在创建Socket|ServerSocket后设置