C\C++语言利用epoll实现高并发聊天室Demo

2019-11-19

00:19:02

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

Makefile:

all:server client
server:server.cpp
    g++ $^ -o [email protected]
client:client.cpp
    g++ $^ -o [email protected]
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!\n\n");
}

//发送广播
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)\n", clientfd);
    int len = recv(clientfd, buf, BUF_SIZE, 0);

    if (0 == len) {
        close(clientfd);
        clients_list.remove(clientfd);
        printf("ClientID = %d closed.\n now there are %d client in the char room\n",
        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\n");

        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\n", clientfd);
                        close(clientfd);
                        isClientwork = 0;
                    } else printf("%s\n", 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 \n");

    //地址复用
    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\n", SERVER_IP);

    //在内核中创建事件表
    int epfd = epoll_create(EPOLL_SIZE);
    if (epfd < 0) {
        error("epfd error");
    }
    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;
        }

        printf("epoll_events_count = %d\n", 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 \n",
                       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\n", clientfd);
                printf("Now there are %d clients int the chat room\n", (int) clients_list.size());

                // 服务端发送欢迎信息
                printf("welcome message\n");
                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;
}

原文地址:https://www.cnblogs.com/JasonPeng1/p/11886544.html

时间: 2024-07-31 14:46:42

C\C++语言利用epoll实现高并发聊天室Demo的相关文章

JVM 利用 VisualVM 对高并发项目进行性能分析(转)

出处:  深入理解 Java 虚拟机-如何利用 VisualVM 对高并发项目进行性能分析 原文地址:https://www.cnblogs.com/myseries/p/12084444.html

深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析

Java虚拟机深入理解系列全部文章更新中... 深入理解Java虚拟机-Java内存区域透彻分析 深入理解Java虚拟机-常用vm参数分析 深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲 深入理解Java虚拟机-如何利用JDK自带的命令行工具监控上百万的高并发的虚拟机性能 深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析 深入理解Java虚拟机-你了解GC算法原理吗 前面在学习JVM的知识的时候,一般都需要利用相关参数进行分析,而分析一般

基于EPOLL模型的局域网聊天室和Echo服务器

一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说select/poll的缺点,以体现epoll的优点. select: (1)可监听的socket受到限制,在32位的系统中,默认最大值为1024. (2)采用轮询方式,当要监听的sock数量很大时,效率低. (3)随着要监听socket数据的增加,要维护一个存放大量fd的数据结构,系统开销太大. pol

Linux epoll 笔记(高并发事件处理机制)

wiki: Epoll优点: Epoll工作流程: Epoll实现机制: epollevent; Epoll源码分析: Epoll接口: epoll_create; epoll_ctl; epoll_close; Epoll工作方式: LT(level-triggered); ET(edge-triggered); Epoll应用模式; Epoll优点: <1>支持一个进程打开大数目的socket描述符(FD) select一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值

C语言使用多进程实现高并发tcp服务器

多进程并发服务器的流程 socket; 创建监听套接字 bind; 绑定地址结构 listen(); 设置监听上限 accept();进行循环监听 fork();接收到客户端请求创建新的进程 close(); 与客户端通讯的套接字关闭 实现C语言并发服务器 #include <stdio.h> #include <apra/inet.h> #include <errno.h> #include <sys/socket.h> #include <unis

python,利用socketserver模块实现并发聊天

利用socketserver模块很容易实现并发功能,下面的server.py和client.py程序实现了这一功能. #server.pyimport socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): while True: conn = self.request while True: data = conn.recv(1024) if str(data,'utf8') == 'q':

Linux编程之Epoll高并发

网络上所有资料都说epoll是高并发.单线程.IO重叠服用的首选架构,比select和poll性能都要好,特别是在有大量不活跃连接的情况下.具体原理就不阐述了,下面说说使用. 具有有三个函数: #include <sys/epoll.h> 1.int epoll_create ( int size ); size是epoll要监视的fd的规模. 2.int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event ); (1

https大势已来?看腾讯专家如何在高并发压测中支持https

WeTest 导读 用epoll编写一个高并发网络程序是很常见的任务,但在epoll中加入ssl层的支持则是一个不常见的场景.腾讯WeTest服务器压力测产品,在用户反馈中收到了不少支持https协议的请求.基于此,本文介绍了在基于epoll的高并发机器人框架中加入openssl,实现对https支持时的基本实现思路.   一.背景 2014年,谷歌在其官方博客中发布公告称,为了打造更安全的互联网环境,谷歌搜索引擎将尝试把"是否使用安全加密"(HTTPS)作为搜索排名算法中的一个参考因

互联网架构是怎么实现“高并发”的

一.什么是高并发 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求. 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等. 响应时间:系统对请求做出响应的时间.例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间. 吞吐量:单位时间内处理的请求数量. QPS:每秒响应