当前位置: 首页 > news >正文

Linux网络编程之TCP/IP实现高并发网络服务器设计指南

目录

引言:

多进程服务器

例程分享:

多线程服务器

 例程分享:

I/O多路复用服务器

select

例程分享:

poll

例程分享:

epoll

例程分享:

总结建议


引言:

        随着互联网的迅猛发展,服务器面临着越来越多的并发请求。如何设计一个能够高效处理大量并发请求的服务器成为了一个关键问题。本文将介绍几种常见的高并发服务器设计方案,包括多进程服务器、多线程服务器、I/O多路复用服务器和epoll服务器,并分析它们的优缺点,以便读者能够选择适合自己需求的设计方案。

多进程服务器

利用fork创建子进程处理每个连接请求。

优点:充分利用多核CPU的计算能力,隔离不同连接之间的资源。

 缺点:父进程需要设置较大的文件描述符限制,进程创建和切换开销较大。

相关API函数:fork、waitpid、socket、bind、listen、accept、read、write、close

实现要点:

  • 父进程close子进程socket,避免泄漏。
  • 信号处理回收子进程。
  • 每个子进程处理一个连接请求。

例程分享:

/*
服务器监听端口,接收客户端连接。
对每个连接fork子进程处理请求。
子进程循环接收客户端数据,转换大小写后返回。
父进程关闭连接socket,信号函数回收子进程。
客户端连接后循环发送接收数据。
使用多进程处理连接请求,充分利用多核CPU。
fork创建进程,waitpid和信号处理回收子进程。
父子进程同步处理,避免混乱。
*//* server.c */#include <stdio.h>   // 标准IO头文件#include <string.h>  // 字符串处理头文件#include <netinet/in.h> // socket编程头文件#include <arpa/inet.h> // IP地址转换头文件#include <signal.h> // 信号处理头文件#include <sys/wait.h> // 等待子进程头文件#include <sys/types.h> // 数据类型头文件#include "wrap.h" // socket函数封装头文件#define MAXLINE 80  // 最大读写字节数#define SERV_PORT 800 // 服务器端口号// 信号处理函数,回收子进程
void do_sigchild(int num) {while (waitpid(0, NULL, WNOHANG) > 0);
}int main(void) {// socket地址结构struct sockaddr_in servaddr, cliaddr;// 客户端地址长度socklen_t cliaddr_len;// 监听和连接socketint listenfd, connfd;// 数据缓冲区char buf[MAXLINE];// 客户端IP字符串char str[INET_ADDRSTRLEN];int i, n; // 循环变量和读字节数pid_t pid; // 进程ID// 安装信号处理函数struct sigaction newact;newact.sa_handler = do_sigchild;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGCHLD, &newact, NULL);// 创建监听socketlistenfd = Socket(AF_INET, SOCK_STREAM, 0);// 初始化服务器地址结构bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);// 绑定地址和端口Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));// 设置监听队列长度Listen(listenfd, 20);// 循环接收客户端连接请求while (1) {Copy codecliaddr_len = sizeof(cliaddr);// 接收一个客户端连接connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);// Fork一个子进程处理连接pid = fork(); if (pid == 0) {// 子进程关闭监听socketClose(listenfd);// 处理客户端请求while (1) {// 接收客户端数据 n = Read(connfd, buf, MAXLINE);// 判断客户端是否关闭if (n == 0) {printf("the other side has been closed.\n");break;}// 打印客户端信息printf("received from %s at PORT %d\n",  inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));// 转换为大写for (i = 0; i < n; i++)buf[i] = toupper(buf[i]);// 发送转换后数据Write(connfd, buf, n);}// 关闭连接 Close(connfd);return 0;} else if (pid > 0) {// 父进程关闭连接socketClose(connfd); } elseperr_exit("fork");
}// 关闭监听socket
Close(listenfd);return 0;
}/* client.c */#include <stdio.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>#include "wrap.h"#define MAXLINE 80
#define SERV_PORT 6666int main(int argc, char *argv[]) {// 服务器地址结构struct sockaddr_in servaddr;// 数据缓冲区char buf[MAXLINE];// socket和读字节数int sockfd, n;// 创建socketsockfd = Socket(AF_INET, SOCK_STREAM, 0);// 初始化服务器地址结构bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);// 连接服务器Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));// 循环发送接收数据while (fgets(buf, MAXLINE, stdin) != NULL) {Copy code// 发送数据到服务器Write(sockfd, buf, strlen(buf));  // 从服务器读取数据n = Read(sockfd, buf, MAXLINE);// 判断服务器是否关闭if (n == 0) {printf("the other side has been closed.\n");break;} else// 输出服务器返回数据Write(STDOUT_FILENO, buf, n);}// 关闭连接Close(sockfd);return 0;
}

