TCP连接建立连接前的准备工作1.socket()socket()是应用程序与协议栈交互的抽象接口。如何实现的由两个部分组成一个是fd的分配有fd用户才能使用一个是tcp控制块TCBUDP连接没有TCB只有一个简单的UDP控制块记录IP和端口的分配当调用socket()创建 TCP socket 时内核会为这个 socket 创建一个 TCB 结构体隐藏在内核中用户程序无法直接访问。其中fd的分配是基于bitmap算法即0 1 2 ...n每一位都有0或1为值分配fd前会从0开始找找到某一位值为0则分配。TCB是管理状态连接的数据结构一个socket绑定唯一一个TCB。细致一点的话通过fd到进程私有的fd表中找到内核中的struct socket结构体再能找到TCB所有 socket 操作都是通过 fd 找到 TCB再由 TCB 完成实际的 TCP 协议逻辑。fd的作用和内核协议栈沟通的凭证不存储任何状态只作为一个索引唯一标识一个打开的socketTCB的作用属于内核态存储一个TCP连接的所有信息存储三次握手四次挥手的所有状态TCP状态机、连接的IPport负责TCP连接的核心操作三次握手、重传、拥塞控制2.bind() //客户端不用bind(fd,ip,port)。执行过程是通过fd找到对应的TCB然后把本地ip和本地port记录进去。因为建立连接会有一个五元组对应源ip,源port,目的ip,目的port,协议。主要是绑定本地portbind端口后方便listen监听端口。3.listen() //客户端不用listen(fd,number) listen的作用1.根据fd找到TCB 设置TCB的status为LISTEN否则后续无法三次握手建立连接2.为TCB初始化 半连接队列SYN queue 和 全连接队列accept_queue半连接队列的作用暂存未完成三次握手的连接收到SYN但未收到ACK全连接队列的作用暂存已完成握手但未被accept()取走的连接number的意义version1:表示半连接队列的长度。1980年使用偏多侧重安全防御避免syn泛洪version2表示全连接队列加半连接队列的长度。即未分配fd的TCB数量version3:表示全连接队列的长度。现代使用偏多因为可以扩大服务器连接性能syn泛红可以交给防火墙等监测设备。连接相关函数4.connect() //服务端不用connect(fd,addr,len(addr)) //这个addr是bind后的只能客户端调用调用后开始与服务端三次握手。服务端没有明确的开始三次握手的函数但是是由listen监听到第一次握手后将该连接加入到协议栈分配的 半连接队列SYN queue。且由accept在第三次握手后从全连接队列accept queue取走连接。而数据包半连接队列到全连接队列的过程是从第三次握手的数据包中提炼出 “五元”从而去半连接队列找到匹配的结点move到全连接队列中。5.accept() //客户端不用clientfd accept(fd,addr,len(addr)) //这个addr是空的三次握手完毕后accept为服务端和客户端创建一个独立的通信通道。而返回的clientfd也只是在服务端用来标识连接的客户端的id客户端有自己建立连接前创建的sockfd。accept任务分配一个clientfd将这个fd与对应的TCB做一个映射。传输数据6.send() 和 recv()上面提到的三次握手建立连接是在两台机器的内核协议栈中进行的对应用层代码来说是透明的只能通过监测协议栈中是否有数据到了来调用send或者recv函数处理。send()的具体工作就是不论客户端还是服务器将用户区的数据copy到内核协议栈的wmem中具体怎么发送到对方的内核协议栈的rmem由协议栈自动操纵。中间就是计算机网络的知识累计确认失序接收快速重传流量控制拥塞控制如果用户方发送了fin申请断开连接会在recv的buffer中写入EOF字节读到这个字节会返回0。断开连接7.close()close(fd)调用close后会释放文件描述符fd及其关联的内核资源如 Socket、TCB、缓冲区等会触发TCP的四次挥手但也是在两台主句的内核协议栈间交互代码层是透明的。正常四次挥手特殊情况1在fin_wait1状态下先收到了fin再发出ack那会进入一种新状态closing再收到ack进入time_wait特殊情况2两边同时close()小作业双机通信两个人都是服务器和客户端。代码展示另外一台的虚拟机修改ip即可#include stdio.h #include string.h #include stdlib.h #include sys/types.h #include sys/socket.h #include sys/epoll.h #include errno.h #include netinet/tcp.h #include arpa/inet.h #include netdb.h #include fcntl.h #include unistd.h #include pthread.h #define PORT 8888 int epfd; void *input_thread(void *arg) { int fd *(int *) arg; char buffer[1024]; int cnt; while (1) { memset(buffer, 0, 1024); if (fgets(buffer, sizeof(buffer), stdin) ! NULL) { // 去掉换行符 buffer[strcspn(buffer, \n)] \0; if (fd 0) { cnt send(fd, buffer, strlen(buffer), 0); // 利用client的conncetion发送 printf(Sent: %d bytes\n, cnt); } } } } int Add_fd_to_event(int fd, int event_type) { struct epoll_event ev; ev.data.fd fd; ev.events event_type; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, ev); } int initclient(int port) { int sockfd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr; memset(addr, 0, sizeof(struct sockaddr_in)); addr.sin_family AF_INET; addr.sin_addr.s_addr inet_addr(192.168.88.20); addr.sin_port htons(port); if (connect(sockfd, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) 0) { perror(connect); } return sockfd; } int initserver(int port) { int sockfd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serveraddr; serveraddr.sin_family AF_INET; serveraddr.sin_addr.s_addr htonl(INADDR_ANY); //0.0.0.0 serveraddr.sin_port htons(port); //1023 if (bind(sockfd, (struct sockaddr *)serveraddr, sizeof(struct sockaddr)) -1) { perror(bind); } listen(sockfd, 10); return sockfd; } void new_connection_come(int fd) { struct sockaddr_in clientaddr; socklen_t len sizeof(clientaddr); int clientfd accept(fd, (struct sockaddr *)clientaddr, len); if (clientfd 0) { printf(new connection disappear\n); return; } printf(accept clientfd : %d\n, clientfd); Add_fd_to_event(clientfd, EPOLLIN); } void show_what_I_received(int fd) { char buffer[1024]; memset(buffer, 0, sizeof(buffer)); int cnt recv(fd, buffer, 1024, 0); if (cnt 0) { printf(recv clientfd: %d disconnect\n, fd); close(fd); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); return; } else if (cnt 0) { close(fd); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); return; } printf(I have received : %s\n, buffer); } void send_message(int fd) { int cnt; char buffer[1024]; scanf(%s, buffer); cnt send(fd, buffer, strlen(buffer), 0); printf(I have sent %d bytes\n, cnt); } int main() { int sockfd2 initserver(PORT); sleep(5); int sockfd1 initclient(PORT); epfd epoll_create(1); Add_fd_to_event(sockfd1, EPOLLOUT); Add_fd_to_event(sockfd2, EPOLLIN); pthread_t tid; pthread_create(tid, NULL, input_thread, sockfd1); struct epoll_event evs[1024]; while (1) { memset(evs, 0, 1024); int nready epoll_wait(epfd, evs, 1024, -1); int i 0; for (i 0; i nready; i) { int fd evs[i].data.fd; if (evs[i].events EPOLLIN) { if (fd sockfd2) { new_connection_come(fd); } else { show_what_I_received(fd); } } } } }问题处理运行结果A主机B主机知识点参考https://github.com/0voice