文章目录
一、Posix API介绍
Posix API就是Linux网络编程的这些API, 本文带入TCP连接通信的背景来介绍
//client
socket():创建套接字
bind():绑定ip地址、端口等信息到socket上
listen():监听
accept():等待客户请求
send():发送数据
recv():接收数据
close():关闭连接
//server
socket():创建套接字
bind():绑定ip地址、端口等信息到socket上(可选)
connect():连接服务器
send():发送数据
recv():接收数据
close():关闭连接
//epoll
epoll_create():创建一个epoll实例
epoll_ctl(): epoll的事件注册,向epoll对象中添修或删事件
epoll_wait(): 等待事件的产生,类似于select调用
//facntl
fcntl()的核心用途:
+修改文件描述符属性(如非阻塞模式)
+管理文件锁(协调多进程/线程访问)
+获取或设置文件状态标志
典型场景:
网络编程中设置非阻塞套接字/
多进程日志文件的并发写入控制/
确保文件操作的原子性
socket
int socket(int domain, int type, int protocol);
+ domain:通信协议,常用AF_INET(IPv4) 和 AF_INET6(IPv6)
+ type:数据传输方式,常用SOCK_STREAM(字节流)和SOCK_DGRAM(报文)
+ protocol:传输协议,通常为0
- socket包含 : 一个文件描述符(sockfd)与 tcp控制块这两个信息
实现了两个功能:详解笔记
(1)分配fd (使用一个bitmap的算法)
(2)分配tcp控制块的信息(包含了TCP为连接维护的信息:两个方向的序号,窗口大小,重传次数等的信息)
bind
int bind(int sockfd, const struct *sockaddr addr, socklen_t addrlen);
- 五元组 < 源IP地址 , 源端口 , 目的IP地址 , 目的端口 , 协议 >
- bind的作用就是绑定本地的ip和端口,还有协议。也就是将TCB的五元组填充 <目的IP地址,目的端口,协议>
- 注:客户端可以绑定(bind),绑定即确定了一个固定的端口,若不绑定,则会选择任意可用端口
二、三次握手(建立连接)
connect
- 三次握手发生在协议栈和协议栈之间,而posix api connect 只是一个导火索, 调用connect之后,协议栈开始三次握手。
- connect函数到底组不阻塞,取决于传进去的fd,如果fd是阻塞,那么直到第三次握手包发送之后就会返回。如果fd是非阻塞,那么返回-1的时候说明连接建立中,返回0代表连接建立成功。
listen
int listen(int sockfd, int backlog);
listen作用:
(1)将TCP连接的状态设置成LISTEN状态
(2)分配全连接队列(accept_queue)和半连接队列(syn_queue)
三次握手流程图解:
1. 三次握手流程
- 第一次握手 : 首先客户端请求连接,发送第1次数据包:里面带有一个同步头syn,seq=x,
- 第二次握手 : 服务器listen()监听到连接后,服务端将其加入到[半连接syn队列],并且向客户端响应发送syn+ack报文;
- 第三次握手: 最后等到客户端发送ack应答报文时,服务端将该连接从[半连接syn队列]中取出,加入到[全连接accept队列]当中。
2. tcp连接是双向的,双向怎么体现?
客户端发送一个数据包告诉服务器我现在发送的数据包序号是多少,
服务端返回的时候也告诉客户端我这个数据包的序号是多少。这就是双向
3. 三次握手为什么是三次?
A客户端发送一次syn,B服务器回一个ack并且携带自己的syn,这时候能确定B服务器存在,这里两次
A客户端再返回一个ack,这时候能确定A客户端存在,这时候就确定了这个双向通道是ok,加上这次那就是三次了
accept
这个时候连接已经建立完,双方都知道对方的存在,现在可以调用accept
int clientfd = accept()
accept函数只做两件事情:
(1)分配fd给对应的客户端
(2)将fd与tcb进行映射
三次握手以后的数据传输图解:
send / recv :数据传输发送与接收
ssize_t send(sockfd, *buf, size_t len, flags);
ssize_t recv(sockfd, *buf, size_t len, flags);
- send只是了将用户态的数据拷贝到内核协议栈对应的TCB里面。至于真正数据发送的时机,什么时候发送的,发送的数据有没有与之前的数据粘在一起,都不是由应用程序决定的,应用程序只能将数据拷贝到内核buffer缓冲区里面。 然后协议栈将sendbuffer的数据,加上TCP的头,加上IP的头,加上以太网的头一起打包发出去。所以调用send将buffer拷贝到内核态缓冲区,与tcp发送数据到对端,是异步的过程。
- 对端网络协议栈接收到数据,同样开始解析,以太网的头mac地址是谁,ip地址从哪里来的,源端口是多少,目的端口是发到哪个进程里面,然后将数据写进对应的TCB里。recv只是将内核态的缓冲区数据拷贝到用户态里面,所以tcp数据到达TCP的recv buffer缓冲区里,与调用recv将缓冲区buffer拷贝到用户态,这两个过程也是异步的。
三、四次挥手(断开连接)
正常情况:
假如左侧没有收到ack,而是先收到fin, 根据迁移图,FIN_WAIT_1进入CLOSING状态,收到ACK后进入TIME_WAIT状态
四次挥手不分客户端和服务器端,只有主动方和被动方。主动方先调用close,close函数回收了fd,然后发了fin包;接收方recv()返回为0的包,即知道对方要关闭连接。后续流程如上图所示。
1. 四次挥手断开连接的流程
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态.
- 第一次挥手: 客户端A发出连接释放报文FIN包,此时,A进入FIN-WAIT-1(终止等待1)状态
- 第二次挥手: 服务器端B接收到A连接释放报文后,发出ACK确认报文, 并且带上自己的序列号seq,此时,服务端B就进入了CLOSE-WAIT 关闭等待状态
- 第三次挥手: 客户端A接收到服务器端B的确认请求后,A就会进入FIN-WAIT-2(终止等待2)状态,等待B向A发送连接释放报文,服务器B就进入了LAST-ACK(最后确认)状态,等待客户端A的确认
- 第四次挥手: A收到B的连接释放报文后,必须发出确认ACK包,此时,客户端A就进入了TIME-WAIT**(时间等待)状态**,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当A撤销相应的TCB后,才会进入CLOSED关闭状态,B接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成
2. 为什么客户端要等待2 msl?
主要原因是为了保证主动方最后第四次挥手发送的ACK报文能到被动方,因为这个ACK报文可能丢失,并且2MSL是最长报文寿命,超过这个时间报文将被丢弃,这样确保新的连接中不会出现旧连接的请求报文
特殊情况 :
双方同时调用close,发送fin包
close: 回收fd和tcb
被动方调用close之后,fd被回收。在接收到ack以后进入CLOSED后,TCB被回收
主动方调用close之后,fd被回收,在time_wait时间到了进入CLOSED后,TCB被回收
3.理解四次挥手close流程

TCP状态迁移图
关于握手回收面试题
一些面试问题
四、思考题
1. connect,listen,accept与三次握手
2. listen参数backlog
3. syn泛洪的解决方亲
4. close与四次挥手
5. 11个状态迁移
6. 大量close_wait与time_wait的原因与解决方素
7. top keepalive与应用层心跳包
8. 拥塞控制与滑动窗口
优秀笔记:
Posix API 与 网络协议栈 详细介绍
参考学习:
https://github.com/0voice