多人聊天室 (epoll - Linux网络编程)

文章目录

    • 零、效果展示
    • 一、服务器代码
    • 二、客户端代码
    • 三、知识点
      • 1.bind()
    • 四、改进方向
    • 五、跟练视频

零、效果展示

一个服务器作为中转站,多个客户端之间可以相互通信。至少需要启动两个客户端。

在这里插入图片描述


三个客户端互相通信
在这里插入图片描述


一、服务器代码

chatServer.cpp

#include <cstdio>
#include <iostream>
#include <string>
#include <sys/epoll.h>  //epoll的头文件
#include <sys/socket.h> //socket的头文件
#include <unistd.h>     //close()的头文件
#include <netinet/in.h> //包含结构体 sockaddr_in
#include <map>          //保存客户端信息
#include <arpa/inet.h>  //提供inet_ntoa函数
using namespace std;

const int MAX_CONNECT = 5; //全局静态变量,允许的最大连接数

struct Client{
    int sockfd; //socket file descriptor 套接字文件描述符 
    string username;
};

int main(){
    //创建一个epoll实例
    int epld = epoll_create(1);
    if(epld < 0){
        perror("epoll create error");
        return -1;
    }

    //创建监听的socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0){ //若socket创建失败,则返回-1
        perror("socket error");
        return -1;
    }

    //绑定本地ip和端口
    struct sockaddr_in addr;  //结构体声明,头文件是<netinet/in.h>
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port  = htons(9999);

    int ret = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0){
        printf("bind error\n");
        cout << "该端口号已被占用,请检查服务器是否已经启动。" << endl;
        return -1;
    }
    
    cout << "服务器中转站已启动,请加入客户端。" << endl;

    //监听客户端
    ret = listen(sockfd,1024);
    if(ret < 0){
        printf("listen error\n");
        return -1;
    }

    //将监听的socket加入epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;

    ret = epoll_ctl(epld,EPOLL_CTL_ADD,sockfd,&ev); //防御性编程,方便出bug时快速定位问题
    if(ret < 0){
        printf("epoll_ctl error\n");
        return -1;
    }
    
    //保存客户端信息
    map<int,Client> clients;
    int clientCount = 0; //添加一个客户端计数器

    //循环监听
    while(true){
        struct epoll_event evs[MAX_CONNECT];
        int n = epoll_wait(epld,evs,MAX_CONNECT,-1);
        if(n < 0){
            printf("epoll_wait error\n");
            break;
        }

        for(int i = 0; i < n; i ++){
            int fd = evs[i].data.fd;
            //如果是监听的fd收到消息,则表示有客户端进行连接了
            if(fd == sockfd){
                struct sockaddr_in client_addr;
                socklen_t client_addr_len = sizeof(client_addr);
                int client_sockfd = accept(sockfd, (struct sockaddr*) & client_addr, &client_addr_len);
                if(client_sockfd < 0){
                    printf("accept error,连接出错\n");
                    continue;
                }
                //将客户端的socket加入epoll
                struct epoll_event ev_client;
                ev_client.events = EPOLLIN; //检测客户端有没有消息过来
                ev_client.data.fd = client_sockfd;
                ret = epoll_ctl(epld, EPOLL_CTL_ADD,client_sockfd,&ev_client);
                if(ret < 0){
                    printf("epoll_ctl error\n");
                    break;
                } //iner_ntoa() 将客户端的IP地址从网络字节顺序转换为点分十进制字符串
                clientCount++; //有新的客户端加入时,增加计数器
                printf("客户端%d已连接: IP地址为 %s\n", clientCount, inet_ntoa(client_addr.sin_addr));
                
                //保存该客户端信息
                Client client;
                client.sockfd = client_sockfd;
                client.username = "";
                clients[client_sockfd] = client;
            }else{
                char buffer[1024];
                int n = read(fd, buffer, 1024);
                if(n < 0){
                    break; //处理错误
                }else if(n == 0){
                    //客户端断开连接
                    close(fd);
                    epoll_ctl(epld,EPOLL_CTL_DEL, fd ,0);
                    clients.erase(fd);
                }else{ // n > 0
                    string msg(buffer,n);

                    //如果该客户端username为空,说明该消息是这个客户端的用户名
                    if(clients[fd].username == ""){
                        clients[fd].username = msg;
                    }else{
                        string name = clients[fd].username;

                        //把消息发给其他所有客户端
                        for(auto &c:clients){
                            if(c.first != fd){
                                string full_message = '[' + name + ']' + ':' + msg;
                                write(c.first, full_message.c_str(), full_message.length());
                                //write(c.first,('[' + name + ']' + ":" + msg).c_str(),msg.size() + name.size() + 4);
                            }
                        }
                    }
                }
            }
        }
    }
    //关闭epoll实例
    close(epld);
    close(sockfd);

    return 0;
}

二、客户端代码

client.cpp

注意g++编译时要加 -pthread

#include <cstdio>
#include <iostream> 
#include <cstring>       //memset()的头文件
#include <sys/socket.h>  //socket(),connect()等函数的头文件
#include <netinet/in.h>  //sockaddr_in的头文件
#include <arpa/inet.h>   //inet_pton()函数的头文件
#include <unistd.h>      //close()函数的头文件
#include <pthread.h>     //pthread创建线程和管理线程的头文件
using namespace std;

