目录
1、POSIX(Portable Operating System Interface)
传输控制块(Transmission Control Block, TCB)
5)超时重传(Timeout Retransmission)
一、基本概念
1、POSIX(Portable Operating System Interface)
POSIX(Portable Operating System Interface)是一个定义了一系列API(应用程序编程接口)的标准,旨在提高不同操作系统之间的兼容性和可移植性。POSIX API涵盖了文件和目录操作、进程控制、线程管理、信号处理、内存管理、网络通信等多方面的功能。
客户端和服务器相关API
客户端
- 使用socket()函数创建一个套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
- 使用connect()函数连接到服务器
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
- 使用send()和recv()函数进行数据通信
send(sockfd, message, strlen(message), 0); recv(sockfd, buffer, sizeof(buffer), 0);
- 使用close()函数关闭套接字
close(sockfd);
服务端
- 使用socket()函数创建一个套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
- 使用bind()函数绑定套接字到特定的IP地址和端口号
struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); bind(server_fd, (struct sockaddr *)&address, sizeof(address));
- 使用listen()函数使套接字进入监听状态
listen(server_fd, 3);
- 使用accept()函数接受客户端连接
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
- 使用send()和recv()函数进行数据通信
recv(new_socket, buffer, sizeof(buffer), 0); send(new_socket, response, strlen(response), 0);
- 使用close()函数关闭套接字
close(new_socket); close(server_fd);
epoll相关函数
- epoll_create():创建一个 epoll 实例
int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); }
- epoll_ctl():控制 epoll 实例,注册、修改或删除感兴趣的事件
struct epoll_event event; event.events = EPOLLIN; // 监控读事件 event.data.fd = fd; // 需要监控的文件描述符 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) { perror("epoll_ctl"); exit(EXIT_FAILURE); }
- epoll_wait():等待事件的发生
struct epoll_event events[MAX_EVENTS]; int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (int i = 0; i < nfds; ++i) { if (events[i].events & EPOLLIN) { // 处理可读事件 } }
二、详细解释
1、连接过程
1)socket()
在网络编程中,socket 的实现确实涉及多个部分,其中主要包括文件描述符(File Descriptor, fd)和传输控制块(Transmission Control Block, TCB)。以下是对这两个部分及其相互作用的详细解释:
文件描述符(File Descriptor, fd)
- 文件描述符分配:
- 当你调用 socket() 函数创建一个新的套接字时,操作系统会分配一个文件描述符。文件描述符是一个整数,用于唯一标识该套接字。
- 文件描述符在内核中作为索引,用于查找与该套接字相关的数据结构和状态信息。
传输控制块(Transmission Control Block, TCB)
- 传输控制块的作用:
- TCB 是一个数据结构,用于存储与 TCP 连接相关的所有状态信息。对于每个 TCP 连接,操作系统内核会维护一个 TCB。
- TCB 包含的信息包括:
- 本地和远程 IP 地址
- 本地和远程端口号
- 连接状态(如 LISTEN、SYN_SENT、ESTABLISHED 等)
- 发送和接收缓冲区
- 序列号和确认号
- 拥塞控制和流量控制参数
2)bind()
bind() 函数通过文件描述符(fd)找到对应的套接字结构,然后将IP地址和端口号设置进去,从而在传输控制块(TCB)中反映这些信息。下面是详细的解释:
bind() 函数的工作原理
-
查找套接字结构:
- 当你调用 bind() 函数时,内核会根据传入的文件描述符(fd)查找对应的套接字结构(socket structure)。这个套接字结构包含了与该套接字相关的各种信息,包括指向 TCB 的指针。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 当你调用 bind() 函数时,内核会根据传入的文件描述符(fd)查找对应的套接字结构(socket structure)。这个套接字结构包含了与该套接字相关的各种信息,包括指向 TCB 的指针。
-
设置地址和端口:
- bind() 函数将传入的IP地址和端口号设置到套接字结构中。在TCP协议中,这些信息最终会存储到对应的传输控制块(TCB)中。
- 具体来说,bind() 函数将 sockaddr 结构体中的 sin_addr 和 sin_port 字段的值设置到套接字结构中。
struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; // IP address address.sin_port = htons(PORT); // Port number
-
更新 TCB:
- 当套接字结构中的 IP 地址和端口号被设置后,这些信息也会反映到相应的 TCB 中。TCB 包含连接状态、IP地址、端口号等关键信息,用于管理和维护 TCP 连接。
- 内核会确保在调用 bind() 函数时,套接字的地址和端口号信息得到正确更新,从而在后续的连接建立和数据传输过程中使用。
bind() 函数调用过程
- 应用程序调用 bind():
- 应用程序调用 bind() 函数,并传入文件描述符、IP地址和端口号。
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); bind(sockfd, (struct sockaddr *)&address, sizeof(address));
- 应用程序调用 bind() 函数,并传入文件描述符、IP地址和端口号。
- 内核处理 bind() 请求:
- 内核接收到 bind() 请求后,根据文件描述符查找对应的套接字结构。
- 内核将传入的 sockaddr 结构体中的IP地址和端口号设置到套接字结构中。
- 内核更新套接字结构中的信息,同时确保这些信息反映到相应的 TCB 中。
- 确认地址和端口设置成功:
- 内核完成地址和端口的设置后,返回成功状态给应用程序,表示 bind() 操作成功。
总结
- 文件描述符(fd):标识套接字。
- 套接字结构(socket structure):包含套接字相关的信息,包括指向 TCB 的指针。
- 传输控制块(TCB):存储连接状态、IP地址、端口号等关键信息。
bind() 函数通过文件描述符查找套接字结构,然后将IP地址和端口号设置进去,最终在TCB中反映这些信息。这确保了在TCP连接建立和数据传输过程中使用正确的地址和端口。
3)listen()
listen() 函数将传输控制块(TCB)中的状态设置为 LISTEN 状态。以下是 listen() 函数的详细工作原理及其在 TCP 连接管理中的作用:
listen() 函数的工作原理
- 函数原型:
int listen(int sockfd, int backlog);
- sockfd:由 socket() 创建并绑定了地址的套接字文件描述符。
- backlog:全连接队列的最大长度。
- 主要操作:
- 当你调用 listen() 函数时,内核会根据传入的文件描述符(fd)查找对应的套接字结构。
- 内核会将该套接字的状态设置为 LISTEN,并将其标记为一个被动套接字,表示它将接受传入的连接请求。
- 状态转换:
- 在 TCP 协议中,套接字的状态会从 CLOSED 转换为 LISTEN。这是 TCP 状态机中的一个重要状态,用于表示服务器套接字正在监听传入的连接请求。
listen() 函数的调用过程
- 应用程序调用 listen():
- 应用程序调用
listen()
函数,并传入套接字文件描述符和待处理连接请求的最大队列长度(backlog
)。
- 应用程序调用
- 内核处理 listen() 请求:
- 内核接收到 listen() 请求后,根据文件描述符查找对应的套接字结构。
- 内核将该套接字的状态设置为 LISTEN,并更新相应的 TCB。
- TCB 中的状态字段被更新为 LISTEN,表示该套接字正在等待传入的连接请求。
- 队列管理:
- 内核会为该监听套接字维护一个连接请求队列。backlog 参数指定了该队列的最大长度。
- 当新的连接请求到达时,内核会将这些请求放入队列中,等待应用程序调用 accept() 函数处理。
listen() 的作用
- 将套接字状态设置为 LISTEN:表示该套接字正在监听传入的连接请求。
- 创建连接请求队列:为传入的连接请求维护一个队列,等待应用程序处理。
listen() 第二参数backlog 的作用
- 定义全连接队列长度:
- backlog 参数指定了全连接队列(Accept Queue)的最大长度,即服务器在 accept() 之前可以存储的完全建立的连接请求的数量。