Linux下编写C++服务器(EPOLL高并发Socket服务器)

概述

之前写的并发服务器是accept到客户端的句柄后,就开启一个线程,让子线程处理这个客户端的通讯,客户端多了会造成高内存,因此限制了高并发,同时非阻塞也可能会造成高CPU使用率。
Linux的EPOLL机制解决了上面的问题,EPOLL使用事件触发的机制,只有事件变化时才会处理事件,可以从事件中获取句柄、事件类型信息,进行处理。

参考文献:

浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO
linux 高并发事件触发处理 — epoll

编码

创建名为epolltest的Makefile项目,目录如下:
在这里插入图片描述
myepoll.h

#pragma once
#include <sys/socket.h>  
#include <sys/epoll.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <stdio.h>  
#include <string.h>
#include <errno.h>  
#include <iostream>  

#define MAXBUFFSIZE	1024
#define MAXEVENTS	500
#define FDSIZE	1000

class myepoll
{
public:
	myepoll();
	~myepoll();

private:
	int socketfd;
	struct sockaddr_in servaddr;
	int epollfd;
	char err_msg[256];
public:
	/*
	port:socket端口
	isblock:是否阻塞
	*/
	bool start(int port, bool isblock = false);

	int get_socketfd();

	bool do_epoll();

	void add_event(int fd, int state);
	void del_event(int fd, int state);
	void mod_event(int fd, int state);

	void handle_events(struct epoll_event *events, int num, char* buf, int &buflen);

	bool handle_accept();

	bool do_read(int fd, char* buf, int &buflen);

	bool do_write(int fd, char* buf, int buflen);

	char* get_errmsg();
};

myepoll.cpp

#include "myepoll.h"



myepoll::myepoll()
{
	socketfd = 0;
	memset(&servaddr, 0, sizeof(servaddr));
}


myepoll::~myepoll()
{
}

