Linux下编写C++服务器(EPOLL高并发Socket服务器)
概述之前写的并发服务器是accept到客户端的句柄后,就开启一个线程,让子线程处理这个客户端的通讯,客户端多了会造成高内存,因此限制了高并发,同时非阻塞也可能会造成高CPU使用率。Linux的EPOLL机制解决了上面的问题,EPOLL使用事件触发的机制,只有事件变化时才会处理事件,可以从事件中获取句柄、事件类型信息,进行处理。参考文献:浅析epoll的水平触发和边缘触发,以及边缘触发为什么...
·
概述
之前写的并发服务器是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和内存的占用率:
更多推荐
已为社区贡献1条内容
所有评论(0)