主流I/O模型总结(Linux Windows)

I/O复用模型(EPOLL)

模型思想:向内核注册需要监听的文件描述符,操作系统负责保存监视对象文件描述符,当有事件发生时,epoll_wait仅返回有事件发生的文件描述符数组
优点:
1.无需编写以监视状态为目的的针对所有文件描述符的循环语句
2.调用epoll_wait时无需每次传递监视对象信息

条件触发:只要输入缓冲区有数据,就会触发epoll_wait()

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);
int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;char buf[BUF_SIZE];struct epoll_event ep_events[EPOLL_SIZE];struct epoll_event event;int epfd, event_cnt;serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));serv_adr.sin_family = AF_INET;if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != 0){error_handling("bind() error");return -1;}if (listen(serv_sock, 5) != 0){error_handling("listen() error");return -2;}epfd = epoll_create(EPOLL_SIZE);event.events = EPOLLIN;event.data.fd = serv_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);while (1){event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);if (event_cnt == -1){error_handling("epoll_wait error!");break;}for (int i = 0; i < event_cnt; i++){if (ep_events[i].data.fd == serv_sock) // 通信事件{socklen_t clnt_addrLen = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (sockaddr *)&clnt_adr, &clnt_addrLen);printf("connected client:%d\n", clnt_sock);event.events = EPOLLIN;event.data.fd = clnt_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);}else // 通信事件{int len = read(ep_events[i].data.fd, buf, BUF_SIZE);if (len == 0) // 连接关闭{epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);}puts(buf);write(ep_events[i].data.fd, buf, strlen(buf));}}}close(serv_sock);close(epfd);return 0;
}
void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

边缘触发:只会触发一次epoll_wait(非阻塞,忙轮询)

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 2
#define EPOLL_SIZE 50
void error_handling(char *buf);
void setnonnlockingmode(int fd);
char buf[BUF_SIZE];
int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct epoll_event ep_events[EPOLL_SIZE];struct epoll_event event;int epfd, event_cnt;serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));serv_adr.sin_family = AF_INET;if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != 0){error_handling("bind() error");return -1;}if (listen(serv_sock, 5) != 0){error_handling("listen() error");return -2;}epfd = epoll_create(EPOLL_SIZE);event.events = EPOLLIN;event.data.fd = serv_sock;setnonnlockingmode(serv_sock);epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);while (1){event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);printf("epoll_wait\n");if (event_cnt == -1){error_handling("epoll_wait error!");break;}for (int i = 0; i < event_cnt; i++){if (ep_events[i].data.fd == serv_sock) // 通信事件{socklen_t clnt_addrLen = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (sockaddr *)&clnt_adr, &clnt_addrLen);printf("connected client:%d\n", clnt_sock);event.events = EPOLLIN | EPOLLET;event.data.fd = clnt_sock;setnonnlockingmode(clnt_sock);epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);}else // 通信事件{while (1){int len = read(ep_events[i].data.fd, buf, BUF_SIZE);if (len == 0) // 连接关闭{epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);printf("closed client:%d\n", ep_events[i].data.fd);break;}else if (len < 0){if (errno == EAGAIN) // 没有数据可读break;}else{write(ep_events[i].data.fd, buf, len);}}}}}close(serv_sock);close(epfd);return 0;
}
void setnonnlockingmode(int fd)
{int flag = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}
void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

I/O复用模型(select)

模型思想:通过位数组(fd_set),向内核注册需要监视的文件描述符,当内核监听到位数组文件描述符有事件发生时,select返回,通知用户程序有事件发生,并返回内核标记了发生事件的文件描述符的位数组,可以通过遍历位数组判断哪些文件描述符发生了事件
特点:select可以跨平台
缺点:
1.每次调用select函数时向操作系统传递监视信息(所有需要监听的文件描述符都需要拷贝)
2.调用select函数后常见的针对所有文件描述符的循环语句

