select/poll/epoll/iocp 网络模型

select
void TestModel::doSelect(int _sock){
	/*
	principle:
	user transport fd_vec to kernel ,kernel check fd signal state ,and return have signal state fd_vec to user

interface:
具体的操作:
(1)四个操作宏
FD_ZERO() 将列表清零
FD_SET() 将fd添加到列表中
FD_CLR() 将fd从列表清除
FD_ISSET() 判断fd是否存在列表

(2)检测fd信号状态的函数
int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);
param:
_nfds:			检测的最大的fd的大小,例如5,则大于或者等于5的fd将不检测
fd_set:		fd的数组,有大小限制(一般1024),select 可以检测可读,可写,错误
_time_out:		超时设置
	*/
	
	fd_set client_set;
	FD_ZERO(&client_set);
	FD_SET(_sock, &client_set);
	const int recv_buf_stander_len = (1 << 10) * 16;
	char recv_buf[recv_buf_stander_len]{ 0 };
	int fd_max = _sock;
	timeval time_out{ 1,0 };
	while (true) {
		fd_set  read_set = client_set;//because fd_set will change ,so we must copy it
		int signal_count = select(fd_max+1, &read_set, NULL, NULL, &time_out);
		if (signal_count == 0)continue;
		if (FD_ISSET(_sock, &read_set) == true) {//listen sock have client enter
			sockaddr_in addr{ 0 };
			unsigned int addr_len = sizeof(addr);
			auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
			if (client_sock == -1) {
				LOGXF(errno, VAR_DATA(strerror(errno)));
				sleep(-1);
				exit(1);
			}
			FD_SET(client_sock, &client_set);
			addClient(&addr, client_sock);
			fd_max = std::max(client_sock, fd_max);
			LOGXD(VAR_DATA(client_sock));

		}
		else {//client recv
			vector<int> need_delete_sock_vec{};
			for(auto it:m_imp->m_clients){
				int client_sock = it->c_sock;
				if (FD_ISSET(client_sock, &read_set) == false)continue;
				int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
				if (recv_len <= 0) { need_delete_sock_vec.emplace_back(client_sock); continue; }
				//LOGXD("recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
			}
			for(auto it:need_delete_sock_vec){
				FD_CLR(it, &client_set);
				delClient(it);
			}

		}

	}


}
poll
void TestModel::doPoll(int _sock){
	/*
	principle:
	user transport poll_fd_vec to kernel ,kernel check fd signal state ,and will change poll_fd_revents,vec no change

	poll检车所需的信号状态,然后改变的是revents,所以,使用的时候还是遍历数组,和select 区别不同的是fd_set传递进去和传递出来
	的是不一样的(client sock个数不一样了,只是返回有信号的sock),所以在fd_set那里,我们得复制一份出来,但是pool_fd,client的sock
	并没有改变,so. 还有就是poll个数不限制了

(1)data struct
struct pollfd{
	int fd;					/* File descriptor to poll.  
	short int events;		/* Types of events poller cares about.  
	short int revents;		/* Types of events that actually occurred.  
};

poll (
struct pollfd *__fds,//poll_fd_vec
nfds_t __nfds, //check max_fd
int __timeout)//unit is ms

	*/
	const int fd_max_count = (200);//理论上可以存在fd_max_count-1个client,1是listen,但是如果经常fd_max_count-1,程序会出错,一般fd_max_count-4
	
	pollfd client_fds[fd_max_count]{-1};
	
	client_fds[0].fd = _sock;
	client_fds[0].events = POLLIN;

	int max_fd = _sock;
	timeval time_out{ 1,0 };

	const int recv_buf_stander_len = (1 << 10) * 16;
	char recv_buf[recv_buf_stander_len]{ 0 };

	while (true) {
		int signal_count = poll(client_fds, max_fd + 1,1);
		if(signal_count==0)continue;
		LOGXD(VAR_DATA(signal_count));
		int signal_number = 0;//increase effective
		//1.deal listen
		if (client_fds[0].revents & POLLIN) {//listen sock,accept
			sockaddr_in addr{ 0 };
			unsigned int addr_len = sizeof(addr);
			auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
			if (client_sock == -1) {
				LOGXF(VAR_DATA(strerror(errno)));
				sleep(-1);
				exit(1);
			}

			addClient(&addr, client_sock);
			max_fd = std::max(max_fd, client_sock);
			for(int i=1;i< fd_max_count;i++){
				if (client_fds[i].fd <= 0) {
					client_fds[i].fd = client_sock;
					client_fds[i].events = POLLIN;
					break;
				}
			}
			signal_number += 1;
		}
		//2.deal client
		for(int i=1;i<fd_max_count;i++){
			//LOGXT(VAR_DATA(i));
			if (signal_number >= signal_count)break;
			if (client_fds[i].fd <= 0)continue;
			if (client_fds[i].revents & (POLLIN | POLLERR)) {
				++signal_number;
				int client_sock = client_fds[i].fd;
				int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
				if (recv_len <= 0) {
					delClient(client_sock);
					client_fds[i].fd = -1;
					continue;
				}
				LOGXD("recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
			}
			

		}
		LOGXD(VAR_DATA(signal_number));
	}

}

epoll
void TestModel::doEpoll(int _sock){
	/*
	epoll 不用从用户态传递fd_vec给内核检测相关的信号,用户直接调用相关函数操作存储在内核的fd_vec
	相关的函数:
	int epoll_ctl (int __epfd, int __op, int __fd,
			  struct epoll_event *__event) __THROW;//用于将fd加入\删除\x修改内核态的epoll_fd_vec

	int signal_count =  epoll_wait(epoll_fd, client_events, max_event_count, 20);//用于检测相关的信号,
	返回值client_events数组,大小是signal_count,20超时毫秒
	max_event_count是返回数据最大的数组大小,与epoll_create的大小值无关
	*/

	int  epoll_fd = epoll_create((1<<20)*10);//内核数组的大小,可接受client 连接的近似数量
	{
		epoll_event temp;
		temp.events = EPOLLIN;
		temp.data.fd = _sock;
		epoll_ctl(epoll_fd, EPOLL_CTL_ADD, _sock, &temp);
	}

	const int max_event_count = 200;
	epoll_event client_events[max_event_count]{ 0 };//
	const int recv_buf_stander_len = (1 << 10) * 16;
	char recv_buf[recv_buf_stander_len]{ 0 };
	while (true) {
		int signal_count = epoll_wait(epoll_fd, client_events, max_event_count, 20);
		if (signal_count == 0)continue;
		for(int i=0;i<signal_count;i++){
			if (client_events[i].data.fd == _sock) {//listen sock
				sockaddr_in addr{ 0 };
				unsigned int addr_len = sizeof(addr);
				auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
				if(client_sock==-1){
					LOGXF(VAR_DATA(strerror(errno)));
					sleep(-1);
					exit(1);
				}
				addClient(&addr, client_sock);
				{
					epoll_event temp;
					temp.events = EPOLLIN;
					temp.data.fd = client_sock;
					epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sock, &temp);
				}
				
			}
			else {//client sock
				int client_sock = client_events[i].data.fd;
				int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
				if (recv_len < 0) {
					delClient(client_sock);
					{
						epoll_event temp;
						temp.events = EPOLLIN;
						temp.data.fd = client_sock;
						epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_sock, &temp);
					}
					continue;
				}
				LOGXD("epoll recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
			}
		}

	}

	close(epoll_fd);


}

