epoll高并发聊天室(C++,linux,socket,pthread)

为了具体地理解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;
}

你可能感兴趣的:(epoll高并发聊天室(C++,linux,socket,pthread))