bool myepoll::start(int port, bool isblock)
{
	if ((socketfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
	servaddr.sin_port = htons(port);
	if (!isblock) {
		int flags = fcntl(socketfd, F_GETFL, 0);
		fcntl(socketfd, F_SETFL, flags | O_NONBLOCK);//设置为非阻塞
	}

	//设置重用地址,防止Address already in use
	int on = 1;
	if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1){
		snprintf(err_msg, sizeof(err_msg), "set reuse addr error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}

	//将本地地址绑定到所创建的套接字上  
	if (bind(socketfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
		snprintf(err_msg, sizeof(err_msg), "bind socket error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	//开始监听是否有客户端连接  
	if (listen(socketfd, 5) == -1) {
		snprintf(err_msg, sizeof(err_msg), "listen socket error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	std::cout << "create socket success\n";
	return true;
}

int myepoll::get_socketfd()
{
	return socketfd;
}

bool myepoll::do_epoll()
{
	struct epoll_event events[MAXEVENTS];
	int ret;
	char buf[MAXBUFFSIZE] = { 0 };
	int buflen = 0;
	//创建一个描述符
	if ((epollfd = epoll_create(FDSIZE)) == -1){
		snprintf(err_msg, sizeof(err_msg), "listen socket error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	//添加监听描述符事件
	add_event(socketfd, EPOLLIN);
	while (true) {
		//获取已经准备好的描述符事件
		/*
		如果要设置read超时
		1,设置socket非阻塞
		2,设置epoll_wait超时1秒
		3,每次进入epoll_wait之前,遍历在线用户列表,踢出长时间没有请求的用户.

		PS:每次用户发来数据, read之后更新该用户last_request时间, 为了上面的步骤3而做
		*/
		ret = epoll_wait(epollfd, events, MAXEVENTS, -1);
		handle_events(events, ret, buf, buflen);
	}
	close(epollfd);
}

void myepoll::add_event(int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	/*
	//如果是ET模式,设置EPOLLET
	ev.events |= EPOLLET;
	//设置是否阻塞
	int flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
	*/
	epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

void myepoll::del_event(int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

void myepoll::mod_event(int fd, int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

void myepoll::handle_events(epoll_event * events, int num, char * buf, int &buflen)
{
	int i;
	int fd;
	//进行选好遍历
	for (i = 0; i < num; i++) {
		fd = events[i].data.fd;
		//根据描述符的类型和事件类型进行处理
		if ((fd == socketfd) && (events[i].events& EPOLLIN))
			handle_accept();
		else if (events[i].events & EPOLLIN)
			do_read(fd, buf, buflen);
		else if (events[i].events & EPOLLOUT)
			do_write(fd, buf, buflen);
		else
			close(fd);
	}
}

bool myepoll::handle_accept()
{
	int clifd;
	struct sockaddr_in cliaddr;
	socklen_t cliaddrlen = sizeof(cliaddr);
	clifd = accept(socketfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
	if (clifd == -1) {
		snprintf(err_msg, sizeof(err_msg), "listen socket error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	else {
		char msg[128] = { 0 };
		//获取端口错误
		sprintf(msg,"accept a new client:%s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
		std::cout << msg;
		//添加一个客户描述符和事件
		add_event(clifd, EPOLLIN);
	}
}

bool myepoll::do_read(int fd, char * buf, int &buflen)
{
	buflen = read(fd, buf, MAXBUFFSIZE);
	if (buflen == -1) {
		snprintf(err_msg, sizeof(err_msg), "read error: %s(errno: %d)\n", strerror(errno), errno);
		close(fd);
		del_event(fd, EPOLLIN);
		return false;
	}
	else if (buflen == 0) {
		close(fd);
		std::cout << "client close.\n";
		del_event(fd, EPOLLIN);
		return true;
	}
	else {
		char msg[MAXBUFFSIZE] = { 0 };
		sprintf(msg, "read message is:%s\n", buf);
		std::cout << msg;
		//修改描述符对应的事件,由读改为写
		mod_event(fd, EPOLLOUT);
	}
	return true;
}

bool myepoll::do_write(int fd, char * buf, int buflen)
{
	int nwrite;
	nwrite = write(fd, buf, buflen);
	if (nwrite == -1)
	{
		snprintf(err_msg, sizeof(err_msg), "write error: %s(errno: %d)\n", strerror(errno), errno);
		close(fd);
		del_event(fd, EPOLLOUT);
		return false;
	}
	else{ 
		char msg[MAXBUFFSIZE] = { 0 };
		sprintf(msg, "write message is:%s\n", buf);
		std::cout << msg;
		mod_event(fd, EPOLLIN);
	}
		
	memset(buf, 0, MAXBUFFSIZE);
	return true;
}

char * myepoll::get_errmsg()
{
	return err_msg;
}

main.cpp

#pragma once
#pragma execution_character_set("utf-8")

#include <iostream>  
#include "myepoll.h"
using namespace std;

int main(int argc, char **argv)
{
	myepoll myepoll;
	if(!myepoll.start(5000,false)){
		cout << myepoll.get_errmsg();
	}
	myepoll.do_epoll();
	return 0;
}

Makefile

CC1=g++

build:main.o myepoll.o
	$(CC1) -gdwarf-2 -o epolltest main.o myepoll.o

main.o:main.cpp myepoll.h
	$(CC1) -gdwarf-2 -c main.cpp

myepoll.o:myepoll.cpp myepoll.h
	$(CC1) -gdwarf-2 -c myepoll.cpp

clean:
	rm *.o epolltest

展示

输出界面,程序逻辑为接收到什么内容返回客户端:
在这里插入图片描述
网上下载的调试助手界面:
在这里插入图片描述
可以多开几个客户端或者写个客户端测试:
在这里插入图片描述
可以在终端查看CPU和内存的占用率:
在这里插入图片描述

发布了14 篇原创文章 · 获赞 6 · 访问量 3124

猜你喜欢

转载自blog.csdn.net/qq_39554698/article/details/98674999