多线程服务器

一个进程内创建线程处理每个连接请求。

 优点:高效利用多核CPU,创建和销毁线程开销较小。

 缺点:需要调整进程的文件描述符限制,需要进行线程同步,线程退出时需要进行资源清理。

 相关API函数:pthread_create、pthread_detach、pthread_join、pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock、socket、bind、listen、accept、read、write、close

要点:

  • 调整进程文件描述符限制
  • 共享数据同步
  • 线程退出处理,防止资源泄漏
  • 过多线程会降低性能

 例程分享:

/*
服务器端创建监听套接字,绑定地址并监听。
主循环调用accept接收客户端连接,为每个客户端创建线程do_work处理请求。
do_work通过read接收客户端数据,转换为大写后返回。
客户端创建连接后,循环发送数据和读取服务器返回数据。
服务器使用线程处理每个连接请求,可以处理大量连接。
通过传递参数,线程可以获取客户端信息。
设置线程分离态,自动回收资源,实现高效的多线程服务器。
*//* server.c */#include <stdio.h> // 标准输入输出头文件#include <string.h> // 字符串处理头文件#include <netinet/in.h> // socket编程头文件#include <arpa/inet.h> // inet地址转换头文件#include <pthread.h> // 线程编程头文件#include "wrap.h" // 封装的socket函数头文件#define MAXLINE 80 //最大读取字符数#define SERV_PORT 6666 //服务器端口// 用于传递给线程的客户信息
struct s_info {
struct sockaddr_in cliaddr; // 客户socket地址
int connfd; // 客户端连接套接字
};// 线程处理函数
void *do_work(void *arg) {int n,i;struct s_info *ts = (struct s_info*)arg; // 获取传递的参数char buf[MAXLINE]; // 数据缓冲区char str[INET_ADDRSTRLEN]; // 存储socket地址的字符串// 设置线程为分离态,线程结束时自动释放资源pthread_detach(pthread_self());while (1) {// 获取客户端发送的数据n = Read(ts->connfd, buf, MAXLINE);  if (n == 0) { // 对端关闭连接printf("the other side has been closed.\n");break;}// 打印客户端信息printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),ntohs((*ts).cliaddr.sin_port));// 将数据转换为大写for (i = 0; i < n; i++)buf[i] = toupper(buf[i]);// 发送转换后的数据Write(ts->connfd, buf, n);}// 关闭客户端连接Close(ts->connfd);
}int main(void) {struct sockaddr_in servaddr, cliaddr; // 本地和客户端的socket地址socklen_t cliaddr_len; // 客户端socket地址长度int listenfd, connfd; // 监听和连接套接字int i = 0; pthread_t tid; // 线程idstruct s_info ts[256]; // 存储所有客户端信息的数组// 创建监听套接字listenfd = Socket(AF_INET, SOCK_STREAM, 0);// 初始化本地socket地址结构bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);// 绑定监听套接字到本地地址Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));// 设置监听队列长度Listen(listenfd, 20);printf("Accepting connections ...\n");// 循环接收客户端连接while (1) {  // 接收一个客户端连接cliaddr_len = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);// 保存客户信息ts[i].cliaddr = cliaddr;  ts[i].connfd = connfd;// 为客户端创建线程处理请求pthread_create(&tid, NULL, do_work, (void*)&ts[i]);i++;}return 0;
}/* client.c */#include <stdio.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>#include "wrap.h"#define MAXLINE 80
#define SERV_PORT 6666int main(int argc, char *argv[])struct sockaddr_in servaddr; // 服务器端地址结构char buf[MAXLINE]; // 数据缓冲区int sockfd, n; // 套接字和读返回值// 创建流式套接字sockfd = Socket(AF_INET, SOCK_STREAM, 0);// 初始化服务器地址结构bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);// 连接服务器Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));// 循环发送数据和读取服务器返回数据while (fgets(buf, MAXLINE, stdin) != NULL) {   // 向服务器发送数据Write(sockfd, buf, strlen(buf));  // 从服务器读取数据n = Read(sockfd, buf, MAXLINE);// 判断服务器是否关闭if (n == 0)printf("the other side has been closed.\n");else// 输出服务器返回数据Write(STDOUT_FILENO, buf, n);}// 关闭socket连接Close(sockfd);return 0;
}