#define BUF_SIZE 1024
char szMsg[BUF_SIZE];

//发送消息
void* SendMsg(void *arg){
    int sock = *((int*)arg);
    while(1){
        //scanf("%s",szMsg);
        fgets(szMsg,BUF_SIZE,stdin); //使用fgets代替scanf
        if(szMsg[strlen(szMsg) - 1] == '\n'){
            szMsg[strlen(szMsg)- 1] = '\0'; //去除换行符
        }
        
        if(!strcmp(szMsg,"QUIT\n") || !strcmp(szMsg,"quit\n")){
            close(sock);
            exit(0);
        }
        send(sock, szMsg, strlen(szMsg), 0);
    }
    return nullptr;
}

//接收消息
void* RecvMsg(void * arg){
    int sock = *((int*)arg);
    char msg[BUF_SIZE];
    while(1){
        int len = recv(sock, msg, sizeof(msg)-1, 0);
        if(len == -1){
            cout << "系统挂了" << endl;
            return (void*)-1;
        }
        msg[len] = '\0';
        printf("%s\n",msg);
    }
    return nullptr;
}

int main()
{
    //创建socket
    int hSock;
    hSock = socket(AF_INET, SOCK_STREAM, 0);
    if(hSock < 0){
        perror("socket creation failed");
        return -1;
    }

    //绑定端口
    sockaddr_in servAdr;
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_port = htons(9999);
    if(inet_pton(AF_INET, "172.16.51.88", &servAdr.sin_addr) <= 0){
        perror("Invalid address");
        return -1;
    }
    
    //连接到服务器
    if(connect(hSock, (struct sockaddr*)&servAdr, sizeof(servAdr)) < 0){
        perror("连接服务器失败");
        cout << "请检查是否已启动服务器。" << endl;
        return -1;
    }else{
        printf("已连接到服务器,IP地址:%s,端口:%d\n", inet_ntoa(servAdr.sin_addr), ntohs(servAdr.sin_port));
        printf("欢迎来到私人聊天室,请输入你的聊天用户名:");
    }
    
    //创建线程
    pthread_t sendThread,recvThread;
    if(pthread_create(&sendThread, NULL, SendMsg, (void*)&hSock)){
        perror("创建发送消息线程失败");
        return -1;
    }
    if(pthread_create(&recvThread, NULL, RecvMsg, (void*)&hSock)){
        perror("创建接收消息线程失败");
        return -1;
    }

    //等待线程结束
    pthread_join(sendThread, NULL);
    pthread_join(recvThread, NULL);

    //关闭socket
    close(hSock);

    return 0;
}

三、知识点

1.bind()

在这里插入图片描述

在这里插入图片描述


四、改进方向

1.做的Linux端,只能在相同的IP上启动几个客户端自己玩。
后续可以做成Windows的exe,买个云服务器,然后发给朋友,进行通信。


五、跟练视频

陈子青多人聊天室-C/C++ 多人聊天室开发-epoll模型的IO多路复用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/460243.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序--开启下拉刷新页面

1、下拉刷新获取数据enablePullDownRefresh 开启下拉刷新&#xff1a; enablePullDownRefreshbooleanfalse是否开启当前页面下拉刷新 案例&#xff1a; 下拉刷新&#xff0c;获取新的列表数据,其实就是进行一次新的网络请求&#xff1a; 第一步&#xff1a;在.json文件中开…

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+OSD动态字符叠加,提供1套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收HLS多路视频融合叠加应用本方案的S…

sqllab第二十二关通关笔记

知识点&#xff1a; cookie注入报错注入 直接抓取对应的数据包&#xff0c;发现还是一个cookie注入 参数值被base64加密了 测试这里使用什么手段读取输入 构造payload:uname1 base64加密&#xff1a;MSc 出现了hacker的页面&#xff0c;说明信息错误但是单引号没起作用 使…

《ARM汇编与逆向工程》读书心得与实战体验

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 &#x1f4d8; 一、引言 &#x1f4dd; 二、…

Livox激光雷达 mid360 跑 fastlio2 - 流程记录

mid360 跑 fastlio2 一、配置 mid360 环境1.1、主机配置静态IP为192.168.1.501.2、Livox-SDK21.3、Livox_ros_driver2二、Fast-lio22.1、下载源码2.2、修改代码2.3、编译、运行 一、配置 mid360 环境 1.1、主机配置静态IP为192.168.1.50 1.2、Livox-SDK2 安装工具 sudo apt…

【Unity+Vuforia】AR 发布安卓的设置

Player Settings > Resolution and Presentation > Default Orientation portrait Player Settings > Other Settings > Auto Graphics API 取消勾选 Player Settings > Other Settings > Graphics APIs 选择OpenGLES3删除其他的 Player Settings…

Python QT 之PySide6简单入门

