0. TCP 编程函数概览
函数名 | 用法 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
socket |
int socket(int domain, int type, int protocol); |
domain : 协议族type : 套接字类型protocol : 协议类型 |
成功时返回套接字描述符,失败时返回 -1 | 创建一个套接字 |
bind |
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
sockfd : 套接字描述符addr : 指向地址结构的指针addrlen : 地址结构的长度 |
成功时返回 0,失败时返回 -1 | 将套接字与特定地址和端口绑定 |
listen |
int listen(int sockfd, int backlog); |
sockfd : 套接字描述符backlog : 等待连接队列的最大长度 |
成功时返回 0,失败时返回 -1 | 将套接字设置为监听状态 |
accept |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
sockfd : 套接字描述符addr : 指向用于存放客户端地址的缓冲区addrlen : 地址结构的长度 |
成功时返回新的套接字描述符,失败时返回 -1 | 接受客户端的连接请求 |
connect |
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
sockfd : 套接字描述符addr : 指向服务器地址的指针addrlen : 地址结构的长度 |
成功时返回 0,失败时返回 -1 | 发起与服务器的连接请求 |
send |
ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
sockfd : 套接字描述符buf : 指向要发送数据的缓冲区len : 要发送的数据长度flags : 发送标志 |
成功时返回发送的字节数,失败时返回 -1 | 发送数据 |
recv |
ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
sockfd : 套接字描述符buf : 用于存放接收数据的缓冲区len : 缓冲区的长度flags : 接收标志 |
成功时返回接收的字节数,失败时返回 -1 | 接收数据 |
close |
int close(int sockfd); |
sockfd : 套接字描述符 |
成功时返回 0,失败时返回 -1 | 关闭套接字 |
0.1. 错误处理相关函数
函数名 | 用法 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
perror |
void perror(const char *s); |
s : 用于输出错误信息的字符串 |
无返回值 | 输出描述性错误消息 |
errno |
int errno; |
无参数 | 整数值 | 全局变量,存储发生错误的代码 |
strerror |
char *strerror(int errnum); |
errnum : 错误码 |
返回错误描述字符串 | 根据错误码返回描述性字符串 |
I. 建立TCP连接
A. TCP简介
-
TCP传输控制协议概述
- TCP是一种面向连接的、可靠的传输层协议,用于在网络中提供可靠的数据传输服务。
- 它通过各种机制(如序列号、确认应答、重传)确保数据的完整性和可靠性。
-
可靠、面向连接通信的重要性
- 可靠性:TCP通过序列号、确认和重传等机制,保证数据在传输过程中不丢失、不重复,并按顺序交付。
- 面向连接:建立连接的过程中,通信双方会进行协商,确保彼此能够正常通信,并在通信结束后释放连接,释放资源。
B. 三次握手
-
三次握手的解释:SYN、SYN-ACK、ACK
握手阶段 发送方 接收方 描述 1. 发送SYN Client Server 发起连接请求,同步序列号(SYN) 2. 接收SYN Server Client 收到请求,回复同步确认和自己的SYN(SYN-ACK) 3. 发送ACK Client Server 确认接收方的SYN,完成握手(ACK) -
序列号在建立可靠连接中的重要性
- 每个TCP报文段都包含一个序列号,用于标识发送的数据在整个数据流中的位置。序列号的使用使得接收方能够正确地组装数据流,并实现可靠的数据传输。
-
通过示例代码演示三次握手过程
- 下面是一个简单的C语言示例代码,演示了TCP三次握手的过程:
// Client send_syn(); // 发送SYN recv_syn_ack(); // 接收SYN-ACK send_ack(); // 发送ACK // Server recv_syn(); // 接收SYN send_syn_ack(); // 发送SYN-ACK recv_ack(); // 接收ACK
II. 数据传输
A. TCP数据传输方式
-
数据流的概念
- TCP提供的是一个面向字节流的数据传输方式,数据被看作是无边界的字节流,而不是分段的消息。
- 这意味着发送方的数据可以被接收方按照任意大小的块进行接收。
-
使用
send()
和recv()
等相关函数进行数据传输-
send(socket, buffer, size, flags)
:将数据从缓冲区发送到连接的套接字。 -
recv(socket, buffer, size, flags)
:从连接的套接字接收数据并存储到缓冲区。
-
-
处理粘包和拆包问题
- 粘包(Packet Pawning):多个发送的小数据包被接收方当作一个大数据包处理。
- 拆包(Packet Splitting):一个发送的大数据包被接收方分割成多个小数据包处理。
- 解决方案包括消息边界标记、固定长度消息和长度字段等方法。
III. 多路复用
A. 多路复用机制介绍
-
select()
、poll()
和epoll()
等多路复用机制概述- 多路复用允许单个进程处理多个连接,提高系统的并发性能。
-
select()
:基于轮询的机制,效率较低。 -
poll()
:改进的select()
,支持更多的文件描述符,但仍有限。 -
epoll()
:Linux特有的高性能多路复用机制,支持水平触发和边缘触发。
-
多路复用的优势和适用场景
- 通过单一的系统调用来监视多个文件描述符,提高效率。
- 适用于大量的并发连接,如服务器需要同时处理多个客户端请求。
-
通过代码示例演示多路复用的基本用法
- 示例代码(使用
epoll()
):
// 创建epoll实例 int epoll_fd = epoll_create1(0); // 将套接字添加到epoll监视列表 struct epoll_event event; event.events = EPOLLIN; event.data.fd = server_socket; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event); while (1) { struct epoll_event events[MAX_EVENTS]; int ready_fds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < ready_fds; ++i) { if (events[i].data.fd == server_socket) { // 处理新的客户端连接 // ... } else { // 处理已连接客户端的数据 // ... } } }
- 示例代码(使用
B. select()函数详解
-
select()
函数的使用方法和参数-
select(max_fd + 1, &read_fds, &write_fds, &except_fds, &timeout)
:监视文件描述符的可读、可写和异常情况。
-
-
阻塞和非阻塞模式的区别
- 阻塞模式:调用
select()
后,会一直等待直到有文件描述符就绪。 - 非阻塞模式:通过设置非阻塞标志,
select()
调用会立即返回,无论文件描述符是否就绪。
- 阻塞模式:调用
-
错误处理和超时设置
- 返回值小于0表示发生错误,可以通过
errno
获取错误信息。 - 超时设置通过
timeout
参数实现,允许程序定时检查文件描述符的状态。
- 返回值小于0表示发生错误,可以通过
C. poll()函数详解
-
poll()
函数的使用方法和参数-
poll(struct pollfd fds[], nfds, timeout)
:监视一组文件描述符的可读、可写和异常情况。
-
-
与
select()
的比较优势-
poll()
没有文件描述符数的限制,更适用于大规模的并发连接。
-
-
处理大量文件描述符的效率
- 采用数组结构,通过
revents
字段判断文件描述符的状态,效率相对较高。
- 采用数组结构,通过
D. epoll()函数详解
-
epoll()
函数的使用方法和参数-
epoll_create1(flags)
:创建一个epoll实例,epoll_ctl()
用于添加、修改和删除文件描述符,epoll_wait()
等待文件描述符就绪。
-
-
边缘触发和水平触发模式的区别
- 边缘触发:只在状态变化时通知一次,需要使用非阻塞模式处理文件描述符。
- 水平触发:在状态保持的情况下会持续通知。
-
高效处理大量并发连接的方法
- 通过事件驱动的方式,只关注活跃的文件描述符,避免遍历整个文件描述符集合。
- 使用
EPOLLET
标志开启边缘触发模式,提高效率。
IV. 实际应用案例
A. 开发一个简单的TCP服务器和客户端
1. TCP服务器
-
服务器端伪代码
// 创建服务器套接字 int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 绑定服务器地址 struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = INADDR_ANY; server_address.sin_port = htons(PORT); bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)); // 监听连接请求 listen(server_socket, BACKLOG); // 接受客户端连接 int client_socket = accept(server_socket, NULL, NULL); // 接收和处理客户端数据 char buffer[BUFFER_SIZE]; recv(client_socket, buffer, BUFFER_SIZE, 0); // 发送数据给客户端 send(client_socket, "Hello, client!", strlen("Hello, client!"), 0); // 关闭连接 close(client_socket); close(server_socket);
-
服务器端流程图
2. TCP客户端
-
客户端伪代码
// 创建客户端套接字 int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 设置服务器地址 struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr(SERVER_IP); server_address.sin_port = htons(PORT); // 连接到服务器 connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)); // 发送数据给服务器 send(client_socket, "Hello, server!", strlen("Hello, server!"), 0); // 接收服务器响应 char buffer[BUFFER_SIZE]; recv(client_socket, buffer, BUFFER_SIZE, 0); // 处理服务器响应 printf("Server response: %s\n", buffer); // 关闭连接 close(client_socket);
-
客户端流程图
3. 处理连接、数据传输和关闭连接的过程
-
连接过程
- 服务器通过
socket()
、bind()
和listen()
创建并准备监听套接字。 - 客户端通过
socket()
和connect()
创建并连接到服务器。
- 服务器通过
-
建立连接流程图
-
数据传输过程
- 服务器通过
accept()
接受客户端连接,并使用send()
和recv()
进行数据传输。 - 客户端通过
send()
和recv()
与服务器进行数据交互。
- 服务器通过
-
关闭连接过程文章来源:https://www.toymoban.com/news/detail-772332.html
- 服务器和客户端均通过
close()
关闭套接字,释放相关资源。
- 服务器和客户端均通过
这个案例演示了一个简单的TCP服务器和客户端的实现过程,使用了伪代码、流程图和文字描述,以便更好地理解连接、数据传输和关闭连接的整个过程。文章来源地址https://www.toymoban.com/news/detail-772332.html
到了这里,关于TCP 编程探秘:建立连接、数据传输与多路复用的精髓的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!