#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 1024
void error_handling(char *buf);
int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct timeval timeout;fd_set reads, cpy_reads; // 位数组int fd_max, fd_num;char buf[BUF_SIZE];serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));serv_adr.sin_family = AF_INET;if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != 0){error_handling("bind() error");return -1;}if (listen(serv_sock, 5) != 0){error_handling("listen() error");return -2;}FD_ZERO(&reads); // 清空位数组FD_SET(serv_sock, &reads);fd_max = serv_sock;while (1){cpy_reads = reads;timeout.tv_sec = 5;timeout.tv_usec = 5000;if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1){break;}if (fd_num == 0) // 超时返回{printf("超时...继续等待连接......\n");continue;}for (int i = 0; i < fd_max + 1; i++){if (FD_ISSET(i, &cpy_reads)) // i文件描述符是有事件发生{if (i == serv_sock) // 连接事件{socklen_t str_len = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (sockaddr *)&clnt_sock, &str_len);printf("connected client:%d", clnt_sock);FD_SET(clnt_sock, &reads);if (fd_max < clnt_sock)fd_max = clnt_sock;}else{ // 通信事件int str_len = read(i, buf, BUF_SIZE);if (str_len == 0) // 客户端断开连接{FD_CLR(i, &reads);close(i);printf("closed client:%d\n", i);}else{write(i, buf, str_len);}}}}}close(serv_sock);return 0;
}
void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

多进程并发服务器

模型思想:父进程负责连接,子进程负责通信

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUF_SIZE 30
void error_handling(char *buf);
void read_childproc(int sig);
int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, cln_adr;pid_t pid;char buf[BUF_SIZE];// 注册信号struct sigaction act;act.sa_handler = read_childproc;sigemptyset(&atc.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, 0);// 初始化网络serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));serv_adr.sin_family = AF_INET;if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != 0){error_handling("bind() error");return -1;}if (listen(serv_sock, 5) != 0){error_handling("listen() error");return -2;}while (1){socklen_t clnt_adr_len = sizeof(cln_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&cln_adr, &clnt_adr_len);if (clnt_sock == -1)continue;elseputs("new client connected...");pid = fork();if (pid == -1){close(clnt_sock);continue;}if (pid == 0) // 子进程(读写){close(serv_sock);int str_len;while ((str_len == read(clnt_sock, buf, BUF_SIZE)) != 0){write(clnt_sock, buf, strlen(buf));}close(clnt_sock);puts("clint disconnected...");return 0;}else{close(clnt_sock);}}close(serv_sock);return 0;
}// 回收子进程信号响应函数
void read_childproc(int sig)
{pid_t pid;int status;pid = waitpid(-1, &status, WNOHANG);printf("removed proc id:%d\n", pid);
}

多线程并发服务器

模型思想:主线程用于连接,开辟线程用于通信
多线程聊天服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_CLNT 100
#define BUF_SIZE 256
void error_handling(char *buf);
void read_childproc(int sig);
void send_msg(char *msg, int len);
void *handle_cnt(void *);
int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutex;
int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, cln_adr;char buf[BUF_SIZE];pthread_t tid;pthread_mutex_init(&mutex, NULL);// 初始化网络serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));serv_adr.sin_family = AF_INET;if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != 0){error_handling("bind() error");return -1;}if (listen(serv_sock, 5) != 0){error_handling("listen() error");return -2;}while (1){socklen_t clnt_adr_len = sizeof(cln_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&cln_adr, &clnt_adr_len);pthread_mutex_lock(&mutex);clnt_socks[clnt_cnt++] = clnt_sock;pthread_mutex_unlock(&mutex);// 开启线程用于通信pthread_create(&tid, NULL, handle_cnt, (void *)&clnt_sock);pthread_detach(tid);printf("Connected client IP:%s \n", inet_ntoa(cln_adr.sin_addr));}close(serv_sock);return 0;
}
void *handle_cnt(void *arg)
{int clnt_sock = *((int *)arg);int str_len = 0;char msg[BUF_SIZE];while ((str_len = read(clnt_sock, msg, BUF_SIZE)) != 0){puts(msg);send_msg(msg, str_len);}// 客户端断开连接pthread_mutex_lock(&mutex);for (int i = 0; i < clnt_cnt; i++){if (clnt_sock == clnt_socks[i]){while (i++ < clnt_cnt - 1){clnt_socks[i] = clnt_socks[i + 1];}break;}}clnt_cnt--;pthread_mutex_unlock(&mutex);close(clnt_sock);return NULL;
}void send_msg(char *msg, int len)
{pthread_mutex_lock(&mutex);for (int i = 0; i < clnt_cnt; i++){write(clnt_socks[i], msg, len);printf("message to :%d\n", clnt_socks[i]);}pthread_mutex_unlock(&mutex);
}void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

IOCP模型(Windows)

