主流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;
        else
            puts("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操作需要的结构体 缓冲区 overlapped
		ioInfo = 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.mfbz.cn/a/783817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux下QT程序启动失败问题排查方法

文章目录 0.问题背景1.程序启动失败常见原因2.排查依赖库问题2.1 依赖库缺失2.2 依赖库加载路径错误2.3 依赖库版本不匹配2.4 QT插件库缺失2.4.1 QT插件库缺失2.4.2 插件库自身的依赖库缺失 2.5 系统基础C库不匹配 3.资源问题3.1 缺少翻译文件3.2 缺少依赖的资源文件3.3 缺少依…

视频融合共享平台LntonCVS视频监控汇聚平台工业视频监控系统

LntonCVS是一款功能强大、灵活部署的安防视频监控平台&#xff0c;具备广泛的扩展性和视频能力。它支持多种主流标准协议&#xff0c;如国标GB28181、RTSP/Onvif、RTMP&#xff0c;同时还能兼容厂家的私有协议和SDK&#xff0c;如海康Ehome、海大宇等。除了传统的安防监控功能外…

基于51单片机的四路抢答器Protues仿真设计

一、设计背景 近年来随着科技的飞速发展&#xff0c;单片机的应用正在不断的走向深入。本文阐述了基于51单片机的八路抢答器设计。本设计中&#xff0c;51单片机充当了核心控制器的角色&#xff0c;通过IO口与各个功能模块相连接。按键模块负责检测参与者的抢答动作&#xff0c…

ExcelVBA运用Excel的【条件格式】(二)

ExcelVBA运用Excel的【条件格式】&#xff08;二&#xff09; 前面知识点回顾 1. 访问 FormatConditions 集合 Range.FormatConditions 2. 添加条件格式 FormatConditions.Add 方法 语法 表达式。添加 (类型、 运算符、 Expression1、 Expression2) 3. 修改或删除条件…

【数据结构】线性表----栈详解

栈 栈&#xff08;Stack&#xff09;是一种常见的数据结构&#xff0c;它具有**后进先出&#xff08;Last In, First Out, LIFO&#xff09;**的特点。栈的运作类似于物理世界中的叠盘子&#xff1a;最新放上去的盘子最先被拿走&#xff0c;而最底部的盘子最后才能被取出。 如…

企业文档加密软件推荐丨2024企业用什么加密软件

在数字化时代&#xff0c;信息安全已经成为企业和个人不可忽视的问题。文档加密软件作为一种保护敏感信息不被非法访问或篡改的有效工具&#xff0c;其重要性日益凸显。通过加密技术&#xff0c;可以确保文档内容在传输和存储过程中的安全性&#xff0c;防止数据泄露和未经授权…

谷粒商城学习笔记-使用renren-fast-vue框架时安装依赖包遇到的问题及解决策略

文章目录 1&#xff0c;npm error Class extends value undefined is not a constuctor or null2&#xff0c;npm warn cli npm v10.8.1 does not support Node.js v16.20.2.3&#xff0c;npm error code CERT_HAS_EXPIRED学习心得 这篇文章记录下使用renren-fast-vue&#xff…

Spring Boot:连接MySQL错误Public Key Retrieval is not allowed

环境&#xff1a; MySQL版本&#xff1a;8.0.17 SpringBoot版本&#xff1a;2.5.15 解决 解决方式很简单&#xff0c;在数据库配置连接字符串spring.datasource.url末尾添加&allowPublicKeyRetrievaltrue即可&#xff0c;如下图&#xff1a; 重新启动&#xff0c;恢复正常…

Ai Native应用开发(一)--数字人

背景 刚参加完24年世界人工智能大会&#xff08;WAIC&#xff09;&#xff0c;聊聊自己的一些感受。这次会明显比去年多很多人&#xff0c;用人山人海来形容应该也不为过。根据我自己粗浅观察参会的人员也比去年更多样化。去年更多还是从业者或者是这块研究人员。今年每个论坛…

Pytorch实战(二):VGG神经网络

文章目录 一、诞生背景二、VGG网络结构2.1VGG块2.2网络运行流程2.3总结 三、实战3.1搭建模型3.2模型训练3.3训练结果可视化3.4模型参数初始化 一、诞生背景 从网络结构中可看出&#xff0c;所有版本VGG均全部使用33大小、步长为1的小卷积核&#xff0c;33卷积核同时也是最小的能…

Linux网络配置管理

目录 一、网络配置 1. 网卡配置 2. 路由 二、 网络信息查看 1.netstat 2. ss 三、 额外的命令 time 一、网络配置 之前我们学过 ifconfig &#xff0c;这个命令可以查看网络接口的地址配置信息&#xff0c;我们只知道它可以查看接口名称、IP 地址、子网掩码等。 但是&a…

java —— tomcat 部署项目

一、通过 war 包部署 1、将项目导出为 war 包&#xff1b; 2、将 war 包放置在 tomcat 目录下的 webapps 文件夹下&#xff0c;该 war 包稍时便自动解析为项目文件夹&#xff1b; 3、启动 tomcat 的 /bin 目录下的 startup.bat 文件&#xff0c;此时即可从浏览器访问项目首页…

windows 11 + kali wsl二合一配置步骤与踩坑

windows 11 kali wsl二合一配置步骤与踩坑 在前几天的某市攻防演练中&#xff0c;在攻防前期&#xff0c;我的虚拟机经常无缘无故出现断网、卡顿等现象&#xff0c;但找不出原因。 为了不影响后续的这些天的攻防演练&#xff0c;我选择在一个晚上通宵 在我的windows 11系统上…

2.作业2

目录 1.作业题目 A图 B代码 2.css盒子模型 0.css盒子模型 1.外边距&#xff08;margin&#xff09; 2.边框&#xff08;border&#xff09; 3.内边距&#xff08;padding&#xff09; ​编辑 3.GET方法与POST方法的区别 学习产出&#xff1a; html的作业 1.作业题目 A图…

无向图中寻找指定路径:深度优先遍历算法

刷题记录 1. 节点依赖 背景: 类似于无向图中, 寻找从 起始节点 --> 目标节点 的 线路. 需求: 现在需要从 起始节点 A, 找到所有到 终点 H 的所有路径 A – B &#xff1a; 路径由一个对象构成 public class NodeAssociation {private String leftNodeName;private Stri…

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码&#xff1a; 新建主图幅图类型指标都可以&#xff01; VAR1:(HL)/2; 唇:REF(SMA(VAR1,5,1),3),COLORGREEN; 齿:REF(SMA(VAR1,8,1),5),COLORRED; 颚:REF(SMA(VAR1,13,1),8),COLORBLUE;

离线查询+线段树,CF522D - Closest Equals

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 522D - Closest Equals 二、解题报告 1、思路分析 考虑查询区间已经给出&#xff0c;我们可以离线查询 对于这类区间离线查询的问题我们通常可以通过左端点排序&#xff0c;然后遍历询问同时维护左区间信息…

数据泄露态势(2024年5月)

监控说明&#xff1a;以下数据由零零信安0.zone安全开源情报系统提供&#xff0c;该系统监控范围包括约10万个明网、深网、暗网、匿名社交社群威胁源。在进行抽样事件分析时&#xff0c;涉及到我国的数据不会选取任何政府、安全与公共事务的事件进行分析。如遇到影响较大的伪造…

《金山 WPS AI 2.0:重塑办公未来的智能引擎》

AITOP100平台获悉&#xff0c;在 2024 世界人工智能大会这一科技盛宴上&#xff0c;金山办公以其前瞻性的视野和创新的技术&#xff0c;正式发布了 WPS AI 2.0&#xff0c;犹如一颗璀璨的星辰&#xff0c;照亮了智能办公的新征程&#xff0c;同时首次公开的金山政务办公模型 1.…