在 epoll
模型下,实现一个能够同时处理 recv
和 send
的 C 语言客户端示例,可以使用多线程或非阻塞 I/O。下面是一个简单的非阻塞 I/O 示例,它展示了如何使用 epoll
来处理 socket 的读写事件。
客户端代码示例
这个示例创建了一个 TCP 客户端,连接到服务器,并能够同时接收和发送数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
// 设置socket为非阻塞模式
int set_nonblocking(int sock) {
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int sock;
struct sockaddr_in server_addr;
// 创建 socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置为非阻塞模式
if (set_nonblocking(sock) == -1) {
perror("set_nonblocking");
close(sock);
exit(EXIT_FAILURE);
}
// 设置服务器地址结构体
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
// 将 IP 地址转换为二进制形式
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("Invalid address / Address not supported");
close(sock);
exit(EXIT_FAILURE);
}
// 尝试连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
if (errno != EINPROGRESS) {
perror("Connection failed");
close(sock);
exit(EXIT_FAILURE);
}
printf("Connecting to the server...\n");
// 使用 epoll 等待连接完成
struct epoll_event event;
struct epoll_event events[MAX_EVENTS];
int epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create1");
close(sock);
exit(EXIT_FAILURE);
}
event.events = EPOLLOUT; // 我们关注可写事件
event.data.fd = sock;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &event) == -1) {
perror("epoll_ctl: listen_sock");
close(epfd);
close(sock);
exit(EXIT_FAILURE);
}
while (1) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1); // 等待事件发生
for (int i = 0; i < n; i++) {
if (events[i].data.fd == sock && events[i].events & EPOLLOUT) {
printf("Connected to the server\n");
// 一旦连接成功,可以移除EPOLLOUT并添加EPOLLIN事件来监听输入数据
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &event);
break;
}
}
break; // 成功连接后退出循环
}
close(epfd);
}
char buffer[BUFFER_SIZE];
while(1){
printf("Enter message: ");
fgets(buffer, BUFFER_SIZE-2 , stdin);
size_t len = strlen(buffer);
buffer[len] = '\n';
buffer[len + 1] = '\0';
send(sock , buffer , strlen(buffer), MSG_NOSIGNAL);
event.events = EPOLLIN | EPOLLET;
event.data.fd = sock;
int epfd2 = epoll_create1(0);
if(epfd2==-1){
perror ("epoll_create");
break ;
}
epoll_ctl(epfd2 ,EPOLL_CTL_ADD,sock,&event);
while(1){
int n=epoll_wait(epfd2 ,events ,MAX_EVENTS,-1 );
for(int i=0;i<n;i++){
if(events[i].data.fd==sock && events[i].events&EPOLLIN){
ssize_t recv_bytes=recv(sock ,buffer,sizeof(buffer)-1 ,MSG_NOSIGNAL );
if(recv_bytes<=0){
perror ("recv failed or connection closed");
break ;
}
buffer[recv_bytes]='\0';
printf ("Server response: %s\n",buffer);
}
}
}
}
close(socket);
return EXIT_SUCCESS;
}
使用说明:
编译程序
确保你的系统安装了 GCC 编译器,然后使用以下命令编译客户端代码:
gcc client.c -o client
运行服务器
首先,确保你已经运行了相应的服务端程序(例如前面提供的基于 epoll
的服务端)。
启动客户端
在另一个终端窗口中,运行客户端程序:
./client
用户交互
在客户端中,你可以输入消息并按回车键发送。之后,程序会等待来自服务器的响应并显示。
注意事项:
- 确保你正确设置了 IP 地址和端口,以匹配你的服务器。
- 此示例使用了边缘触发(Edge Triggered)模式,你可能需要根据具体需求调整。
- 本代码主要用于学习目的,请根据生产环境需求进行适当修改和错误处理。
内容由零声教学AI助手提供,问题来源于学员提问