模型思想:创建完成端口对象,将连接套接字注册到完成端口对象中,向操作系统投递异步请求,开辟线程监听完成端口有已完成的I/O操作,并通知程序进行处理
注意:投递一个I/O请求,完成端口才会响应一次I/O操作

#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<WinSock2.h>
#include<process.h>
#include<iostream>
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
//客户端地址信息结构体
typedef struct
{SOCKET hClntSock;SOCKADDR_IN clntAdr;
}PER_HANDLE_DATA,*LPPER_HANDLE_DATA;
typedef struct
{OVERLAPPED overlapped;//传递overlapped地址相当于传递整个结构体首地址WSABUF wsaBuf;char buffer[BUF_SIZE];//缓冲区int rwMode; //READ or WRITE  IOCP不区分输入还是输出完成 只通知I/O完成状态
}PER_IO_DATA,*LPPER_IO_DATA;unsigned __stdcall EchoThreadMain(void* arg);//接收完成I/O的线程
void PrintWinsockError(const char* apiName);int main(int argc, char* argv[])
{WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData)!=0){PrintWinsockError("WSAStartup");return -1;}HANDLE hComPort;//完成端口LPPER_IO_DATA ioInfo;LPPER_HANDLE_DATA handleInfo;SOCKET hServSock;//服务器套接字SOCKADDR_IN servAdr;//服务器地址信息//创建完成端口 向CP对象分配此电脑CPU核数的线程hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建CPU核数个线程(接收已完成的I/O操作结果)SYSTEM_INFO sysInfo;GetSystemInfo(&sysInfo);//获取系统信息for (int i = 0; i < sysInfo.dwNumberOfProcessors; i++){_beginthreadex(NULL, 0,EchoThreadMain, (LPVOID)hComPort,0,NULL);}//创建连接套接字(重叠结构非阻塞)hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(atoi(argv[1]));servAdr.sin_family = PF_INET;bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));listen(hServSock, 5);while (1){SOCKET hClntSock;SOCKADDR_IN clntAdr;int addrLen = sizeof(clntAdr);hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);//等待连接//创建客户端地址信息结构体handleInfo = new PER_HANDLE_DATA;handleInfo->hClntSock = hClntSock;memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);//连接完成端口和已连接客户端套接字CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (ULONG_PTR)handleInfo,0);//创建I/O操作需要的结构体 缓冲区 overlappedioInfo = new PER_IO_DATA;memset(&ioInfo->overlapped, 0, sizeof(OVERLAPPED));ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->wsaBuf.len = BUF_SIZE;ioInfo->rwMode = READ;//投递异步Recv请求DWORD recvBytes, flags = 0;WSARecv(hClntSock, &ioInfo->wsaBuf, 1, &recvBytes, &flags, &ioInfo->overlapped, NULL);}return 0;
}
//线程函数
unsigned __stdcall EchoThreadMain(void* pComPort)
{HANDLE hComPort = (HANDLE)pComPort;SOCKET sock;DWORD bytesTrans;LPPER_HANDLE_DATA handleInfo;LPPER_IO_DATA ioInfo;DWORD flags = 0;while (1){//监听是否有I/O操作完成GetQueuedCompletionStatus(hComPort, &bytesTrans, (PULONG_PTR)&handleInfo,(LPOVERLAPPED*)&ioInfo, INFINITE);sock = handleInfo->hClntSock;if (ioInfo->rwMode == READ)//read 完成{puts("message received!");if (bytesTrans == 0)//传输EOF时{closesocket(sock);free(handleInfo);free(ioInfo);continue;}//投递异步Send请求(回声)memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));ioInfo->wsaBuf.len = bytesTrans;ioInfo->rwMode = WRITE;WSASend(sock, &ioInfo->wsaBuf, 1, NULL, 0, &ioInfo->overlapped, NULL);//投递异步Recv请求ioInfo = new PER_IO_DATA;memset(&ioInfo->overlapped, 0, sizeof(WSAOVERLAPPED));ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->wsaBuf.len = BUF_SIZE;ioInfo->rwMode = READ;WSARecv(sock, &ioInfo->wsaBuf, 1, NULL, &flags, &ioInfo->overlapped, NULL);}else{puts("message sent!");free(ioInfo);}}return 0;
}
void PrintWinsockError(const char* apiName) {int errorCode = WSAGetLastError();LPVOID lpMsgBuf;DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS,NULL,errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0, NULL);if (bufLen) {std::cerr << apiName << " failed with error: " << lpMsgBuf << std::endl;LocalFree(lpMsgBuf);}else {std::cerr << apiName << " failed with unknown error code: " << errorCode << std::endl;}
}

