计算机网络实践记录
- 天津大学 2024 计算机网络 TCP 课程实践
- Lab Member:海棠未雨,梨花先雪
- 最终成绩 93 分捏 😋
文件结构说明
1 | tju_tcp ----------------- 项目根目录 |
系统环境信息
1 | neofetch |
具体实验记录
连接管理

三次握手建立连接
1. tju_connect( )
tju_connect( )
函数是三次握手的开端。在该函数中,客户端的sock绑定好本地的ip和port后向服务器端发送SYN包,同时把当前的状态变化为SYN_SENT。成功建立连接后将sock放入ESTABLISHED的hash表中。
1 | int tju_connect(tju_tcp_t* sock, tju_sock_addr target_addr){ |
2. Timeout_retransmission( )
1 | void Timeout_retransmission(tju_tcp_t* sock, int exp_state, char* pkt, int pktlen) { //超时重传发包函数 |
3. change_sock_state( )
1 | void change_sock_state(tju_tcp_t* sock, int state) { //添加:sock状态改变 |
4. tju_accept()
tju_accept( )
函数中添加了一个阻塞,使得只有当全连接hash表中存在这一个sock,服务器端才算连接完成,才能把新的sock加入到ESTABLISHED的hash表中。
1 | tju_tcp_t *tju_accept(tju_tcp_t *listen_sock){ |
5. tju_handle_packet( )
tju_handle_packet( )
函数中需要解决对各种报文的解析。onTCPPocket( )
函数如果从established_hash或listen_hash中找到了对应的socket,就会调用 tju_handle_packet( )
函数对收到的数据包进行处理。
1 | int tju_handle_packet(tju_tcp_t* sock, char* pkt){ |
6. pkt2buffer( )
1 | void pkt2buffer(tju_tcp_t* sock, char* pkt) { |
四次挥手关闭连接
1. tju_close ( )
四次挥手有两种情况,分别为先后关闭和同时关闭,两种方式首先都需要调用tju_close( )
函数,因此,在tju_close( )
函数中,需要构造一个 FIN+ACK 报文,seq和ack都是当前报文窗口的下一个数值,并把自身状态变为 FIN-WAIT1。
1 | int tju_close (tju_tcp_t* sock){ |
2. tju_handle_packet( )
接下来分为两种情况,但处理过程均在tju_handle_packet( )
中完成,tju_handle_packet( )
作为对收到的包进行响应的函数。
1 | int tju_handle_packet(tju_tcp_t* sock, char* pkt){ |
可靠数据传输
发送缓冲区管理
1. global.h
中宏定义 TCP 发送窗口大小。
1 |
2. tju_packet.h
中为发送缓冲区创建1000个报文长度的循环队列,方便数据包超时重传。
1 | // TCP 报文的结构定义 |
3. tju_send( )
上层调用 tju_send( )
时,我们在 tju_send( )
函数内创建发送线程并把发送数据存入缓冲区中。
这里我们先了解一下创建线程 pthread_create( )
函数。
int pthread_create(pthread_t* restrict tidp,const pthread_attr_t* restrict_attr,void* (start_rtn)(void),void *restrict arg);
- tidp:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID。
- attr:用于定制各种不同的线程属性。APUE的12.3节讨论了线程属性。通常直接设为NULL。
- start_rtn:新创建线程从此函数开始运行。无参数是arg设为NULL即可。
- arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
- 返回值为 0(表示线程成功创建)。
1 | int send_thread_flag = 0; |
4. send_pkt( )
1 | void *send_pkt(tju_tcp_t* sock){ |
失序报文数组的管理
1. tju_handle_packet( )
tju_handle_packet( )
中添加建立连接后收到数据包的处理。
1 | int tju_handle_packet(tju_tcp_t* sock, char* pkt){ |
2. tju_tcp_t
结构体中创建空间用来存储失序报文并记录失序报文的个数。
1 | // TJU_TCP 结构体 保存TJU_TCP用到的各种数据 |
3. serverrdt( )
1 | void serverrdt(tju_tcp_t* sock, char* pkt){ // 服务器端处理数据包 |
4. my_swap( )
1 | void my_swap(char** a, char** b){ |
实现累计应答

Figure 1. 累计应答
1. clientrdt( )
clientrdt( )
函数中添加累计应答处理。当发送方收到任意大于当前base的ack报文后,直接把当前base进行更新即可。
1 | void clientrdt(tju_tcp_t* sock, char* pkt){ // 客户端处理数据包 |
超时重传
1. tju_send( )
tju_send( )
中创建了一个新的线程判断是否要进行重传。
1 | int resend_thread_flag = 0; |
2. resend_pkt( )
1 | void *resend_pkt(tju_tcp_t* sock){ |
3. clientrdt( )
clientrdt( )
函数中添加动态设置超时间隔的实现。
1 | void clientrdt(tju_tcp_t* sock, char* pkt){ // 客户端处理数据包 |
快速重传

Figure 1. 快速重传
1. clientrdt( )
clientrdt( )
函数中添加快速重传的实现。
1 | void clientrdt(tju_tcp_t* sock, char* pkt){ // 客户端处理数据包 |
流量控制
接收方计算接受缓冲区大小
1. serverrdt( )
1 | void serverrdt(tju_tcp_t* sock, char* pkt){// 服务端处理数据包 |
发送方调整发送缓冲区大小
1. clientrdt( )
clientrdt( )
函数确定window_size大小。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |
2. get_advertised_window( )
1 | uint16_t get_advertised_window(char* msg){ |
0窗口探测
1. send_pkt( )
send_pkt( )
发送数据的线程中加入一个判断条件。
0窗口探测用于在ADVERTISED WINDOW为0的情况下,发送大小为1的数据报文以获得实时的窗口返回值。
1 | void *send_pkt(tju_tcp_t* sock){ |
拥塞控制
慢启动
1. 套接字的初始化使得发送方从其他状态进入慢启动状态。
1 | tju_tcp_t* tju_socket(){ |
2. resend_pkt( )
函数超时事件使得发送方从其他状态进入慢启动状态。
1 | if (nowtimeval > timeoutval){ |
在慢启动状态下,发送方每当接收到正确的 ACK 报文,就会将其拥塞窗口增大 1 个 MSS(MAX_DLEN)。虽然是不断的自增 MSS,但是由于拥塞窗口的增大(进而导致发送窗口的增大),每次自增的次数为 1->2->4->8 直至达到ssthresh,所以拥塞窗口整体上呈现指数增长的趋势。
3. clientrdt( )
clientrdt( )
函数中发送方每当接收到正确的 ACK 报文,就会将其拥塞窗口增大 1 个 MSS。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |
4. resend_pkt( )
resend_pkt( )
函数中处理超时重传导致慢启动参数变化。
1 | void *resend_pkt(tju_tcp_t* sock){ |
拥塞避免
1. 拥塞窗口超过 ssthresh 进入拥塞避免状态。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |
2. 快速恢复阶段收到正确的 ACK 使得发送方从其他状态进入拥塞避免状态。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |
3. 在拥塞避免状态下,发送方每当收到一个正确的 ACK 报文,拥塞窗口就会增大(1/cwnd)个 MSS。拥塞窗口整体上呈现线性增长的趋势。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |
快速恢复
1. clientrdt( )
发送方收到 3 个冗余 ACK 时,进入快速恢复状态。
1 | if(sock->window.wnd_send->ack_cnt == 3){ //快速重传 |
2. 在快速恢复状态下,如果还是收到冗余 ACK,那么依然在此状态,cwnd+=MSS;当收到正确 ACK 时,则进入拥塞避免状态 ,cwnd=ssthresh;当超时时,回到慢启动,cwnd=1MSS。
1 | void clientrdt(tju_tcp_t *sock, char *pkt){ |