Windows下Socket TCP网络传输及ping-pong缓冲区进程同步研究实现

该博客探讨了一个Windows平台上的TCP网络传输项目,通过引入ping-pong缓冲区策略优化接收端的延时问题。文章详细介绍了如何利用两个缓冲区在写入和读取过程中切换,确保数据流畅传输,并通过多线程实现接收、写入和读取的同步。此外,还分享了基于winsock的接收线程实现和主函数的代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近做一个socket网络传输的项目,在使用TCP进行传输的时候,接收端有时候会因为写入到文件的时间而造成一定的延时,所以在接收端使用了ping-pong buffer的策略进行优化。

所谓ping-pong buffer,也就是定义两个buffer,当有数据进来的时候,负责写入buffer的进程就寻找第一个没有被占用而且可写的buffer,进行写入,写好之后,将占用flag释放,同时设置一个flag提示此buffer已经可读,然后再接下去找另外一个可写的buffer,写入新的数据。

而读入的进程也是一直对buffer状态进行检测,一旦发现没有被占用,而且已经可以被读,就把这个buffer的数据取出来,然后标志为可写。状态的转换关系如下:

status1:可写 full = false; occp = false; 

这时候开始写入,同时改变状态:

status2: 正在写 full = false; occp = true;

写好之后buffer被释放:

status3: 写完了 full = true; occp = false;

而这个状态正是读入线程的开始状态,一旦开始读入,就改变状态

status4: 正在读 full = true; occp = true;

读完了之后:

status1 : full = false; occp = false;

又回到了可以写入的状态。

对于buffer的实现,选择循环链表的形式,一直进行循环检测:

typedef struct listBuf
{
	char * buff;
	int counter;
	bool full;
	bool occp;
	struct listBuf* next;
}lBuf;

提供了创建循环链表和插入新的元素的函数:

void createBuf(lBuf ** rootP)
{
	lBuf * tmp = *rootP;
	tmp->counter = 0;
	tmp->buff = new char[BLOCK_NUM*BUFFERSIZE];
	tmp->next = tmp;
	tmp->full = false;
	tmp->occp = false;
}
void insertBuf(lBuf ** rootP)
{
	lBuf * tmp = new lBuf();
	tmp->counter = 0;
	tmp->buff = new char[BLOCK_NUM*BUFFERSIZE];
	tmp->full = false;
	tm
<think>好的,我现在需要帮助用户编写一个C语言程序来实现ping pong”接收数据。首先,我得弄清楚用户的需求到底是什么。Ping pong在计算机网络中通常指的是两个节点之间互相发送数据包的过程,类似于乒乓球比赛中的来回击球。所以,用户可能想要的是一个程序,能够接收来自另一个节点的数据包,然后可能发送一个响应回去,形成持续的通信循环。 接下来,我需要考虑如何用C语言实现这个功能。在Linux环境下,常用的网络编程接口是套接字(sockets)。所以,程序应该使用socket相关的系统调用。这里可能涉及到的步骤包括创建套接字、绑定端口、监听连接、接受连接、接收数据、发送响应,以及关闭连接等。 首先,创建套接字。使用socket()函数,参数应该是AF_INET(IPv4)和SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。用户提到的“ping pong”通常可能基于TCP,因为需要可靠的连接,但也不排除UDP的可能,比如模拟ICMP的ping,但ICMP通常需要root权限。这里可能需要进一步确认,但用户没有说明,可能两种情况都要考虑。不过根据常见情况,可能先以TCP为例。 然后,绑定端口,使用bind()函数,需要指定地址和端口号。接着,如果是TCP,需要listen()和accept()来接受连接。而UDP则不需要建立连接,直接接收数据报。 接下来是接收数据,使用recv()或recvfrom()函数,取决于TCP还是UDP。接收数据后,程序可能需要发送回应,比如“pong”,然后循环继续接收。 可能的错误处理包括检查每个系统调用的返回值,处理错误情况,比如绑定失败、接收失败等。 现在,我需要考虑用户是否需要的是一个简单的例子,可能更倾向于TCP,因为这样步骤更明确。所以,先写一个TCP版本的ping pong服务器。然后,如果需要的话,再补充UDP的部分。 程序的结构大致如下: 1. 创建TCP套接字。 2. 设置地址重用选项,防止端口占用。 3. 绑定到指定端口。 4. 开始监听连接。 5. 接受客户端连接。 6. 进入循环,接收数据,打印出来,然后发送pong响应。 7. 当接收到结束信号或错误时关闭连接和套接字。 但用户提到的是“接收数据”,可能只需要接收部分,但ping pong通常需要回应。所以,程序应该接收数据,然后发送响应。例如,当收到"ping"时,回复"pong"。 同时,需要注意缓冲区的大小,正确处理接收到的数据,确保字符串以空字符结尾,避免溢出。 可能的错误点包括:忘记将接收的数据缓冲区末尾置零,导致打印时出现问题;没有处理部分接收的情况;没有处理客户端断开连接的情况。 另外,可能需要考虑多客户端的情况,但用户可能只需要一个简单的单线程版本,一次处理一个连接。 现在,我需要将这些步骤转化为C代码。例如: - 使用socket()创建套接字。 - 设置SO_REUSEADDR选项。 - 填充struct sockaddr_in结构体,绑定到所有接口的某个端口,比如8080。 - 调用listen()。 - 在循环中accept()客户端连接。 - 在accept后的循环中,使用recv()读取数据,打印,然后发送响应。 - 关闭连接。 可能的示例代码结构: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[1024]; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket创建失败"); exit(EXIT_FAILURE); } // 设置端口重用 int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080); if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("绑定失败"); close(server_fd); exit(EXIT_FAILURE); } // 监听 if (listen(server_fd, 5) < 0) { perror("监听失败"); close(server_fd); exit(EXIT_FAILURE); } printf("服务器启动,等待连接...\n"); // 接受连接 client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("接受连接失败"); close(server_fd); exit(EXIT_FAILURE); } printf("客户端已连接\n"); while (1) { ssize_t bytes_received = recv(client_fd, buffer, sizeof(buffer) - 1, 0); if (bytes_received <= 0) { break; // 连接关闭或出错 } buffer[bytes_received] = '\0'; // 确保字符串终止 printf("接收: %s", buffer); // 发送pong const char *response = "pong\n"; send(client_fd, response, strlen(response), 0); } close(client_fd); close(server_fd); return 0; } 不过,这个例子是TCP的,且只能处理一次连接,接收到数据后发送pong。用户可能需要更详细的解释,比如每个步骤的作用,以及如何测试。 另外,如果用户希望的是UDP版本,步骤会有所不同。UDP不需要listen或accept,直接使用recvfrom和sendto。例如: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main() { int sockfd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[1024]; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket创建失败"); exit(EXIT_FAILURE); } server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080); if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("绑定失败"); close(sockfd); exit(EXIT_FAILURE); } printf("UDP服务器启动,等待数据...\n"); while (1) { ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&client_addr, &client_len); if (bytes_received <= 0) { perror("接收失败"); continue; } buffer[bytes_received] = '\0'; printf("接收: %s", buffer); const char *response = "pong\n"; sendto(sockfd, response, strlen(response), 0, (struct sockaddr*)&client_addr, client_len); } close(sockfd); return 0; } 这样,用户可以根据需要选择TCP或UDP版本。需要解释两者的区别,比如TCP是面向连接的,可靠;UDP是无连接的,可能丢失数据包但更快。 同时,用户可能需要知道如何测试这个程序。例如,使用telnet或nc作为客户端。比如,在终端运行nc localhost 8080,然后输入ping,应该会收到pong。 另外,需要提醒编译命令,比如gcc -o pingpong pingpong.c,然后运行./pingpong。 可能的错误处理方面,比如检查send和recv的返回值,处理中断的情况,但为了示例简洁,可能暂时省略,但需要指出在实际应用中需要更完善的错误处理。 总结,用户需要一个能够接收数据并回应pong的C程序,可能是基于TCP或UDP的。需要分步骤解释代码,并给出示例和测试方法。</think>以下是一个基于TCP协议的C语言`ping pong`服务器实现,能够接收数据并自动回复`pong`: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[BUFFER_SIZE] = {0}; // 1. 创建TCP套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket创建失败"); exit(EXIT_FAILURE); } // 2. 设置套接字选项(允许地址重用) int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("设置套接字选项失败"); exit(EXIT_FAILURE); } // 3. 绑定地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) { perror("绑定失败"); exit(EXIT_FAILURE); } // 4. 开始监听连接 if (listen(server_fd, 3) < 0) { perror("监听失败"); exit(EXIT_FAILURE); } printf("服务器已启动,监听端口 %d...\n", PORT); // 5. 接受客户端连接 if ((client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) { perror("接受连接失败"); exit(EXIT_FAILURE); } printf("客户端已连接\n"); // 6. 数据交互循环 while (1) { // 接收数据 ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE); if (bytes_read <= 0) { printf("连接已断开\n"); break; } buffer[bytes_read] = '\0'; // 确保字符串终止 printf("收到: %s", buffer); // 发送响应 const char* response = "pong\n"; send(client_fd, response, strlen(response), 0); } // 7. 关闭连接 close(client_fd); close(server_fd); return 0; } ``` ### 实现原理分步解析 1. **创建套接字** 使用`socket()`系统调用创建TCP套接字,参数`AF_INET`表示IPv4协议,`SOCK_STREAM`表示面向连接的TCP协议。 2. **设置套接字选项** `SO_REUSEADDR`选项允许端口在关闭后立即重用,避免出现"Address already in use"错误。 3. **绑定端口** `bind()`将套接字与指定端口绑定,`INADDR_ANY`表示监听所有网络接口的请求。 4. **监听连接** `listen()`将套接字设置为被动监听模式,第二个参数`3`表示等待队列的最大长度。 5. **接受连接** `accept()`阻塞等待客户端连接,建立连接后会返回一个新的套接字描述符用于数据通信。 6. **数据交互** - `read()`接收客户端数据,最大接收`BUFFER_SIZE-1`个字节 - 收到数据后添加字符串终止符`\0` - 使用`send()`发送"pong"响应 7. **关闭连接** 通信完成后关闭客户端和服务器套接字 ### 测试方法 1. 编译程序: ```bash gcc pingpong.c -o pingpong ``` 2. 启动服务器: ```bash ./pingpong ``` 3. 使用`telnet`或`nc`测试(新终端): ```bash nc localhost 8080 ``` 4. 输入`ping`后应收到`pong`响应 ### 扩展说明 - **UDP版本实现** 将`socket()`第二个参数改为`SOCK_DGRAM`,使用`recvfrom()`和`sendto()`代替`read()/write()` - **多客户端支持** 使用`fork()`创建子进程或`pthread`创建线程处理多个连接 - **错误处理增强** 添加对`send()`返回值的检查,处理信号中断等情况 - **协议改进** 定义应用层协议格式(如添加数据长度前缀),增强通信可靠性 建议在Linux环境下测试,如需Windows版本需要改用Winsock API。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值