异步重叠I/O模型(Windows)

模型思想:向操作系统投递异步I/O请求,当有I/O操作完成时,调取相应响应函数(Completion Routine函数),并通过 overlapped 传递已连接的客户端信息 特点:每个客户端都需要一个overlapped结构,投递一次I/O请求,操作系统仅通知一次
注意:该模型操作系统会异步 处理 I/O操作,处理完成后操作系统再将处理结果返回给程序
缺点:重复调用非阻塞模式的accept函数和进入alertable wait状态为目的WleepEx函数严重影响性能

#include<iostream>
#include<stdio.h>
#include<WinSock2.h>
#define BUF_SIZE 1024
void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);//读完成接收响应函数
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);//写完成接收响应函数
void PrintWinsockError(const char* apiName);//错误打印函数
/
typedef struct {SOCKET hClntSock;//套接字句柄char buf[BUF_SIZE];//缓冲区WSABUF wsaBuf;
}PER_IO_DATA,*LPPER_IO_DATA;int main(int argc, char* argv[])
{WSADATA wsaData;if (WSAStartup(MAKEWORD(2,2),&wsaData)!=0){PrintWinsockError("WSAStartup");return -1;}SOCKET hLisnSock, hRecvSock;SOCKADDR_IN lisnAdr, recvAdr;LPWSAOVERLAPPED lpOvLp;DWORD recvBytes;LPPER_IO_DATA hbInfo;DWORD mode = 1, flagInfo = 0;//创建非阻塞连接套接字hLisnSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);ioctlsocket(hLisnSock, FIONBIO, &mode);//设置非阻塞memset(&lisnAdr, 0, sizeof(lisnAdr));lisnAdr.sin_family = PF_INET;lisnAdr.sin_addr.s_addr = htonl(INADDR_ANY);lisnAdr.sin_port = htons(atoi(argv[1]));if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr))==SOCKET_ERROR){PrintWinsockError("bind");return -2;}if (listen(hLisnSock,5) == SOCKET_ERROR){PrintWinsockError("listen");return -2;}int RecvAdr_len = sizeof(recvAdr);while (1){SleepEx(100, TRUE);//使线程处于alertable wait状态(等待接收操作系统消息状态)hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &RecvAdr_len);//非阻塞接收连接if (hRecvSock == INVALID_SOCKET){if (WSAGetLastError() == WSAEWOULDBLOCK)//非阻塞没有客户端连接{continue;//继续监听连接}else{PrintWinsockError("accept");}}printf("Client connected...\n");lpOvLp = new WSAOVERLAPPED;//创建一个重叠结构memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));//储存客户端信息hbInfo = new PER_IO_DATA;hbInfo->hClntSock = hRecvSock;hbInfo->wsaBuf.buf = hbInfo->buf;hbInfo->wsaBuf.len = BUF_SIZE;lpOvLp->hEvent = (HANDLE)hbInfo;//利用hEvent成员传递客户端信息//投递异步Recv请求WSARecv(hRecvSock, &hbInfo->wsaBuf, 1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);}closesocket(hRecvSock);closesocket(hLisnSock);WSACleanup();return 0;
}//异步数据接收完成操作系统通知函数
void CALLBACK ReadCompRoutine(DWORD dwError,DWORD szRecvRytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)
{LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);//传递过来的客户端信息SOCKET hSock = hbInfo->hClntSock;LPWSABUF bufInfo = &(hbInfo->wsaBuf);//操作系统将接收完成的数据已经写入DWORD sentBytes;if (szRecvRytes == 0)//客户端关闭,没有收到数据{closesocket(hSock);free(lpOverlapped->hEvent);free(lpOverlapped);printf("Client disconnected...");}else{puts(bufInfo->buf);bufInfo->len = szRecvRytes;//投递异步Send请求WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);}
}
//异步数据发送完成操作系统通知函数
void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szRecvRytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);SOCKET hSock = hbInfo->hClntSock;LPWSABUF bufInfo = &(hbInfo->wsaBuf);DWORD recvBytes;DWORD flagInfo = 0;//投递Recv异步请求WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}void PrintWinsockError(const char* apiName) {int errorCode = WSAGetLastError();LPVOID lpMsgBuf;DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS,NULL,errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0, NULL);if (bufLen) {std::cerr << apiName << " failed with error: " << lpMsgBuf << std::endl;LocalFree(lpMsgBuf);}else {std::cerr << apiName << " failed with unknown error code: " << errorCode << std::endl;}
}