I/O多路复用服务器

select/poll/epoll使单线程可以同时处理多个连接请求。

select

优点: 可移植,使用简单。

缺点: 连接数受限,监听效率低。

要点:

  • select监听读写事件
  • 每次循环重置监听描述符
  • 根据返回就绪数遍历处理事件
  • 根据描述符状态处理连接关闭等
例程分享:
/*
服务器端初始化socket地址,创建监听套接字。
使用select()监听套接字可读事件。
调用accept()接收客户端连接请求。
添加新的连接到select监听的文件描述符集。
循环扫描就绪文件描述符,调用read()接收客户端数据。
对数据进行处理后,调用write()返回给客户端。
客户端创建连接后,循环read()和write()实现双向通信。
服务器使用select()处理多个连接,但依然是同步阻塞模型
*//* server.c *//* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h" // 这是一个自定义的头文件,封装了socket函数#define MAXLINE 80 // 缓冲区的最大长度
#define SERV_PORT 6666 // 服务器端口号int main(int argc, char *argv[])
{int i, maxi, maxfd, listenfd, connfd, sockfd;int nready, client[FD_SETSIZE]; // FD_SETSIZE通常为1024ssize_t n;fd_set rset, allset; // 用于select()的文件描述符集char buf[MAXLINE]; // 数据缓冲区char str[INET_ADDRSTRLEN]; // 地址字符串缓冲区socklen_t cliaddr_len;struct sockaddr_in cliaddr, servaddr; // 客户端和服务器地址结构listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 创建一个socketbzero(&servaddr, sizeof(servaddr)); // 清零服务器地址结构servaddr.sin_family = AF_INET; // 设置地址族为IPv4servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听任何接口servaddr.sin_port = htons(SERV_PORT); // 设置端口号Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 将socket绑定到地址Listen(listenfd, 20); // 监听连接,队列长度为20maxfd = listenfd; // 初始化maxfdmaxi = -1; // 初始化maxifor (i = 0; i < FD_SETSIZE; i++)client[i] = -1; // 初始化client[]FD_ZERO(&allset); // 清空allsetFD_SET(listenfd, &allset); // 将listenfd添加到allsetfor ( ; ; ) { // 主服务器循环rset = allset; // 每次循环都重置rsetnready = select(maxfd+1, &rset, NULL, NULL, NULL); // 调用select()if (nready < 0)perr_exit("select error"); // 如果select()返回错误,退出if (FD_ISSET(listenfd, &rset)) { // 如果有新的客户端正在连接cliaddr_len = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); // 接受新的连接printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port)); // 打印客户端的地址和端口for (i = 0; i < FD_SETSIZE; i++) {if (client[i] < 0) {client[i] = connfd; // 将接受的文件描述符保存在client[]中break;}}if (i == FD_SETSIZE) {fputs("too many clients\n", stderr); // 如果连接的客户端过多,打印错误并退出exit(1);}FD_SET(connfd, &allset); // 将新的文件描述符添加到allsetif (connfd > maxfd)maxfd = connfd; // 如果需要,更新maxfdif (i > maxi)maxi = i; // 如果需要,更新maxiif (--nready == 0)continue; // 如果没有更多的就绪文件描述符,继续下一次循环}for (i = 0; i <= maxi; i++) { // 检查哪些客户端有数据准备好读取if ( (sockfd = client[i]) < 0)continue;if (FD_ISSET(sockfd, &rset)) {if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {Close(sockfd); // 如果客户端已经关闭了连接,也关闭服务器端FD_CLR(sockfd, &allset); // 从allset中移除文件描述符client[i] = -1; // 从client[]中移除文件描述符} else {int j;for (j = 0; j < n; j++)buf[j] = toupper(buf[j]); // 将数据转换为大写Write(sockfd, buf, n); // 将数据写回客户端}if (--nready == 0)break; // 如果没有更多的就绪文件描述符,跳出循环}}}close(listenfd); // 关闭监听的socketreturn 0;
}/* client.c *//* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 这是一个自定义的头文件,封装了socket函数#define MAXLINE 80 // 缓冲区的最大长度
#define SERV_PORT 6666 // 服务器端口号int main(int argc, char *argv[])
{struct sockaddr_in servaddr; // 服务器地址结构char buf[MAXLINE]; // 数据缓冲区int sockfd, n; // Socket文件描述符和读取的字节数sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 创建一个socketbzero(&servaddr, sizeof(servaddr)); // 清零服务器地址结构servaddr.sin_family = AF_INET; // 设置地址族为IPv4inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 设置服务器的IP地址servaddr.sin_port = htons(SERV_PORT); // 设置端口号Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 连接到服务器while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客户端循环Write(sockfd, buf, strlen(buf)); // 将输入写入服务器n = Read(sockfd, buf, MAXLINE); // 读取服务器的响应if (n == 0)printf("the other side has been closed.\n"); // 如果服务器已经关闭了连接,打印一条消息elseWrite(STDOUT_FILENO, buf, n); // 将服务器的响应写入stdout}Close(sockfd); // 关闭socketreturn 0;
}

poll

优点: 没有连接数限制。

缺点: 依然轮询模型,效率低。

要点:

  • pollfd结构体监听事件
  • 每次循环遍历pollfd处理就绪事件
  • 根据返回事件和错误处理连接状态
例程分享:
/*
服务器端:
创建监听socket,绑定地址并监听
使用pollfd数组保存所有连接的文件描述符
调用poll监听socket上的事件,主要是POLLRDNORM读事件
当监听socket有事件时,表示有新连接,调用accept获取新连接
将新连接的文件描述符添加到pollfd数组中,继续监听读事件
当连接socket有读事件时,调用read读取客户端数据
对数据进行转换处理,调用write将数据返回给客户端
根据返回事件和错误情况判断连接是否正常
客户端:
创建socket,连接服务器地址
循环调用read读取用户输入
调用write将用户输入发送给服务器
调用read读取服务器返回数据
将服务器数据输出到标准输出
关闭连接
*//* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h" // 这是一个自定义的头文件,封装了socket函数#define MAXLINE 80 // 缓冲区的最大长度
#define SERV_PORT 6666 // 服务器端口号
#define OPEN_MAX 1024 // 最大的打开文件描述符数量int main(int argc, char *argv[])
{int i, j, maxi, listenfd, connfd, sockfd;int nready;ssize_t n;char buf[MAXLINE], str[INET_ADDRSTRLEN];socklen_t clilen;struct pollfd client[OPEN_MAX]; // pollfd结构体数组,用于存储多个文件描述符struct sockaddr_in cliaddr, servaddr; // 客户端和服务器地址结构listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 创建一个socketbzero(&servaddr, sizeof(servaddr)); // 清零服务器地址结构servaddr.sin_family = AF_INET; // 设置地址族为IPv4servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听任何接口servaddr.sin_port = htons(SERV_PORT); // 设置端口号Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 将socket绑定到地址Listen(listenfd, 20); // 监听连接,队列长度为20client[0].fd = listenfd;client[0].events = POLLRDNORM; // listenfd监听普通读事件for (i = 1; i < OPEN_MAX; i++)client[i].fd = -1; // 用-1初始化client[]里剩下元素maxi = 0; // client[]数组有效元素中最大元素下标for ( ; ; ) { // 主服务器循环nready = poll(client, maxi+1, -1); // 调用poll()函数,阻塞等待文件描述符就绪if (client[0].revents & POLLRDNORM) { // 有客户端链接请求clilen = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // 接受新的连接printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port)); // 打印客户端的地址和端口for (i = 1; i < OPEN_MAX; i++) {if (client[i].fd < 0) {client[i].fd = connfd; // 找到client[]中空闲的位置,存放accept返回的connfdbreak;}}if (i == OPEN_MAX)perr_exit("too many clients"); // 如果连接的客户端过多,打印错误并退出client[i].events = POLLRDNORM; // 设置刚刚返回的connfd,监控读事件if (i > maxi)maxi = i; // 更新client[]中最大元素下标if (--nready <= 0)continue; // 没有更多就绪事件时,继续回到poll阻塞}for (i = 1; i <= maxi; i++) { // 检测client[]if ((sockfd = client[i].fd) < 0)continue;if (client[i].revents & (POLLRDNORM | POLLERR)) { // 如果有数据可读或者有错误发生if ((n = Read(sockfd, buf, MAXLINE)) < 0) {if (errno == ECONNRESET) { // 当收到 RST标志时/* connection reset by client */printf("client[%d] aborted connection\n", i);Close(sockfd); // 关闭socketclient[i].fd = -1; // 从client[]中移除文件描述符} else {perr_exit("read error"); // 如果读取错误,退出}} else if (n == 0) {/* connection closed by client */printf("client[%d] closed connection\n", i);Close(sockfd); // 关闭socketclient[i].fd = -1; // 从client[]中移除文件描述符} else {for (j = 0; j < n; j++)buf[j] = toupper(buf[j]); // 将数据转换为大写Writen(sockfd, buf, n); // 将数据写回客户端}if (--nready <= 0)break; // 如果没有更多的就绪文件描述符,跳出循环}}}return 0;
}/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 这是一个自定义的头文件,封装了socket函数#define MAXLINE 80 // 缓冲区的最大长度
#define SERV_PORT 6666 // 服务器端口号int main(int argc, char *argv[])
{struct sockaddr_in servaddr; // 服务器地址结构char buf[MAXLINE]; // 数据缓冲区int sockfd, n; // Socket文件描述符和读取的字节数sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 创建一个socketbzero(&servaddr, sizeof(servaddr)); // 清零服务器地址结构servaddr.sin_family = AF_INET; // 设置地址族为IPv4inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 设置服务器的IP地址servaddr.sin_port = htons(SERV_PORT); // 设置端口号Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 连接到服务器while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客户端循环Write(sockfd, buf, strlen(buf)); // 将输入写入服务器n = Read(sockfd, buf, MAXLINE); // 读取服务器的响应if (n == 0)printf("the other side has been closed.\n"); // 如果服务器已经关闭了连接,打印一条消息elseWrite(STDOUT_FILENO, buf, n); // 将服务器的响应写入stdout}Close(sockfd); // 关闭socketreturn 0;
}

