目录
- 系列文章
- unix网络编程1.1——TCP协议详解(一)
- unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现
- unix网络编程2.2——高并发服务器(二)多进程与多线程实现
- unix网络编程2.3——高并发服务器(三)多路IO复用之select
- unix网络编程2.4——高并发服务器(四)epoll基础篇
- unix网络编程2.5——高并发服务器(五)epoll进阶篇——基于epoll实现reactor
- unix网络编程2.6——高并发服务器(六)基于epoll&&reactor实现http服务器
- unix网络编程2.7——高并发服务器(七)基于epoll&&reactor实现web_socket服务器
- 网络编程所用API以及相关数据结构
- 建立连接阶段
- 数据传输阶段
- 断开连接阶段
- 参考:
系列文章
阅读本文需要先阅读下面的文章:
unix网络编程1.1——TCP协议详解(一)
unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现
unix网络编程2.2——高并发服务器(二)多进程与多线程实现
unix网络编程2.3——高并发服务器(三)多路IO复用之select
unix网络编程2.4——高并发服务器(四)epoll基础篇
unix网络编程2.5——高并发服务器(五)epoll进阶篇——基于epoll实现reactor
unix网络编程2.6——高并发服务器(六)基于epoll&&reactor实现http服务器
unix网络编程2.7——高并发服务器(七)基于epoll&&reactor实现web_socket服务器
网络编程所用API以及相关数据结构
tcb数据结构
-
Socket包含两部分,一个是IP地址,一个是端口号。同一个设备可以对应一个IP地址,但不同的管道用不同的端口号区分,于是同一个设备发送给其他不同设备的信息就不会产生混乱。在同一时刻,设备可能会产生多种数据需要分发给不同的设备,为了确保数据能够正确分发,TCP协议用一种叫做TCB(Transmission Control Block,传输控制块)的数据结构把发给不同设备的数据封装起来。
-
一个TCB数据块包含了数据发送双方对应的socket信息以及拥有装载数据的缓冲区。在两个设备要建立连接发送数据之前,双方都必须要做一些准备工作,分配内存建立起TCB数据块就是连接建立前必须要做的准备工作。我们还需要了解的一点是TCP连接的建立方式,由于TCP协议建立在服务器–客户端的模式之上,因此对于两种不同角色的设备,他们发起连接的方式不一样。
-
客户端发起连接的方式叫主动打开(Active Open)。也就是客户端需要主动向服务器发送消息,表达自己想建立数据连接的请求。服务器发起连接的方式叫被动打开(Passive Open),通常服务器在没有客户端主动请求的时候不知道当前有哪个设备想向它发起连接,因此它只能构建一个端口并监听该端口,等待客户端从该端口向它发起连接请求。在OPEN(打开)阶段无论是客户端还是服务器都需要准备好TCB数据结构,但由于服务器不知道要连接它的客户端的地址信息,因此在构建TCB模块时会默认将客户端对应的Socket数据初始化为0。
-
当客户端和服务器端双方都把自己的Socket和TCB数据结构准备好后,双方就可以进入所谓的“三次握手”TCP连接建立过程。
tcb对应的结构体
struct tcb {
short tcb_state; /* TCP state */
short tcb_ostate; /* output state */
short tcb_type; /* TCP type (SERVER, CLIENT) */
int tcb_mutex; /* tcb mutual exclusion */
short tcb_code; /* TCP code for next packet */
short tcb_flags; /* various TCB state flags */
short tcb_error; /* return error for user side */
IPaddr tcb_rip; /* remote IP address */
u_short tcb_rport; /* remote TCP port */
IPaddr tcb_lip; /* local IP address */
u_short tcb_lport; /* local TCP port */
struct netif *tcb_pni; /* pointer to our interface */
tcpseq tcb_suna; /* send unacked */
tcpseq tcb_snext; /* send next */
tcpseq tcb_slast; /* sequence of FIN, if TCBF_SNDFIN */
u_long tcb_swindow; /* send window size (octets) */
tcpseq tcb_lwseq; /* sequence of last window update */
tcpseq tcb_lwack; /* ack seq of last window update */
u_int tcb_cwnd; /* congestion window size (octets) */
u_int tcb_ssthresh; /* slow start threshold (octets) */
u_int tcb_smss; /* send max segment size (octets) */
tcpseq tcb_iss; /* initial send sequence */
int tcb_srt; /* smoothed Round Trip Time */
int tcb_rtde; /* Round Trip deviation estimator */
int tcb_persist; /* persist timeout value */
int tcb_keep; /* keepalive timeout value */
int tcb_rexmt; /* retransmit timeout value */
int tcb_rexmtcount; /* number of rexmts sent */
tcpseq tcb_rnext; /* receive next */
tcpseq tcb_rupseq; /* receive urgent pointer */
tcpseq tcb_supseq; /* send urgent pointer */
int tcb_lqsize; /* listen queue size (SERVERs) */
int tcb_listenq; /* listen queue port (SERVERs) */
struct tcb *tcb_pptcb; /* pointer to parent TCB (for ACCEPT) */
int tcb_ocsem; /* open/close semaphore */
int tcb_dvnum; /* TCP slave pseudo device number */
int tcb_ssema; /* send semaphore */
u_char *tcb_sndbuf; /* send buffer */
u_int tcb_sbstart; /* start of valid data */
u_int tcb_sbcount; /* data character count */
u_int tcb_sbsize; /* send buffer size (bytes) */
int tcb_rsema; /* receive semaphore */
u_char *tcb_rcvbuf; /* receive buffer (circular) */
u_int tcb_rbstart; /* start of valid data */
u_int tcb_rbcount; /* data character count */
u_int tcb_rbsize; /* receive buffer size (bytes) */
u_int tcb_rmss; /* receive max segment size */
tcpseq tcb_cwin; /* seq of currently advertised window */
int tcb_rsegq; /* segment fragment queue */
tcpseq tcb_finseq; /* FIN sequence number, or 0 */
tcpseq tcb_pushseq; /* PUSH sequence number, or 0 */
};
服务端
- socket(): 创建listen_fd
- bind(): 绑定服务端ip和端口,使listen_fd与ip和端口建立关系,此时连接4元组(src ip, src port, des ip, des port)中的src ip和src port已经准备好,并且tcb准备已经初始化
- listen(): 监听listen_fd,此时服务端的状态由CLOSE转为LISTEN, 当客户端发送SYN包准备建立连接,服务端会init/add new tcb作为一个新的节点push in syn队列,此时服务端的状态由LISTEN转为SYN_RCVD, 并且服务端会发送SYN + ACK给客户端
- accept(): 当收到客户端的ACK后,此时会把syn队列中的tcb节点pop出来,再push in accept队列中,服务端状态由SYN_RCVD转为ESTABLISHED,三次握手完成。后续数据传输会从accept队列中取出tcb节点进行数据传输。并且accept为每个节点分配一个socket(conn_fd)
- recv/read(): 接收数据
- send/write(): 发送数据
- close(): 关闭连接
客户端
- socket(): 创建client_fd
- bind(): 可选,非必须
- connect(): 基于client_fd,利用连接4元组向服务端发SYN包,三次握手开始,连接开始建立,此时客户端状态从CLOSE转为SYN_SEND, 注意,connect其实是阻塞的,客户端的状态迁移会阻塞到三次握手成功完成或者失败才会返回,因此客户端此时还要等待服务端回SYN + ACK,并且客户端再回最后1次ACK给服务端,此时客户端状态从SYN_SEND转为ESTABLISHED,三次握手完成。
- recv/read(): 接收数据
- send/write(): 发送数据
- close(): 关闭连接
建立连接阶段
TCP头数据格式
序列号的作用:解决数据传输乱序
listen时backlog的意义
- int listen (int socketfd, int backlog)
参数一 socketfd 为 socketfd 文件描述符
参数二 backlog,参数历史版本有所变化
早期backlog 是 SYN队列大小,也就是未完成连接的队列大小
后来backlog 变成 accept 队列,也就是已完成连接的队列长度, 所以现在通常认为backlog 是accept队列
但是上限值是内核参数 somaxconn 的⼤⼩,也就说 accpet 队列⻓度 = min(backlog, somaxconn)。
- linux内核中会维护两个队列:syn队列与accept队列
半连接队列 —— syn队列
- 接收到一个syn建立连接请求,服务端处于syn_rcvd状态,此时未完成连接
全连接队列 —— accept队列
- 已完成tcp3次握手过程,处于established状态,此时已完成连接
服务端三次握手syn队列与accept队列示意图
listen_fd 能不能发送数据?
- 可以,三次握手时,服务端收到客户端第一次SYN后,给其回SYN + ACK时,就是用listen_fd回的
syn泛洪攻击(待学习)
https://blog.csdn.net/bandaoyu/article/details/109400717
数据传输阶段
send 发送与tcp粘包、分包
- 客户端发送数据时,可能会出现1次send或多次send的情况,比如发送几个G的视频,服务端传输层如果用tcp,则需要考虑分包、粘包的处理,因为tcp时面向字节流,只能对发送的字节流进行保序,但是服务端并不知道1次应该读多少数据,因为应用层对传输层进行封装协议的时候,需要对数据做分包处理,常见的有两种措施:
- 应用层协议头前面加包长度:
每次解析到tcp的playload时,取前1个word(提前约定好,也可以是2个字节等其他长度)作为包长度,然后对字节流直接偏移或循环读包长度的码流 - 为每个包加分隔符:
比如http协议每个包直接以\r\n\r\n作为包 end 的标志
服务端不能通过send的返回值 > 0 来判断发送是否成功
- send的作用仅是把data拷贝到协议栈中send buffer中,后续内核驱动网卡将buffer中的数据发送出去,然后利用连接4元组中的dest ip在路由中不断找下一跳去发包,直到对端的网卡中,当send > 0仅代表数据拷贝到协议栈中成功,对方是否接收数据成功发送端是判断不了的,如果发送端需要知道接收端是否接收数据成功,需要接收端给发送端返回一个值来确定(应用层面确定数据发送成功)系统底层确定数据是否发送成功(协议层面确定数据发送成功)
服务器探测机制——心跳包
- 定时机制,检测客户端是否在线,宕机
- 服务器给客户端发数据发现失败
断开连接阶段
半关闭与shutdown
- 补充说明:参考文章是多年前的多进程技术,现在基于IO多路复用其实不会由于fork产生子进程使得socket_fd的引用计数 != 1而产生这种情况(但其他情况会导致)
4次挥手状态
大量的close_wait状态的原因?怎么解决
https://www.cnblogs.com/grey-wolf/p/10936657.html
- 服务端迟迟不回FIN,其实可以用异步IO将应用层与传输层解耦
大量的time_wait状态的原因?怎么解决
https://blog.csdn.net/weixin_44844089/article/details/115626152
参考:
https://www.jianshu.com/p/4138fc17b126
https://xiaolincoding.com/