IOCP
void TestModel::doIOCP(int _scok) {
	/*
	原理:
	将client的sock(实质就是fd)和同一个fd相关联,这样子只需要检测fd的状态,获取的client_fd的所需的信号的状态

HANDLE WINAPI CreateIoCompletionPort(
  _In_     HANDLE    FileHandle,
  _In_opt_ HANDLE    ExistingCompletionPort,
  _In_     ULONG_PTR CompletionKey,
  _In_     DWORD     NumberOfConcurrentThreads
);
官方文档是这样的:
	在创建新的完成端口时,
	FileHandle==INVALID_HANDLE_VALUE
	ExistingCompletionPort==NULL
	并且此时忽略CompletionKey,然后就会返回新的完成端口的handle

	将handle 和完成端口绑定的时候
	CreateIoCompletionPort((HANDLE)acceptSocket, completionPort, (DWORD)acceptSocket, NULL);
	if completionPort!=NULL   NumberOfConcurrentThreads will ingnore
	CompletionKey这东西是自己设置的,会一直跟随

BOOL GetQueuedCompletionStatus(
	HANDLE CompletionPort,				//指定的IOCP
	LPDWORD lpNumberOfBytes,			//一次完成后的I/O操作所传送数据的字节数
	PULONG_PTR lpCompletionKey,			//当文件I/O操作完成后,用于存放与之关联的CK
	LPOVERLAPPED *lpOverlapped,			//为调用IOCP机制所引用的OVERLAPPED结构
	DWORD dwMilliseconds);				//用于指定调用者等待CP的时间

int WSAAPI WSARecv (
	SOCKET s,							//socket
	LPWSABUF lpBuffers,					//存放数据的数组的指针
	DWORD dwBufferCount,				//数组的数量
	LPDWORD lpNumberOfBytesRecvd,		//接收字节数的指针
	LPINT lpFlags,						//指向标志位的指针
	LPWSAOVERLAPPED lpOverlapped,		//指向重叠结构的指针
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);//调用例程的指针

	如果需要什么样子的信号,得提前给client 的socket设置,就比如需要recv的,则
	WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
	这其中lpBuffers(PerIoData->DataBuf)得存在内存空间,不要为空,

	检测状态:
	 GetQueuedCompletionStatus(completionPort, &BytesTransferred,&completion_key, (LPOVERLAPPED*)&PerIoData, INFINITE)
	 这个时候BytesTransferred接收的数据的大小,
	 completion_key就是与client相对应的key,我们可以直接设置为sock,也可存相应的结构体的指针,
	 然后可以从指针找到结构体就可以找到sock,只要想办法和sock建立起联系就好
	 PerIoData从这里直接获取到接收的数据

	 最后因为传递进去的信号已经使用过了,相当于没有了,我们得继续传递相应的信号进去,为下一次的信号检测做准备
	 WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
	*/

	/// 结构体定义  
	const int recv_stander_size = (1 << 10) * 8;
	typedef struct {
		OVERLAPPED Overlapped;
		WSABUF DataBuf;
		CHAR Buffer[recv_stander_size];
	}PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;

	do {
		//start
		HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
		if (completionPort == NULL) {
			LOGXF("create iocp error", VAR_DATA(GetLastError()));
			break;
		}
		//2.open thread of check client_sock state
		SYSTEM_INFO mySysInfo;
		GetSystemInfo(&mySysInfo);
		for (DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2 + 2); ++i) {
			thread t([completionPort,this] {
				
				DWORD BytesTransferred;
				LPPER_IO_OPERATION_DATA PerIoData;
				DWORD completion_key = 0;
				while (true){
					if (false == GetQueuedCompletionStatus(completionPort, &BytesTransferred, 
						&completion_key, (LPOVERLAPPED*)&PerIoData, INFINITE)){
						LOGXW("GetQueuedCompletionStatus false");
						if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED)){
							int client_sock = completion_key;
							delClient(client_sock);
							delete PerIoData;//this delete wsarecv new
							continue;
						}
						else{
							LOGXF("GetQueuedCompletionStatus failed!");
							break;
						}
						
					}
					int client_sock = completion_key;
					
					if (BytesTransferred == 0){
						// 说明客户端已经退出  
						int client_sock = completion_key;
						delClient(client_sock);
						delete PerIoData;//this delete wsarecv new
						continue;
					}

					// 取得数据并处理  
					LOGXD("recv data", string((char*)(PerIoData->DataBuf.buf), BytesTransferred), VAR_DATA(client_sock));

					// 继续向 socket 投递WSARecv操作  
					DWORD Flags = 0;
					DWORD dwRecv = 0;
					ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
					PerIoData->DataBuf.buf = PerIoData->Buffer;
					PerIoData->DataBuf.len =sizeof(PerIoData->Buffer);
					WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
				}

				});
			t.detach();
		}
		//3.start accept client
		std::thread  acceptThread([this, _scok,completionPort] {
			do {
				SOCKADDR_IN saRemote;
				int RemoteLen;
				SOCKET acceptSocket;
				RemoteLen = sizeof(saRemote);
				// 3.1 接收连接,并分配完成端,这儿可以用AcceptEx()
				acceptSocket = accept(_scok, (SOCKADDR*)&saRemote, &RemoteLen);
				if (SOCKET_ERROR == acceptSocket) {	// 接收客户端失败
					LOGXF("accpet client fail:", VAR_DATA(GetLastError()));
					break;
				}
				//3.2 与completionPort绑定
				CreateIoCompletionPort((HANDLE)acceptSocket, completionPort, (DWORD)acceptSocket, NULL);
				//3.3
				LPPER_IO_OPERATION_DATA PerIoData = new PER_IO_OPERATION_DATA();//here new ,no delete
				ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
				PerIoData->DataBuf.buf = PerIoData->Buffer;
				PerIoData->DataBuf.len = sizeof(PerIoData->Buffer);
				DWORD RecvBytes;
				DWORD Flags = 0;
				WSARecv(acceptSocket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &PerIoData->Overlapped, NULL);
				//
				addClient(&saRemote, acceptSocket);
			} while (true);
			});
		acceptThread.detach();

	} while (false);


}

good luck to you

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值