初識Linux下epoll網絡通信

#ifndef _WIN32

#include<unistd.h> //uni std
#include<arpa/inet.h>
#include<string.h>
#include<sys/epoll.h>

#endif

#define SOCKET int
#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR            (-1)

#include<stdio.h>
#include<vector>
#include<thread>
#include<algorithm>

std::vector<SOCKET> g_clients;

bool g_bRun = true;
void cmdThread()
{
	while (true)
	{
		char cmdBuf[256] = {};
		scanf("%s", cmdBuf);
		if (0 == strcmp(cmdBuf, "exit"))
		{
			g_bRun = false;
			printf("退出cmdThread線程\n");
			break;
		}
		else {
			printf("不支持的命令。\n");
		}
	}
}

int cell_epoll_ctl(int epfd, int op, SOCKET sockfd, uint32_t events)
{
	epoll_event ev;
	//事件類型
	ev.events = events;
	//事件關聯的socket描述符對象
	ev.data.fd = sockfd;
	//向epoll對象註冊需要管理、監聽的Socket文件描述符
	//並且說明關心的事件
	//返回0代表操作成功,返回負值代表失敗 -1
	if (epoll_ctl(epfd, op, sockfd, &ev) == -1)
	{
		//if(events & EPOLLIN)
		// do something
		printf("error, epoll_ctl(%d,%d,%d)\n", epfd, op, sockfd);
	}
}

//緩衝區
char g_szBUff[4096] = {};
int g_nLen = 0;
int readData(SOCKET cSock)
{
	g_nLen = (int)recv(cSock, g_szBUff, 4096, 0);
	return g_nLen;
}

int WriteData(SOCKET cSock)
{
	if (g_nLen > 0)
	{
		int nLen = (int)send(cSock, g_szBUff, g_nLen, 0);
		g_nLen = 0;
		return nLen;
	}
	return 1;
}

int clientLeave(SOCKET cSock)
{
	close(cSock);
	printf("客戶端<socket=%d>已退出\n", cSock);
	auto itr = std::find(g_clients.begin(), g_clients.end(), cSock);
	g_clients.erase(itr);
}

int main()
{
	//啓動cmd線程
	std::thread t1(cmdThread);
	t1.detach();
	/////////////////////////////////
	//-- 用Socket API建立簡易TCP服務端
	// 1 建立一個socket 套接字
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	// 2.1 bind 綁定用於接受客戶端連接的網絡端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);//host to net unsigned short
	_sin.sin_addr.s_addr = INADDR_ANY;
	// 2.2
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		printf("錯誤,綁定網絡端口失敗...\n");
	}
	else {
		printf("綁定網絡端口成功...\n");
	}

	// 3 listen 監聽網絡端口
	if (SOCKET_ERROR == listen(_sock, 64))
	{
		printf("錯誤,監聽網絡端口失敗...\n");
	}
	else {
		printf("監聽網絡端口成功...\n");
	}
	const int maxClient = 60000;
	//linux 2.6.8 後size就沒有作用了
	//由epoll動態管理,理論最大值爲filemax
	//通過cat /proc/sys/fs/file-max來查詢
	int epfd = epoll_create(maxClient);

	//向epoll對象註冊需要管理、監聽的Socket文件描述符
	cell_epoll_ctl(epfd, EPOLL_CTL_ADD, _sock, EPOLLIN);
	//
	int msgCount = 0;
	int cCount = 0;
	//用於接收檢測到的網絡事件的數組
	epoll_event events[maxClient] = {};
	while (g_bRun)
	{
		//epfd epoll對象的描述符
		//events 用於接收檢測到的網絡事件的數組
		//maxevents 接收數組的大小,能夠接收的事件數量
		//timeout
		//		t=-1 直到有事件發生才返回
		//		t= 0 立即返回 std::map
		//		t> 0 如果沒有事件那麼等待t毫秒後返回。
		int n = epoll_wait(epfd, events, maxClient, 1);
		if (n < 0)
		{
			printf("error,epoll_wait ret=%d\n", n);
			break;
		}

		for (int i = 0; i < n; i++)
		{
			//當服務端socket發生事件時,表示有新客戶端連接
			if (events[i].data.fd == _sock)
			{
				if (events[i].events & EPOLLIN)
				{
					// 4 accept 等待接受客戶端連接
					sockaddr_in clientAddr = {};
					int nAddrLen = sizeof(sockaddr_in);
					SOCKET _cSock = INVALID_SOCKET;
					_cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen);
					cCount++;
					if (INVALID_SOCKET == _cSock)
					{
						printf("錯誤,接受到無效客戶端SOCKET %d 錯誤代碼是%d,錯誤信息是’%s’...\n", cCount, errno, strerror(errno));
					}
					else
					{
						g_clients.push_back(_cSock);
						cell_epoll_ctl(epfd, EPOLL_CTL_ADD, _cSock, EPOLLIN);
						printf("新客戶端加入:socket = %d,IP = %s  %d\n", (int)_cSock, inet_ntoa(clientAddr.sin_addr), cCount);
					}
					continue;
				}
				// printf("other %d...\n",cCount);
			}
			//當前socket有數據可讀,也有可能時發生錯誤
			if (events[i].events & EPOLLIN)
			{
				//printf("EPOLLIN|%d\n",++msgCount);
				auto cSock = events[i].data.fd;
				int ret = readData(cSock);
				if (ret <= 0)
				{
					clientLeave(cSock);
				}
				else {
					//printf("收到客戶端數據:id = %d, socket = %d, len = %d\n",msgCount, cSock, ret);
				}
				//cell_epoll_ctl(epfd, EPOLL_CTL_MOD, cSock, EPOLLOUT);
			}
			//當前socket是否可寫數據
			if (events[i].events & EPOLLOUT)
			{
				printf("EPOLLOUT|%d\n", msgCount);
				auto cSock = events[i].data.fd;
				int ret = WriteData(cSock);
				if (ret <= 0)
				{
					clientLeave(cSock);
				}
				if (msgCount < 5)
					cell_epoll_ctl(epfd, EPOLL_CTL_MOD, cSock, EPOLLIN);
				else
					cell_epoll_ctl(epfd, EPOLL_CTL_DEL, cSock, 0);
			}
			/*
			if(events[i].events & EPOLLERR)
			{
			auto cSock = events[i].data.fd;
			printf("EPOLLERR:id = %d, socket = %d\n",msgCount, cSock);
			}
			if(events[i].events & EPOLLHUP)
			{
			auto cSock = events[i].data.fd;
			printf("EPOLLHUP:id = %d, socket = %d\n",msgCount, cSock);
			}
			*/
		}
	}

	for (auto client : g_clients)
	{
		close(client);
	}
	close(epfd);
	close(_sock);
	printf("已退出。\n");
	return 0;
}


/*
cell_epoll_ctl(epfd, EPOLL_CTL_ADD, cSock, EPOLLOUT|EPOLLIN);
c++ 函數傳參 按位與 按位或
C/C++函數參數使用位或運算
https://www.zhihu.com/question/23814540/answer/25745880
https://blog.csdn.net/sinat_29003361/article/details/52713749
*/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章