epoll

优点:提高程序在大量并发连接中的系统CPU利用率,能够高效处理大量并发请求。

缺点:需要调整进程的文件描述符限制,需要进行连接管理。

要点:

  • epoll_create创建句柄
  • epoll_ctl注册和控制事件
  • epoll_wait等待就绪事件
  • 根据就绪事件处理请求
例程分享:
/*
服务器端:
服务器端的代码主要完成以下任务:创建一个TCP socket并绑定到指定的IP地址和端口。
使用epoll创建一个事件监听列表,并将监听socket添加到这个列表中。
进入一个无限循环,使用epoll_wait()函数等待事件的发生。
当新的客户端连接时,接受连接并将新的socket添加到epoll的监听列表中。
当已连接的客户端发送数据时,读取数据,将数据转换为大写,然后将数据回写到客户端。
当客户端关闭连接时,从epoll的监听列表中移除这个socket,并关闭这个socket。
客户端端:
客户端的代码主要完成以下任务:创建一个TCP socket并连接到服务器。
进入一个无限循环,从stdin读取输入,将输入写入服务器,然后读取服务器的响应并将响应写入stdout。
当服务器关闭连接时,打印一条消息并退出循环。
*//* server.c */
// ...省略部分代码...// 创建一个TCP socket
listenfd = Socket(AF_INET, SOCK_STREAM, 0);// 清零服务器地址结构
bzero(&servaddr, sizeof(servaddr));
// 设置地址族为IPv4
servaddr.sin_family = AF_INET;
// 监听任何接口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// 设置端口号
servaddr.sin_port = htons(SERV_PORT);// 将socket绑定到地址
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));// 监听连接,队列长度为20
Listen(listenfd, 20);// 创建epoll实例
efd = epoll_create(OPEN_MAX);
if (efd == -1)perr_exit("epoll_create");// 设置监听事件为EPOLLIN(可读事件),并将listenfd添加到epoll的监听列表中
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if (res == -1)perr_exit("epoll_ctl");// 主服务器循环
while (1) {// 调用epoll_wait()函数,阻塞等待文件描述符就绪nready = epoll_wait(efd, ep, OPEN_MAX, -1);if (nready == -1)perr_exit("epoll_wait");// 遍历就绪的文件描述符for (i = 0; i < nready; i++) {// 如果不是可读事件,跳过if (!(ep[i].events & EPOLLIN))continue;// 如果是新的客户端连接if (ep[i].data.fd == listenfd) {// 接受新的连接clilen = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));// 将新的socket添加到epoll的监听列表中tep.events = EPOLLIN; tep.data.fd = connfd;res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);if (res == -1)perr_exit("epoll_ctl");} else {// 如果是已连接的客户端发送的数据sockfd = ep[i].data.fd;n = Read(sockfd, buf, MAXLINE);if (n == 0) {// 如果客户端关闭了连接,从epoll的监听列表中移除这个socket,并关闭这个socketres = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);if (res == -1)perr_exit("epoll_ctl");Close(sockfd);printf("client[%d] closed connection\n", j);} else {// 如果接收到数据,将数据转换为大写,然后将数据回写到客户端for (j = 0; j < n; j++)buf[j] = toupper(buf[j]);Writen(sockfd, buf, n);}}}
}
// 关闭监听socket和epoll实例
close(listenfd);
close(efd);
return 0;/* client.c */
// ...省略部分代码...// 创建一个TCP socket
sockfd = Socket(AF_INET, SOCK_STREAM, 0);// 清零服务器地址结构
bzero(&servaddr, sizeof(servaddr));
// 设置地址族为IPv4
servaddr.sin_family = AF_INET;
// 设置服务器的IP地址
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
// 设置端口号
servaddr.sin_port = htons(SERV_PORT);// 连接到服务器
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));// 主客户端循环
while (fgets(buf, MAXLINE, stdin) != NULL) {// 将输入写入服务器Write(sockfd, buf, strlen(buf));// 读取服务器的响应n = Read(sockfd, buf, MAXLINE);if (n == 0)// 如果服务器已经关闭了连接,打印一条消息printf("the other side has been closed.\n");else// 将服务器的响应写入stdoutWrite(STDOUT_FILENO, buf, n);
}// 关闭socket
Close(sockfd);
return 0;

