为了具体地理解epoll的使用,本博文是用epoll实现了一个高并发聊天室服务器。
客户端使用pthread的多线程来实时读取所收到的信息,并且最初连接后输入的数据代表了该客户的名字;
服务器支持客户端查找当前所在线的客户name和id,支持客户端对指定的客户端私信,服务器采用边缘触发的模式;(其中需要注意的是若客户端想要私信另一个客户端,则通过输入@targetname@message的形式来私信,客户端输入cl来获取当前在线的client列表)
参考的代码:https://blog.csdn.net/sinat_35297665/article/details/79476256
参考了此博主对于epoll的详细使用。
chatroom.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1234
#define EPOLL_SIZE 5000
#define BUF_SIZE 0xFFFF
#define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"
#define SERVER_MESSAGE "ClientID %d: name:%s >> %s"
#define EXIT "EXIT"
#define CAUTION "There is only one int the char room!"
int setnonblocking(int sockfd);
void addfd( int epollfd, int fd, bool enable_et );
int sendBroadcastmessage(int clientfd);
using namespace std;
map<int,string> clt_map;//储存客户信息,socketfd和客户名字
int setnonblocking(int sockfd)//非阻塞模式设置
{
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)| O_NONBLOCK);
return 0;
}
void addfd( int epollfd, int fd, bool enable_et )//将fd加入到epoll中,并设置边缘触发模式
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN;
if( enable_et )
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
setnonblocking(fd);
printf("fd added to epoll!\n\n");
}
string private_msg(string& name){//私信时处理输入字符串
string msg="";
if(name[0]=='@'){
for(int i=1;i<name.length();i++){
if(name[i]=='@'){
msg+=name.substr(i+1);
name=name.substr(1,i-1);
break;
}
}
}
return msg;
}
int sendBroadcastmessage(int clientfd)//处理从客户端输入的信息
{
// buf[BUF_SIZE] receive new chat message
// message[BUF_SIZE] save format message
char buf[BUF_SIZE], message[BUF_SIZE];
bzero(buf, BUF_SIZE);
bzero(message, BUF_SIZE);
// receive message
printf("read from client(clientID = %d)\n", clientfd);
int len = recv(clientfd, buf, BUF_SIZE, 0);
if(len == 0) // len = 0 means the client closed connection
{
close(clientfd);
clt_map.erase(clientfd);
printf("ClientID = %d closed.\n now there are %d client in the char room\n", clientfd, clt_map.size());
}
if(clt_map.size() == 1) { // this means There is only one int the char room
send(clientfd, CAUTION, strlen(CAUTION), 0);
return len;
}
if(0==strcmp("cl",buf)){//获取当前在线的client列表
for(auto i:clt_map){
char t[BUF_SIZE]={};
sprintf(t,"Socket ID: %d -> Client Name: %s",i.first,(char*)i.second.c_str());
if( send(clientfd, t, BUF_SIZE, 0) < 0 ) { perror("error"); exit(-1);}
}
}
else //broadcast message
{
string name(buf);
string msg=private_msg(name);
if(name==clt_map[clientfd]){//输入的目标客户端名字就是该客户端的名字时
string nouser="Can't send message to yourself.";
if( send(clientfd, (char*)nouser.c_str(), nouser.length(), 0) < 0 ) { perror("error"); exit(-1);}
return len;
}
int tarfd=-1;
for(auto i:clt_map){//将客户端输入的私信信息输入到目标客户端上
if(i.second==name){
tarfd=i.first;
sprintf(message,"Message from %s: %s",(char*)clt_map[clientfd].c_str(),(char*)msg.c_str());
if( send(i.first, message, BUF_SIZE, 0) < 0 ) { perror("error"); exit(-1);}
return len;
}
}
if(tarfd==-1&&msg!=""){//当客户端直接进行群聊时进行广播
string nouser="No client named "+name;
if( send(clientfd, (char*)nouser.c_str(), nouser.length(), 0) < 0 ) { perror("error"); exit(-1);}
return len;
}
// format message to broadcast
sprintf(message, SERVER_MESSAGE, clientfd,(char*)clt_map[clientfd].c_str(), buf);
list<int>::iterator it;
for(auto it : clt_map) {
if(it.first != clientfd){
if( send(it.first, message, BUF_SIZE, 0) < 0 ) { perror("error"); exit(-1);}
}
}
}
return len;
}
server.cpp
#include "chatroom.h"
int main(int argc, char *argv[])
{
//服务器IP + port
struct sockaddr_in serverAddr;
serverAddr.sin_family = PF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
//创建监听socket
int listener = socket(PF_INET, SOCK_STREAM, 0);
if(listener < 0) { perror("listener"); exit(-1);}
printf("listen socket created \n");
//绑定地址
const int on=1;
setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("bind error");
exit(-1);
}
//监听
int ret = listen(listener, 5);
if(ret < 0) { perror("listen error"); exit(-1);}
printf("Start to listen: %s\n", SERVER_IP);
//在内核中创建事件表
int epfd = epoll_create(EPOLL_SIZE);
if(epfd < 0) { perror("epfd error"); exit(-1);}
printf("epoll created, epollfd = %d\n", epfd);
static struct epoll_event events[EPOLL_SIZE];
//往内核事件表里添加事件
addfd(epfd, listener, true);
//主循环
while(1)
{
//epoll_events_count表示就绪事件的数目
int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
if(epoll_events_count < 0) {
perror("epoll failure");
break;
}
//处理这epoll_events_count个就绪事件
for(int i = 0; i < epoll_events_count; ++i)
{
int sockfd = events[i].data.fd;
//新用户连接
if(sockfd == listener)
{
struct sockaddr_in client_address;
socklen_t client_addrLength = sizeof(struct sockaddr_in);
int clientfd = accept( listener, ( struct sockaddr* )&client_address, &client_addrLength );
char name[100]={};
recv(clientfd,name,sizeof(name),0);//接收客户端的名字信息
printf("\n%s connected, socketID : %d\n",
name,
clientfd);
addfd(epfd, clientfd, true);
// 服务端用list保存用户连接
clt_map[clientfd]=string(name);
printf("Now there are %d clients int the chat room\n", (int)clt_map.size());
// 服务端发送欢迎信息
char message[BUF_SIZE];
bzero(message, BUF_SIZE);
sprintf(message, SERVER_WELCOME, clientfd);
int ret = send(clientfd, message, BUF_SIZE, 0);
if(ret < 0) { perror("send error"); exit(-1); }
}
//处理用户发来的消息,并广播,使其他用户收到信息
else
{
int ret = sendBroadcastmessage(sockfd);
if(ret < 0) { perror("error");exit(-1); }
}
}
}
close(listener); //关闭socket
close(epfd); //关闭内核
return 0;
}
client.cpp
#include "../server/chatroom.h"
using namespace std;
void* receive(void* arg){
int *temp=((int*)arg);
int sock=*temp;
while(true){
char recvBuf[BUF_SIZE] = {};
int reLen = recv(sock, recvBuf, BUF_SIZE, 0);
cout<<endl<<recvBuf<<endl;
}
pthread_exit(NULL);
}
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
server.sin_addr.s_addr = inet_addr(SERVER_IP);
if(connect(sock,(struct sockaddr *)&server,sizeof(server)) < 0)
{
perror("connect");
return 2;
}
string name;
cout<<"Enter name:";
getline(cin,name);
write(sock,(char*)name.c_str(),name.length());
void* temp=&sock;
pthread_t th;
pthread_create(&th,NULL,receive,temp);
while(true){
string s;
getline(cin,s);
write(sock,(char*)s.c_str(),s.length());
}
close(sock);
return 0;
}