tcp断开连接的四次挥手
先说说tcp的四次挥手,这里假定A端为主动发起关闭端,B端为被动接收关闭请求端。A把tcp的数据包中标识位FIN置为1,seq为一个随机数,发送这个包给B端,自己进入FIN_WAIT_1状态;B端收到了马上给A端回复ack(A端收到ack进入FIN_WAIT_2状态),然后自己进入CLOSE_WAIT状态。然后这个时候需要业务代码处理,把自己需要发给客户端的数据发送完,然后业务代码主动调用相应语言库函数提供的close函数,来触发关闭操作:给A端发送FIN seq的数据包,这是第三次握手。这个时候自己进入last ack状态。 A端此时收到包然后给B端口发送相应ack.A端自己此时进入time_wait状态。 B端收到ack后从last_ack就顺利进入close状态了。A端等到timewait 2msl时间后(这个时间不同的操作系统的设置不同,大约是2分钟),自动进入close状态。
如果在B端不主动调用相应自己语言的close函数,那么就会一直处于close wait状态。大量socket连接不能正常释放。直到socket服务器端打开的文件数超过系统的最大限制数,其他连接无法正常建立连接,建立连接的时候抛出too many open files异常
网上搜索的图,便于理解(侵删)
linux 统计tcp连接的各种状态的连接数
这里每5秒输出一次 可以修改为自己想要的时间
while true ;do netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' && print '-------------' ; sleep 5; done;
java版本的完整复现代码
只需要把Server.java中socket.close();这行注释掉就能观察到系统的close_wait 状态的tcp连接会一直无法释放,而打开这行注释,tcp连接即可正常关闭
Server.java
/**
* @auther zhoudazhuang
* @date 19-5-20 17:44
* @description
*/
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
public class Server {
static class Worker implements Runnable {
Socket socket;
Phaser phaser;
Worker(Socket socket,Phaser phaser){
this.socket = socket;
this.phaser = phaser;
}
@Override
public void run() {
try {
//获取输入流,并读入客户端的信息
InputStream in = socket.getInputStream(); //字节输入流
Thread.sleep(3000);
InputStreamReader inreader = new InputStreamReader(in); //把字节输入流转换为字符流
BufferedReader br = new BufferedReader(inreader); //为输入流添加缓冲
String info;
PrintWriter printWriter;
OutputStream outputStream;
//readline \n
while((info = br.readLine())!=null){
System.out.println("收到客户端发送的消息:"+info);
//获取输出流,相应客户端的信息
outputStream = socket.getOutputStream();
printWriter = new PrintWriter(outputStream);//包装为打印流
printWriter.write("来自服务端的消息!\n");
printWriter.flush(); //刷新缓冲
if (info.equals("shutdown")){
// 等待客户端断开连接
System.out.println("服务端进入关闭等待状态...");
Thread.sleep(1000*30);
// socket.shutdownInput();//关闭输入流
// socket.shutdownOutput();
//关闭资源
// printWriter.close();
// outputStream.close();
// br.close();
// inreader.close();
// in.close();
//打开注释则正常关闭 否则服务端会出现大量的close_wait状态
socket.close();
System.out.println("服务端完成等待状态...");
break;
}
}
// 需要观察 不能让线程执行完毕
System.out.println("服务端线程进行休眠,为了观察close_wait...");
Thread.sleep(1000 * 60 * 30);
System.out.println("服务端线程执行完毕,即将退出");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Phaser phaser = new Phaser();
try {
//创建一个服务器socket,即serversocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(8888);
//调用accept()方法开始监听,等待客户端的连接
System.out.println("***服务器即将启动,等待客户端的连接***");
while (true) {
Socket socket = serverSocket.accept();
executorService.execute(new Worker(socket,phaser));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client.java
import java.io.*;
import java.net.Socket;
/**
* @auther zhoudazhuang
* @date 19-5-20 17:45
* @description
*/
public class Client {
public static void main(String[] args) {
for (int i = 0; i< 1000; i++) {
new Thread(
() -> {
// 创建客户端socket建立连接,指定服务器地址和端口
try {
Socket socket = new Socket("127.0.0.1", 8888);
// 获取输出流,向服务器端发送信息
OutputStream outputStream = socket.getOutputStream(); // 字节输出流
PrintWriter pw = new PrintWriter(outputStream); // 将输出流包装为打印流
pw.write("shutdown\n");
pw.flush();
// 获取输入流,读取服务器端的响应
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("收到服务端发送过来的消息:" + info);
// 关闭资源
// 20s后主动关闭连接
Thread.sleep(1000 * 20);
System.out.println("开始关闭。。。");
//socket.shutdownInput();
//socket.shutdownOutput();
br.close();
inputStream.close();
pw.close();
outputStream.close();
socket.close();
System.out.println("client完成关闭 线程不退出 进行睡眠 否则影响评估 线程没了这边的main执行完成,则服务端会直接断开,因为客户端不在了");
Thread.sleep(1000 * 60 * 10);
System.out.println("客户端线程执行完毕");
// 关闭连接后跳出while循环 否则java.io.IOException: Stream closed
break;
}
// 需要观察 不能让线程执行完毕
System.out.println("客户端线程进行休眠,为了观察close_wait...");
Thread.sleep(1000 * 60 * 30);
System.out.println("客户端线程执行完毕,即将退出");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
})
.start();
}
}
}
golang版本的完整复现代码
只需要把c.Close()这行代码注释掉以及打开注释 然后使用上面的命令行则能直接观察到效果
server.go
package main
import (
"fmt"
"net"
"time"
)
func main() {
// tcp 监听并接受端口
l, err := net.Listen("tcp", "127.0.0.1:65535")
if err != nil {
fmt.Println(err)
return
}
//最后关闭
defer l.Close()
fmt.Println("tcp服务端开始监听65535端口...")
// 使用循环一直接受连接
for {
//Listener.Accept() 接受连接
c, err := l.Accept()
if err != nil {
return
}
//处理tcp请求
go handleConnection(c)
}
}
func handleConnection(c net.Conn) {
//一些代码逻辑...
fmt.Println("tcp服务端开始处理请求...")
//读取
buffer := make([]byte, 1024)
//如果客户端无数据则会阻塞
c.Read(buffer)
//输出buffer
c.Write(buffer)
fmt.Println("tcp服务端开始处理请求完毕...")
time.Sleep(40 * time.Second)
//c.Close()
fmt.Println("服务端开始close")
}
client.go
package main
import (
"fmt"
"net"
"sync"
"time"
)
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
//net.dial 拨号 获取tcp连接
conn, err := net.Dial("tcp", "127.0.0.1:65535")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("获取127.0.0.1:65535的tcp连接成功...")
defer conn.Close()
defer wg.Done()
//需要放在read前面,输出到服务端,否则服务端阻塞
conn.Write([]byte("echo data to server ,then to client!!!"))
//读取到buffer
buffer := make([]byte, 1024)
conn.Read(buffer)
fmt.Println(string(buffer))
time.Sleep(30 * time.Second)
conn.Close()
//便于观察
time.Sleep(30 * time.Minute)
}()
}
wg.Wait()
fmt.Println("全部完成")
}
输出
无法正常关闭会一直循环输出:(每台服务器的输出不会相同,大致如下)
CLOSE_WAIT 1009
ESTABLISHED 25
SYN_SENT 118
正常关闭的循环输出:(每台服务器的输出不会相同,大致如下)
TIME_WAIT 1000
ESTABLISHED 24
LAST_ACK 1
SYN_SENT 77
这里的time_wait状态会逐渐消失