总结建议

        epoll服务器根据不同的需求和场景,我们可以选择不同的高并发服务器设计方案。多进程服务器、多线程服务器、I/O多路复用服务器和epoll服务器都有各自的优缺点和适用场景。通过分享的例程和相关API函数的介绍,读者可以更好地理解和选择适合自己需求的设计方案,从而高效处理大量并发请求,满足互联网快速发展的需求。

相关文章:

Linux网络编程之TCP/IP实现高并发网络服务器设计指南

目录 引言&#xff1a; 多进程服务器 例程分享&#xff1a; 多线程服务器 例程分享&#xff1a; I/O多路复用服务器 select 例程分享&#xff1a; poll 例程分享&#xff1a; epoll 例程分享&#xff1a; 总结建议 引言&#xff1a; 随着互联网的迅猛发展&#xff…...

【SpringBoot实战】基于阿里云实现文件上传

【SpringBoot实战】基于阿里云实现文件上传 在实际项目开发中&#xff0c;不可避免地会使用到阿里云OSS进行文件存储。尽管阿里云有详细的开发文档&#xff0c;但本篇博客的目的是让我们能够用简明的代码快速实现这个功能。 引入依赖 <dependencies><!-- 阿里云oss…...

大数据技术学习笔记(十一)—— Flume