目录 1.开发环境配置 1.1 下载PySide6 2.2 配置pycharm相关快捷方式 PySide6_Designer - QT Designer 设计UI PySide6_UIC - 将QT Designer生成的UI文件转换为python文件 PySide6_RCC - 将RCC文件转换为python文件 2.第一个开发实例 2.1 QT desiger设计界面 2.2 将ui文…

知名比特币质押协议项目Babylon联合创始人David将出席参加Hack.Summit()2024区块链开发者大会

Babylon项目已确认将派遣其项目代表出席2024年在香港数码港举办的Hack.Summit()2024区块链开发者大会。作为比特币生态的领军项目&#xff0c;Babylon积极参与全球区块链领域的交流与合作&#xff0c;此次出席大会将为其提供一个展示项目进展、交流技术与创新思路的重要平台。B…

信雅纳网络测试的二次开发集成:XOA(Xena Open-Source Automation)开源自动化测试

目录 XOA是什么 XOA CLI XOA Python API ​XOA Python Test Suite/测试套件 XOA Converter Source Code XOA是什么 XOA&#xff08;Xena Open-Source Automation&#xff09;是一个开源的测试自动化框架&#xff0c;追求“高效、易用、灵活”的跨操作系统的开发框架。能…

开启Safari手势支持

在使用Safari 的时候&#xff0c;大家有没有觉得不支持手势使用起来不是很方便&#xff0c; 触摸板只支持少量简单的手势&#xff0c;如缩放&#xff0c;滚动等。如果使用鼠标的用户&#xff0c;则完全无法使用手势。经过折腾研究&#xff0c;使用CirMenu应用可以完美解决这个要…

SpringCloud Stream 消息驱动

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第九篇&#xff0c;即介绍 Stream 消息驱动。 二、消息驱动概念 2.1 消息驱动是什么 官方定义 Spring …

k8s helm 删除 tiller

kuberneter 上面装了 helm 想卸载还并不是那么简单, 参考 stackoverflow 回复 kubectl get -n kube-system secrets,sa,clusterrolebinding -o name|grep tiller|xargs kubectl -n kube-system delete kubectl get all -n kube-system -l apphelm -o name|xargs kubectl dele…

【数据结构】串的模式匹配(KMP+朴素模式匹配)

2.串的模式匹配 什么是字符串的模式匹配&#xff1f; 在主串中找到与模式串相同的子串&#xff0c;并返回其所在位置。 模式串&#xff1a;要匹配的一串。注&#xff1a;子串是主串的一部分&#xff0c;一定在主串中存在&#xff0c;但模式串不一定在主串中找得到。 2.1 朴素模…

软件无线电系列——带通信号采样定理

本节目录 一、带通信号采样定理 1、带通信号采样定理的定义 2、带通信号采样定理的证明本节内容 一、带通信号采样定理 1、带通信号采样定理的定义 Nyquist采样定理是对频谱分布在(0,fH)上的基带信号的采样分析的&#xff0c;如果信号的频谱分布在某一限定的频带(fL,fH)上&…

Docker使用(四)Docker常见问题分析和解决收集整理

Docker使用(四)Docker常见问题分析和解决收集整理 五、常见问题 1、 启动异常 【描述】&#xff1a; 【分析】&#xff1a;[rootlocalhost ~]# systemctl status docker 【解决】&#xff1a; &#xff08;1&#xff09;卸载后重新安装&#xff0c;不能解决这个问题。 …

腾讯与字节跳动联合创立萨罗斯网络科技公司 深度整合游戏项目

易采游戏网3月15日消息&#xff1a;抖音集团已将其游戏部门的资产转交给腾讯公司管理&#xff0c;而该部门的员工亦将整体迁移至腾讯新成立的子公司。此举在业界引起了广泛的激烈探讨与深度关注。 据透露&#xff0c;由深圳引力工作室主导的S1手游项目和由江南独力工作室研发的…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:List)

列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据&#xff0c;例如图片和文本。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件内容区小于一屏时&#xff0c;默认没有回弹效果。…

uniapp uview 头像裁剪组件的问题

当切换页面频繁进出头像裁剪组件u-avatar-cropper.vue 获取同一个设备信息时会出现两种不同的高度具体如下 导致 头像裁剪页面高度出现问题&#xff0c;下方按钮被canvas组件遮盖了 解决方法 在进入这个页面前的一个页面做如下代码操作 直接将设备信息提前获取&#xff0c;保…

【论文翻译】UP-DETR—Unsupervised Pre-training for Detection Transformers

0.论文摘要 摘要——通过Transformer model编码器——解码器架构&#xff0c;用于目标检测的检测Transformer model&#xff08;DETR&#xff09;达到了与Faster R-CNN相比具有竞争力的性能。然而&#xff0c;使用scratch transformers训练&#xff0c;DETR需要大规模的训练数…

android seekbar thumb 上添加进度值并居中

环境&#xff1a;android studio 、java 项目需要在进度条的滑块上显示进度值并居中&#xff0c; UI设计图&#xff1a; 代码实现效果图&#xff1a; 浅色模式&#xff1a; 深色模式&#xff1a; 由于一开始没有自定义seekbar&#xff0c; 使用源码Seekar&#xff0c; 滑块要…
最新文章