zoukankan      html  css  js  c++  java
  • CC++语言利用epoll实现高并发聊天室Demo

    2019-11-19

    00:19:02

    参考大佬:https://github.com/LinHaoo/chat

     Makefile:

    all:server client
    server:server.cpp
        g++ $^ -o $@
    client:client.cpp
        g++ $^ -o $@
    clean:
        rm server client

    utility.h

    #ifndef CHAT_UTILITY_H
    #define CHAT_UTILITY_H
    
    #include <iostream>
    #include <list>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/epoll.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    using namespace std;
    
    //clients_list save all the clients's socket
    list<int> clients_list;
    
    /***** macro defintion *****/
    //server ip
    #define SERVER_IP "127.0.0.1"
    
    //server port
    #define SERVER_PORT 8888
    
    //epoll size
    #define EPOLL_SIZE 5000
    
    //message buffer size
    #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 say >> %s"
    
    //exit
    #define EXIT "EXIT"
    
    #define CAUTION "There is only ont int the char root!"
    
    /****** some function *****/
    /**
     *设置非阻塞
     */
    int setnonblockint(int sockfd) {
        fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK);
        return 0;
    }
    
    /**
     * 将文件描述符 fd 添加到 epollfd 标示的内核事件表中,
     * 并注册 EPOLLIN 和 EPOOLET 事件,
     * EPOLLIN 是数据可读事件;EPOOLET 表明是 ET 工作方式。
     * 最后将文件描述符设置非阻塞方式
     * @param epollfd:epoll句柄
     * @param fd:文件描述符
     * @param enable_et:enable_et = true,
     * 是否采用epoll的ET(边缘触发)工作方式;否则采用LT(水平触发)工作方式
     */
    void addfd(int epollfd, int fd, bool enable_et) {
        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);
        setnonblockint(fd);
        printf("fd added to epoll!
    
    ");
    }
    
    //发送广播
    int sendBroadcastmessage(int clientfd) {
        char buf[BUF_SIZE];
        char message[BUF_SIZE];
        bzero(buf, BUF_SIZE);
        bzero(buf, BUF_SIZE);
    
        printf("read from client(clientID = %d)
    ", clientfd);
        int len = recv(clientfd, buf, BUF_SIZE, 0);
    
        if (0 == len) {
            close(clientfd);
            clients_list.remove(clientfd);
            printf("ClientID = %d closed.
     now there are %d client in the char room
    ",
            clientfd, (int)clients_list.size());
        } else {
            if (1 == clients_list.size()) {
                send(clientfd, CAUTION, strlen(CAUTION), 0);
                return 0;
            }
            sprintf(message, SERVER_MESSAGE, clientfd, buf);
            list<int>::iterator it;
            for (it = clients_list.begin(); it != clients_list.end(); ++it) {
                if (*it != clientfd) {
                    if (send(*it, message, BUF_SIZE, 0) < 0) {
                        perror("error");
                        exit(-1);
                    }
                }
            }
        }
        return len;
    }
    
    #endif //CHAT_UTILITY_H

    client.cpp

    #include "utility.h"
    
    #define error(msg) 
        do {perror(msg); exit(EXIT_FAILURE); } while (0)
    
    int main(int argc, char *argv[]) {
        /**
         * TCP 客户端通信
         * 1.创建套接字(socket)
         * 2.使用 connect() 建立到达服务器的连接(connect)
         * 3.客户端进行通信(使用 write()/send() 或 send()/recv() )
         * 4.使用 close() 关闭客户连接
         */
    
        /**
         * 1:创建套接字socket
         * param1:指定地址族为IPv4;param2:指定传输协议为流式套接字;param3:指定传输协议为TCP,可设为0,由系统推导
         */
        int clientfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (clientfd < 0) { error("socket error"); }
    
        // 填充sockadd结构,指定ip与端口
        struct sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(SERVER_PORT);
        serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    
        // 2:连接服务端
        if (connect(clientfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
            error("connect error");
        }
    
        // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
        int pipefd[2];
        if (pipe(pipefd) < 0) { error("pipe error"); }
    
        /**
         * epoll使用
         * 1:调用 epoll_create 函数在 Linux 内核中创建一个事件表;
         * 2:然后将文件描述符添加到所创建的事件表中 (epoll_ctl);
         * 3:在主循环中,调用 epoll_wait 等待返回就绪的文件描述符集合;
         * 4:分别处理就绪的事件集合
         */
        // 创建epoll
        int epfd = epoll_create(EPOLL_SIZE);
        if (epfd < 0) { error("epfd error"); }
    
        static struct epoll_event events[2];
        //将sock和管道读端描述符都添加到内核事件表中
        addfd(epfd, clientfd, true);
        addfd(epfd, pipefd[0], true);
    
        // 表示客户端是否正常工作
        bool isClientwork = true;
    
        // 聊天信息缓冲区
        char message[BUF_SIZE];
    
        // Fork
        int pid = fork();
        if (pid < 0) {
            error("fork error");
        } else if (pid == 0) {      // 子进程
            //子进程负责写入管道,因此先关闭读端
            close(pipefd[0]);
            printf("Please input 'exit' to exit the chat room
    ");
    
            while (isClientwork) {
                bzero(&message, BUF_SIZE);
                fgets(message, BUF_SIZE, stdin);
    
                // 客户输出exit,退出
                if (strncasecmp(message, EXIT, strlen(EXIT)) == 0) {
                    isClientwork = 0;
                } else {    // 子进程将信息写入管道
                    if (write(pipefd[1], message, strlen(message) - 1) < 0) {
                        error("fork error");
                    }
                }
            }
        } else { //pid > 0 父进程
            //父进程负责读管道数据,因此先关闭写端
            close(pipefd[1]);
    
            // 主循环(epoll_wait)
            while (isClientwork) {
                // 等待事件的产生,函数返回需要处理的事件数目
                int epoll_events_count = epoll_wait(epfd, events, 2, -1);
                // 处理就绪事件
                for (int i = 0; i < epoll_events_count; ++i) {
                    bzero(&message, BUF_SIZE);
    
                    //服务端发来消息
                    if (events[i].data.fd == clientfd) {
                        //接受服务端消息
                        int ret = recv(clientfd, message, BUF_SIZE, 0);
    
                        // ret= 0 服务端关闭
                        if (ret == 0) {
                            printf("Server closed connection: %d
    ", clientfd);
                            close(clientfd);
                            isClientwork = 0;
                        } else printf("%s
    ", message);
                    }
                        //子进程写入事件发生,父进程处理并发送服务端
                    else {
                        //父进程从管道中读取数据
                        int ret = read(events[i].data.fd, message, BUF_SIZE);
    
                        // ret = 0
                        if (ret == 0) {
                            isClientwork = 0;
                        } else {   // 将信息发送给服务端
                            send(clientfd, message, BUF_SIZE, 0);
                        }
                    }
                }//for
            }//while
        }
    
        if (pid) {
            //关闭父进程和sock
            close(pipefd[0]);
            close(clientfd);
        } else {
            //关闭子进程
            close(pipefd[1]);
        }
        return 0;
    }

    server.cpp

    #include "utility.h"
    
    #define error(msg) 
        do {perror(msg); exit(EXIT_FAILURE); } while (0)
    
    int main(int argc, char *argv[]) {
        /**
         * TCP服务端通信
         * 1:使用 socket()创建 TCP 套接字(socket)
         * 2:将创建的套接字绑定到一个本地地址和端口上(bind)
         * 3:将套接字设为监听模式,准备接收客户端请求(listen)
         * 4:等待客户请求到来: 当请求到来后,接受连接请求,返回一个对应于此次连接的新的套接字(accept)
         * 5:用 accept 返回的套接字和客户端进行通信(使用write()/send()或send()/recv())
         * 6:返回,等待另一个客户请求
         * 7:关闭套接字
         */
    
        /**
         * 1:创建套接字socket
         * param1:指定地址族为IPv4;param2:指定传输协议为流式套接字;param3:指定传输协议为TCP,可设为0,由系统推导
         */
        int listener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (listener < 0) {
            error("socket error");
        }
        printf("listen socket created 
    ");
    
        //地址复用
        int on = 1;
        if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
            error("setsockopt");
        }
    
        struct sockaddr_in serverAddr;
        serverAddr.sin_family = PF_INET;
        serverAddr.sin_port = htons(SERVER_PORT);
        serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
        //绑定地址
        if (bind(listener, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
            error("bind error");
        }
    
        //监听
        if (listen(listener, SOMAXCONN) < 0) {
            error("listen error");
        }
    
        printf("Start to listen: %s
    ", SERVER_IP);
    
        //在内核中创建事件表
        int epfd = epoll_create(EPOLL_SIZE);
        if (epfd < 0) {
            error("epfd error");
        }
        printf("epoll created, epollfd = %d
    ", 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;
            }
    
            printf("epoll_events_count = %d
    ", epoll_events_count);
            //处理这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);
    
                    printf("client connection from: %s : % d(IP : port), clientfd = %d 
    ",
                           inet_ntoa(client_address.sin_addr),
                           ntohs(client_address.sin_port),
                           clientfd);
    
                    addfd(epfd, clientfd, true);
    
                    // 服务端用list保存用户连接
                    clients_list.push_back(clientfd);
                    printf("Add new clientfd = %d to epoll
    ", clientfd);
                    printf("Now there are %d clients int the chat room
    ", (int) clients_list.size());
    
                    // 服务端发送欢迎信息
                    printf("welcome message
    ");
                    char message[BUF_SIZE];
                    bzero(message, BUF_SIZE);
                    sprintf(message, SERVER_WELCOME, clientfd);
                    int ret = send(clientfd, message, BUF_SIZE, 0);
                    if (ret < 0) {
                        error("send error");
                    }
                } else {           //处理用户发来的消息,并广播,使其他用户收到信息
                    int ret = sendBroadcastmessage(sockfd);
                    if (ret < 0) {
                        error("error");
                    }
                }
            }
        }
        close(listener); //关闭socket
        close(epfd);    //关闭内核
        return 0;
    }

     

  • 相关阅读:
    Windows Socket编程简介
    IIS7.0 Appcmd 命令详解
    VC显示网页验证码、模拟CSDN网页登录
    c++对象初始化中 ZeroMemory、memset、直接赋0的区别
    在MFC程序中显示 JPG/GIF图像
    开始学习WTL——(1)关于STL/ATL/WTL之间的区别
    可编辑子项的CListCtrl类
    VC添加自定义消息
    VC调用JavaScript函数--处理QQ网页登录密码加密(空间、农场、WEB QQ等)
    VC创建定时关闭的MessageBox
  • 原文地址:https://www.cnblogs.com/JasonPeng1/p/11886544.html
Copyright © 2011-2022 走看看