TCP
1 TCP握手/挥手
1.1 服务端代码
1 |
|
1.2 客户端代码
1 |
|
1.3 网络抓包
1.3.1 命令工具
1 |
|
1.3.2 网络接口查询
1 |
|
1.3.3 抓包
1 |
|
以上可以清晰看出所谓的tcp连接和断开的3次握手和4次挥手
- tcp包中seq是随机的
- 对包的确认ack=seq+1
- 发送过程中将各自的win信息告诉对方,通过窗口机制解决网络拥塞场景下发送速率问题
尝试了几次抓包都发现在3次握手之后,双方建立了tcp连接之后,服务端都会紧随着发一个空包ack包,这个地方留着明白了再回来填坑(todo)
2 Socket
2.1 服务端代码
1 |
|
我们将代码阻塞在创建ServerSocket之后,也就是说在程序层面上仅仅做了一件事情,我们观察现象去推导内核做了什么事情
2.2 客户端代码
1 |
|
正常情况下,客户端尝试跟服务端tcp连接之后,发个数据,然后阻塞在这,保持这条连接状态
2.3 终端查看文件描述符和网络状态
jps
查看服务对应的进程号
sudo lsof -p {pid}
查看进程号信息
2.3.1 只启动服务端
服务端
当前服务监听着9993这个端口
2.3.2 启动一个客户端
服务端
客户端
netstat
很明显
- 服务端侧依然只能看到服务端程序在监听着端口,看不到服务端跟某个客户端已经建立了tcp连接
- 客户端侧可以看到一条跟服务端的tcp连接,本机:51466->本机:9993
- 服务端其实已经收到了客户端发送的消息,放在了Recv-Q中了
2.3.3 再启动一个客户端
服务端
客户端
netstat
不出所料
- 服务端侧依然看不到已经建立好的连接
- 新起的客户端可以看到已经跟服务端建立好了一条tcp连接
- 客户端已经发了条消息给服务端,服务端也收到了,放在了Recv-Q
2.4 放开服务端代码阻塞
程序能够继续执行下去,在while
轮询中,两次通过server.accept()
系统调用向内核获取到了连接的信息,其实就是上面9u和10u两个文件描述符的封装
无论程序是否通过代码显式地向os申请,这两个socket都实实在在存在着
2.5 四元组
到此可以再理解一下socket的本质
Socket就是一个四元组,根据源和目标的ip跟port组合出具有唯一性的东西就是socket,对于os而言它一定也是个资源
再回顾下上面的追溯过程,在服务端程序一直阻塞在Socket socket = ServerSocket#accept()
之前,虽然程序没有继续执行,但是客户端和服务端已经正常建立了tcp连接,并且发送了消息,完成了通信
总结下来就是
- tcp的连接跟源和目标(ip, port)有关,给定这两个元祖组合出四元组,os就可以创建出来资源(资源的本质应该就是os标识的fd和分配的内存)
- 消息会有对应的内存缓存区域存放,这些操作托管给了os的内核完成
- 用户程序的
Socket socket = ServerSocket#accept()
并不是对tcp本身进行干涉,而是通过sc向os的内核获取建立好的tcp连接的fd,然后java将内核返回的已经建立好的tcp连接的一个fd封装成Socket对象 - 之后用户程序依赖于scoket对象的操作(读写)都是跟内核获取交互内存信息而已(Recv-Q、Send-Q)
2.6 ServerSocket构造参数backlog
1 |
|
ServerSocket
的构造方法提供了backlog
参数,根据doc描述requested maximum length of the queue of incoming connections
可以看出是设置了个连接数阈值,支持的最大连接请求数=backlog+1
代码验证一下
1 |
|
1 |
|
设置了backlog
值为3,在客户端代码轮询连接被监听的端口,达到阈值之后socket.connect()
阻塞住