目录 1 Flume 概述1.1 Flume 定义1.2 Flume 基础架构 2 Flume 安装3 Flume 入门案例3.1 监控端口数据3.2 实时监控单个追加文件3.3 实时监控目录下多个新文件3.4 实时监控目录下的多个追加文件 4 Flume 进阶4.1 Flume 事务4.2 Flume Agent 内部原理4.3 Flume 拓扑结构4.3.1 简单…...

电路设计时,继电器线圈、风扇电机绕组等感性负载必须有续流二极管。

续流二极管(也常被称为“自由轮流二极管”或“反向并联二极管”)在感性负载电路中的应用非常重要,尤其是在继电器线圈、风扇电机绕组等设备中。感性负载是指那些在其线圈中会产生感应电动势的负载,例如电动机、变压器和继电器等。当这些设备的电源被切断时,它们的线圈会因…...

Mongodb基础介绍与应用场景

NoSql 解决方案第二种 Mongodb MongoDB 是一款开源 高性能 无模式的文档型数据库 当然 它是NoSql数据库中的一种 是最像关系型数据库的 非关系型数据库 首先 最需要注意的是 无模式的文档型数据库 这个需要后面我们看到它的数据才能明白 其次是 最像关系型数据库的非关系型数据…...

mysql参数配置binlog