异步通知I/O模型(Windows)

模型思想:将套接字和内核事件对象绑定,通过内核事件对象的状态变化(no-signaled→signaled),判定对应套接字是否有I/O操作,并通知程序有I/O操作需要处理 特点:一个套接字需要对应创建一个内核对象
注意:该模型只是异步 通知 有I/O操作需要处理,但操作系统不异步处理I/O操作
缺点:仅异步通知有I/O操作,不异步处理I/O操作

#include<string.h>
#include<stdio.h>
#include<WinSock2.h>
#define BUF_SIZE 100
void CompressSockets(SOCKET hSockArr[], int idx, int total);
void CompressEvent(WSAEVENT hEventArr[], int idx, int total);
char msg[BUF_SIZE];//缓冲区
int main(int argc,char* argv[])
{WSADATA wsaData;//初始化网络if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){printf("WSAStartup error!\n");return -1;}SOCKET hServSock, hClentSock;//服务器客户端套接字SOCKADDR_IN servAdr, clntAdr;//服务器客户端地址SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];//套接字数组WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];//句柄数组int numOfClntSock = 0;//套接字数量WSAEVENT newEvent;//事件对象//初始化服务器套接字hServSock = socket(PF_INET, SOCK_STREAM, 0);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = ntohs(atoi(argv[1]));if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){printf("bind error!\n");return -2;}if (listen(hServSock, 5) == SOCKET_ERROR){printf("listen error!\n");return -3;}newEvent = WSACreateEvent();//创建一个事件对象if (WSAEventSelect(hServSock, newEvent, FD_ACCEPT) == SOCKET_ERROR)//连接事件对象的套接字 套接字发生FD_ACCEPT事件,newEvent内核对象改变为signaled状态{printf("listen error!\n");return -4;}hSockArr[numOfClntSock] = hServSock;//加入套接字hEventArr[numOfClntSock] = newEvent;//加入句柄numOfClntSock++;//开始监听事件while (1){int posInfo, StartIdx;//StartIdx = posInfo-WSA_WAIT_EVENT_0; 转变为signaled状态的事件对象最小句柄索引posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);//等待一个事件发生StartIdx = posInfo - WSA_WAIT_EVENT_0; //转变为signaled状态的事件对象的最小句柄的索引for (int i = StartIdx; i < numOfClntSock; i++){int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);//遍历其他每个事件对象(非阻塞)if (sigEventIdx == WSA_WAIT_FAILED || sigEventIdx == WSA_WAIT_TIMEOUT)//i对应内核对象没有发生事件{continue;}else//i内核对象发生了事件{WSANETWORKEVENTS netEvents;//保存事件类型和错误信息sigEventIdx = i;WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);//区分事件类型if (netEvents.lNetworkEvents & FD_ACCEPT)//连接事件{if (netEvents.iErrorCode[FD_ACCEPT_BIT])//错误{printf("Accept Error!\n");return -5;}int clntAdrLen = sizeof(clntAdr);//客户端地址长度hClentSock = accept(hSockArr[sigEventIdx], (SOCKADDR*)&clntAdr, &clntAdrLen);//接收连接newEvent = WSACreateEvent();//创建新内核事件对象WSAEventSelect(hClentSock, newEvent, FD_READ | FD_CLOSE);//连接事件对象的套接hEventArr[numOfClntSock] = newEvent;hSockArr[numOfClntSock] = hClentSock;numOfClntSock++;printf("connected new client...");}if (netEvents.lNetworkEvents & FD_READ)//通信事件{if (netEvents.iErrorCode[FD_READ_BIT] != 0){printf("read error");return -6;}int strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);send(hSockArr[sigEventIdx], msg, strLen, 0);}if (netEvents.lNetworkEvents & FD_CLOSE)//断开连接事件{if (netEvents.iErrorCode[FD_CLOSE_BIT] != 0){printf("Close Error");break;}//关闭套接字和事件对象WSACloseEvent(hEventArr[sigEventIdx]);closesocket(hSockArr[sigEventIdx]);//调整套接字数组和事件数组平衡numOfClntSock--;CompressEvent(hEventArr, sigEventIdx, numOfClntSock);CompressSockets(hSockArr, sigEventIdx, numOfClntSock);}}}}WSACleanup();return 0;
}void CompressSockets(SOCKET hSockArr[], int idx, int total)
{for (int i=idx; i < total; i++){hSockArr[i] = hSockArr[i + 1];}
}
void CompressEvent(WSAEVENT hEventArr[], int idx, int total)
{for (int i=idx; i < total; i++){hEventArr[i] = hEventArr[i + 1];}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/43018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

案例|水上水下一体化测量,为九寨沟精准把脉

​ 九寨沟&#xff0c;被誉为“人间仙境”&#xff0c;其湖群以独特的地理位置和优美的自然景观吸引着世界各地的游客&#xff0c;更是九寨沟生态系统中不可或缺的重要组成部分。因此&#xff0c;精准地掌握湖群的地形数据、水体分布及变化情况&#xff0c;能够揭示水下生态系…

【数据结构与算法基础】算法复杂度

欢迎光顾我的homepage 前言 算法就是定义良好的计算过程&#xff0c;它取一个活一组的值输入&#xff0c;并产生出一个或一组值作为输出。简单来说&#xff0c;算法就是一系列的计算步骤&#xff0c;用来将输入数据转化成输出结果。 一、算法效率 如何去衡量一个算法的好坏&am…

[C++]——同步异步日志系统(3)

同步异步日志系统 一、日志系统框架设计1.1模块划分1.1.1 日志等级模块1.1.2 日志消息模块1.1.3 日志消息格式化模块1.1.4 日志落地模块&#xff08;日志落地的方向是工厂模式&#xff09;1.1.5 日志器模块&#xff08;日志器的生成是建造者模式&#xff09;1.1.6 异步线程模块…

Android12上实现双以太网卡共存同时访问外网

具体实现如下&#xff1a; 修改main 表优先级到9999&#xff0c; 作用&#xff1a;eth0 eth1 访问 不去teardown 低分数网线 diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 418e…

Ubuntu 22.04 设置swap交换空间

经常爆内存&#xff0c;导致很多应用没有办法一直正常运行&#xff0c;可以通过设置swap来缓解一下&#xff0c;虽然和内存的速度无法媲美&#xff0c;但是能一定程度缓解一下问题。 一、查看当前分区 查看当前系统的swap大小 free -m 二、关闭现有的swap分区 将/etc/fstab…

CUDA Kernel调试与优化--背景知识扫盲(LLM生成)

CUDA Kernel调试与优化–背景知识扫盲(LLM生成) 对于使用CUDA进行调试与性能优化&#xff0c;官方提供了丰富的参考资料和工具。以下是一些关键资源&#xff0c;可以帮助你更好地调试和优化CUDA代码&#xff1a; 官方文档和指南 CUDA Toolkit Documentation URL: CUDA Toolk…

强化学习总结(有具体代码实现)

文章目录 第一部分 强化学习基础第1章 强化学习概述1.1 强化学习概念1.2 强化学习的环境1.3 强化学习的目标1.4 强化学习的数据 第2章 多臂老虎机问题&#xff08;MAB问题&#xff09;2.1 问题描述2.1.1 问题定义2.1.2 形式化描述2.1.3 累积懊悔2.1.4 估计期望奖励 2.2 解决方法…

CSS 【详解】CSS 函数(含 calc,min,max,clamp,cubic-bezier,env,steps 等)

函数描述CSS 版本attr()返回选择元素的属性值。2calc()允许计算 CSS 的属性值&#xff0c;比如动态计算长度值。3cubic-bezier()定义了一个贝塞尔曲线(Cubic Bezier)。3hsl()使用色相、饱和度、亮度来定义颜色。3hsla()使用色相、饱和度、亮度、透明度来定义颜色。3linear-grad…

Bert 变种, T5模型

NLP-预训练模型-2019-NLU&#xff1a;DistilBERT【 BERT模型压缩】【模型大小减小了40%&#xff08;66M&#xff09;&#xff0c;推断速度提升了60%&#xff0c;但性能只降低了约3%】_distillbert-CSDN博客 https://zhuanlan.zhihu.com/p/673535548 大语言模型系列-T5_t5模型…

【机器学习】必会数学知识:一文掌握数据科学核心数学知识点(上),值得收藏~

核心数学知识点 1、引言2、数据科学必会数学知识2.1 线性代数2.2 微积分2.3 概率论2.4 数理统计2.5 随机过程2.6 数据分布2.7 贝叶斯统计2.8 线性回归2.9 逻辑回归2.10 矩阵分解2.11 主成分分析&#xff08;PCA&#xff09;2.12 奇异值分解&#xff08;SVD&#xff09; 3、总结…

【Git 入门】初始化配置与新建仓库

文章目录 前言配置git新建仓库仓库的概念创建仓库命令总结前言 在现代软件开发中,版本控制系统已经成为了不可或缺的工具。其中,Git 是最为广泛使用的版本控制系统之一。Git 不仅可以帮助我们管理和跟踪代码的变化,还可以方便地与他人协作。本文将介绍 Git 的基础知识,包括…

【人工智能大语言模型技术发展研究报告 2024】

文末‍有福利&#xff01; 人工智能作为引领新一轮科技产业革命的战略性技术和新质生产力重要驱动力&#xff0c;正在引发经济、社会、文化等领域的变革和重塑&#xff0c;2023 年以来&#xff0c;以 ChatGPT、GPT-4 为代表的大模型技术的出台&#xff0c;因其强大的内容生成及…

提升教师健康,聚焦智慧校园人事系统的职工体检功能

智慧校园人事管理系统内置的职工体检管理&#xff0c;是专为教职员工设计的一项健康管理创新实践&#xff0c;巧妙融合先进信息技术&#xff0c;致力于为教职工提供更加便捷、易懂且持续性的健康检查与管理支持。该服务从多个维度出发&#xff0c;全面呵护教职工的身心健康。 该…

给你的博客加上评论区

一个网站如果有评论功能&#xff0c;可以更好的和读者互动。VuePress 也有很多评论插件&#xff0c;这里简单介绍下&#xff0c;最后介绍本站所使用的 Twikoo。 大部分评论插件都是使用的 Github 或 Gitee 的 issue 功能&#xff0c;也就是用 issue 去存储评论&#xff1b;而 …

自然语言处理(NLP)与大语言模型(LLM) 主要差异

一、简述 NLP 和 LLM 技术是大规模分析和生成人类语言的核心。随着它们的日益普及&#xff0c;区分 LLM 与 NLP 变得越来越重要。 NLP 包含一套用于理解、操纵和生成人类语言的算法。自 20 世纪 50 年代诞生以来&#xff0c;NLP 已发展到分析文本关系的阶段。它使用词性标注、命…

脚本实现保留文本中特定字符之后的字符串

#目的背景 原始txt文本如下图 目的是为了去除序号&#xff0c;每行只单独呈现域名 手工删除漫长又麻烦&#xff0c;使用脚本快捷些 代码实现逻辑&#xff1a; 1.使用open函数打开文本&#xff0c;之后用变量lines存储文本的所有行&#xff0c;使用for循环&#xff0c;让变量te…

暑假学习计划怎么做 用待办计划软件安排更科学

暑期来临&#xff0c;无论是学生还是老师&#xff0c;做好暑期计划都至关重要。记得去年暑假&#xff0c;我给自己定下了阅读十本书的目标&#xff0c;却因为缺乏明确的计划&#xff0c;最后只草草读完了两本。而今年&#xff0c;我决定尝试一种新的方式——使用待办计划软件来…

大学生数学竞赛教程(蒲和平)

大学生数学竞赛教程(蒲和平) https://pan.baidu.com/s/1ytcIbVcZpof9WM1xa2dDfA 提取码: kf2r 源文件来自于&#xff1a;大学生数学竞赛教程【蒲和平】

谷粒商城实战笔记-24-分布式组件-SpringCloud Alibaba-Nacos配置中心-命名空间与配置分组

文章目录 一&#xff0c;命名空间1&#xff0c;简介1.1&#xff0c;命名空间的主要功能和特点1.2&#xff0c;使用场景1.3&#xff0c;如何指定命名空间 2&#xff0c;命名空间实战2.1&#xff0c;环境隔离2.2&#xff0c;服务隔离 二&#xff0c;配置集三&#xff0c;配置集ID…

【数据基础】— 基于Go1.19的站点模板爬虫的实现

目录 1. 定义目标站点 2. 使用Go的库 3. 发送HTTP请求 4. 解析HTML并提取数据 5. 存储数据 6. 并发处理 示例代码 基于Go 1.19的站点模板爬虫实现通常涉及几个关键步骤&#xff1a;定义目标站点、解析HTML页面、提取所需数据、存储数据以及可能的并发处理。下面我将详细…