官网地址&#xff1a; MySQL :: MySQL Replication :: 2.6.4 Binary Logging Options and Variables 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. MySQL 复制 / ... / 二进制日志记录选项和变量 2.6.4 二进…...

pytorch常用的几个函数详解

文章目录 view基本用法自动计算维度保持原始数据不变 t函数功能语法返回值示例注意事项 permute() 函数基本概念permute() 函数的使用 unsqueeze() 函数基本概念unsqueeze() 函数的使用 squeeze() 函数基本概念squeeze() 函数的使用 transpose() 函数基本概念transpose() 函数的…...

Linux下安装Flume

1 下载Flume Welcome to Apache Flume — Apache Flume 下载1.9.0版本 2 上传服务器并解压安装 3 删除lib目录下的guava-11.0.2.jar &#xff08;如同服务器安装了hadoop&#xff0c;则删除&#xff0c;如没有安装hadoop则保留这个文件&#xff0c;否则无法启动flume&#…...

20231225使用BLE-AnalyzerPro WCH升级版BLE-PRO蓝牙分析仪抓取BLE广播数据

20231225使用BLE-AnalyzerPro WCH升级版BLE-PRO蓝牙分析仪抓取BLE广播数据 2023/12/25 20:05 结论&#xff1a;硬件蓝牙分析仪 不一定比 手机端的APK的效果好&#xff01; 亿佰特E104-2G4U04A需要3片【单通道】&#xff0c;电脑端的UI为全英文的。 BLE-AnalyzerPro WCH升级版B…...

.net6使用Sejil可视化日志

&#xff08;关注博主后&#xff0c;在“粉丝专栏”&#xff0c;可免费阅读此文&#xff09; 之前介绍了这篇.net 5使用LogDashboard_.net 5logdashboard rootpath-CSDN博客 这篇文章将会更加的简单&#xff0c;最终的效果都是可视化日志。 在程序非常庞大的时候&…...

mysql(51) : 大数据导出为insert

代码 import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects;public class 大数据导出为insert {public …...

MFC查找错误的方法

在visual studio2005上Debug总是会出现各种问题&#xff0c;比如指针错误&#xff0c;乱码等&#xff0c;无法正确查看变量的值&#xff0c;这时候可以使用AfxMessageBox()方法对数据进行弹窗输出&#xff0c;但AfxMessageBox()函数只支持CString数据输出&#xff0c;我们就需要…...

Jave EE 网络原理之网络层与数据链路层

文章目录 1. 网络层1.1 IP 协议1.1.1 协议头格式1.1.2 地址管理1.1.2.1 认识 IP 地址 1.1.3 路由选择 2. 数据链路层2.1 认识以太网2.1.1 以太网帧格式2.1.2 DNS 应用层协议 1. 网络层 网络层要做的事情&#xff0c;主要是两个方面 地址管理 &#xff08;制定一系列的规则&am…...

ElasticSearch 使用映射定义索引结构

动态映射 dynamic 可选值解释true默认值&#xff0c;启用动态映射&#xff0c;新增的字段会添加到映射中runtime查询时动态添加到映射中false禁用动态映射&#xff0c;忽略未知字段strict发现未知字段&#xff0c;抛出异常 显示映射 创建映射 PUT user {"mappings&qu…...

HTML---网页布局

目录 文章目录 一.常见的网页布局 二.标准文档流 标准文档流常见标签 三.display属性 四.float属性 总结 一.常见网页布局 二.标准文档流 标准文档流常见标签 标准文档流的组成 块级元素<div>、<p>、<h1>-<h6>、<ul>、<ol>等内联元素<…...

python 普通存款(单利)计算公式:

python 普通存款&#xff08;单利&#xff09;计算公式&#xff1a; 代码如下&#xff1a; #普通存款 单利计算公式&#xff1a;a:原值&#xff0c;n:计算年限&#xff0c;li&#xff1a;利率&#xff08;小数&#xff09;, def danli(a,n,li):print("普通存款(单利)计…...

什么是 PHP 内存溢出 ?遇到了要如何解决呢 ?

PHP内存溢出指的是在PHP应用程序中&#xff0c;分配给脚本执行的内存超出了PHP配置文件中设置的限制。当脚本尝试使用比可用内存更多的内存时&#xff0c;就会发生内存溢出错误。 一、内存溢出可能由以下几个原因引起&#xff1a; 循环引用&#xff1a;如果存在循环引用&#…...

本地使用 docker 运行OpenSearch + Dashboard + IK 分词插件

准备基础镜像 注意一定要拉取和当前 IK 分词插件版本一致的 OpenSearch 镜像: https://github.com/aparo/opensearch-analysis-ik/releases 写这篇文章的时候 IK 最新版本 2.11.0, 而 dockerhub 上 OpenSearch 最新版是 2.11.1 如果版本不匹配的话是不能用的, 小版本号对不上…...

【JavaEE初阶一】线程的概念与简单创建

1. 认识线程&#xff08;Thread&#xff09; 1.1 关于线程 1.1.1 线程是什么 由前一节的内容可知&#xff0c;进程在进行频繁的创建和销毁的时候&#xff0c;开销比较大&#xff08;主要体现在资源的申请和释放上&#xff09;&#xff0c;线程就是为了解决上述产生的问题而提…...

三叠云工程劳务管理,优化建筑施工管理,提升效率与质量

随着建筑行业的蓬勃发展&#xff0c;工程施工现场管理变得愈发复杂。传统的人员管理方式已经无法满足企业快速发展的需求。如何提高施工效率、优化人力资源管理成为了建筑企业亟待解决的问题。逐渐走向数字化的工程建设行业&#xff0c;急需一种足以匹配这一时代变革、高效管理…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...