多人聊天室 (epoll - Linux网络编程)
文章目录
- 零、效果展示
- 一、服务器代码
- 二、客户端代码
- 三、知识点
- 1.connect()
- 2.socket()
- 3.bind()
- 4.send()
- 5.recv()
- 四、改进方向
- 五、跟练视频
零、效果展示
一个服务器作为中转站,多个客户端之间可以相互通信。至少需要启动两个客户端。
三个客户端互相通信
一、服务器代码
chatServer.cpp
函数:socket()、bind()、listen()、accept()、read()、write()
#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 epfd = epoll_create1(0); //或老版本 epoll_create(1);if(epfd < 0){perror("epoll create error");return -1;}//创建监听的socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){ //若socket创建失败,则返回-1perror("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加入epollstruct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;ret = epoll_ctl(epfd,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(epfd,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加入epollstruct epoll_event ev_client;ev_client.events = EPOLLIN; //检测客户端有没有消息过来ev_client.data.fd = client_sockfd;ret = epoll_ctl(epfd, 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(epfd,EPOLL_CTL_DEL, fd ,0);clients.erase(fd);}else{ // n > 0string 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(epfd);close(sockfd);return 0;
}
二、客户端代码
client.cpp (注意g++编译时要加 -pthread)
函数:socket()、connect()、send()、recv()
#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代替scanfif(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()
{//创建socketint 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);//关闭socketclose(hSock);return 0;
}
三、知识点
1.connect()
#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect()成功返回0,失败返回-1
以下是一个简单的 TCP 客户端示例,展示了如何使用 connect() 连接到服务器:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("Socket creation failed");return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(12345); // 服务器监听的端口inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr); // 服务器的IP地址if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("Connection failed");return -1;}printf("Connected to the server\n");// 之后可以使用sockfd进行数据传输close(sockfd); // 关闭套接字return 0;
}
在这个示例中,客户端程序创建了一个套接字,设置服务器的 IP 地址和端口,然后尝试与服务器建立连接。如果 connect() 调用成功,客户端就与服务器建立了连接,并可以通过该套接字进行数据通信。
2.socket()
1.参数
int socket(int address_family, int type, int protocol);
(1)address family:
①AF_INET:IPv4 网络协议。用于TCP/IP和UDP/IP网络通信。
②AF_INET6:IPv6 网络协议。用于TCP/IP和UDP/IP网络通信,但支持IPv6地址。
③AF_UNIX(或AF_LOCAL):本地通道通信。用于在同一台机器上的进程间通信。
2)type:
①SOCK_STREAM:TCP协议,提供面向连接的稳定数据传输,保证数据能够按顺序、完整地到达。
②SOCK_DGRAM:UDP协议,提供无连接的数据传输服务。发送的是独立的消息,不保证顺序或数据完整性。
③SOCK_RAW:提供原始网络协议访问。在网络模型中,这种类型的套接字允许直接访问IP层,通常用于网络协议的开发和测试。
(3)protocol:默认协议填0
2.返回值:
①成功时,socket() 返回一个非负整数,即新创建的套接字文件描述符。
②出错时,返回 -1,并设置全局变量 errno 以表示具体的错误类型。
3.创建一个使用IPv4地址和TCP协议的套接字:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket creation failed");
}
这里,AF_INET 指定使用IPv4地址,SOCK_STREAM 指定使用面向连接的数据传输方式(TCP),0 表示自动选择使用TCP协议。
3.bind()
4.send()
发送数据:将数据放到发送缓冲区。由内核决定什么时候将数据发送出去。
5.recv()
接收数据:当数据送到Linux内核后,数据不是立即给到应用程序,而是放在接收缓冲区,等应用程序什么时候调用recv()函数,什么时候才由内核给到应用程序。
四、改进方向
1.做的Linux端,只能在相同的IP上启动几个客户端自己玩。
后续可以做成Windows的exe,买个云服务器,然后发给朋友,进行通信。
五、跟练视频
陈子青多人聊天室-C/C++ 多人聊天室开发-epoll模型的IO多路复用
相关文章:

多人聊天室 (epoll - Linux网络编程)
文章目录 零、效果展示一、服务器代码二、客户端代码三、知识点1.connect()2.socket()3.bind()4.send()5.recv() 四、改进方向五、跟练视频 零、效果展示 一个服务器作为中转站,多个客户端之间可以相互通信。至少需要启动两个客户端。 三个客户端互相通信 一、服务…...

vite配置
"vite": "^5.1.4" resolve.alias:配置别名 1、执行npm install -D types/node 或者 yarn add types/node -D 2、以下配置代表访问src时可以用“”代替 resolve: {alias: {"": path.resolve(__dirname, "./src"),},}, 使…...

服务器生产环境问题解决思路
游戏服务器开发节奏比较快,版本迭代很频繁,有一些项目甚至出现了周更新(每周准时停服更新维护)。由于功能开发时间短,研发人员本身技术能力等原因,线上出现bug很常见。笔者经历过的游戏项目,一年到头没几次更新不出现bug的(当然,配置问题也算bug)。那当出现bug,我们…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Column)
沿垂直方向布局的容器。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 接口 Column(value?: {space?: string | number}) 从API version 9开始,该接口…...

LLM之RAG实战(三十)| 探索RAG语义分块策略
在LLM之RAG实战(二十九)| 探索RAG PDF解析解析文档后,我们可以获得结构化或半结构化的数据。现在的主要任务是将它们分解成更小的块来提取详细的特征,然后嵌入这些特征来表示它们的语义,其在RAG中的位置如图1所示&…...

软件测试-------Web(性能测试 / 界面测试 / 兼容性测试 / 安全性测试)
Web(性能测试 / 界面测试 / 兼容性测试 / 安全性测试) 一、Web性能测试:(压力测试、负载测试、连接速度测试)1、压力测试: 并发测试 (如500人同时登录邮箱) 2、负载测试…...

工欲善其事,必先利其器,Markdown和Mermaid的梦幻联动(2)
该文章Github地址:https://github.com/AntonyCheng/typora-notes/tree/master/chapter03-mermaid 在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文…...

STM32基础--使用寄存器点亮流水灯
GPIO 简介 GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的 GPIO被分成很多组,每组有 16 个引脚…...

代码随想录训练营Day25:● 216.组合总和III ● 17.电话号码的字母组合
216.组合总和III 题目链接 https://leetcode.cn/problems/combination-sum-iii/description/ 题目描述 思路 自己写的效率会慢一些,而且没有用到剪枝 class Solution {List<List<Integer>> list new ArrayList<>();List<Integer> lis…...

SwiftUI的 特性 - ViewModify
SwiftUI的 特性 - ViewModify 记录一下SwiftUI的 特性 - ViewModify的使用方式 可以通过viewModify来管理视图的样式,结合extension来完成封装达到解偶效果 import SwiftUI/// 我们可以通过viewModify来管理视图的样式,来达到解偶效果 struct DefaultB…...

中间件 | RPC - [Dubbo]
INDEX 1 Dubbo 与 web 容器的关系2 注册发现流程3 服务配置3.1 注册方式 & 订阅方式3.2 服务导出3.3 配置参数 4 底层技术4.1 Dubbo 的 spi 机制4.2 Dubbo 的线程池4.3 Dubbo 的负载均衡策略4.3 Dubbo 的协议 1 Dubbo 与 web 容器的关系 dubbo 本质上是一个 RPC 框架&…...

【中等】保研/考研408机试-二叉树相关
目录 一、基本二叉树 1.1结构 1.2前序遍历(注意三种遍历中Visit所在的位置) 1.2中序遍历 1.3后序遍历 二、真题实战 2.1KY11 二叉树遍历(清华大学复试上机题)【较难】 2.2KY212 二叉树遍历二叉树遍历(华中科技大…...

自动驾驶---Motion Planning之构建SLT Driving Corridor
1 背景 在上篇博客《自动驾驶---Motion Planning之Speed Boundary》中,主要介绍了Apollo中Speed Boundary的一些内容,可以构造ST图得到边界信息,最后结合粗糙的速度曲线和路径曲线,即可使用优化的方法求解得到最终的轨迹信息(s,s,s,l,l,l)。 本篇博客笔者主要介绍近…...

本地文件包含漏洞利用
目录 前期信息收集获取网站权限获取服务器权限纵向提权 前期信息收集 拿到目标的资产,先试一下IP能不能访问 探测一下目标的端口运行的是什么服务 nmap -sC -sV xx.xx9.95.185 -Pn获取网站权限 我们可以知道目标的80端口上运行着http服务,服务器是u…...

【docker】docker的常用命令
📝个人主页:五敷有你 🔥系列专栏:中间件 ⛺️稳中求进,晒太阳 常规命令 docker version #查看docker 版本信息docker info #显示docker 的系统信息,包括镜像和容器数量docker --help #查看所有的命…...

jmeter实战
jmeter学习 1,接口在定义时,post请求参数尽量放在body里面,get请求参数尽量放在parameters里面,否则会导致jmeter请求接口报错的问题(jmeter底层有较为严格的请求格式) 2,定义全局变量使用:Config Elemen…...

面试官常问问题
1、请你简单的自我介绍一下? 【Tips】① 口述内容不可与简历内容冲突;②阐述方式避免过度官方 且语速较快;③言简意赅,直击要害,抓重点突出项;④面试前应自己模拟练习几次,避免过度紧张导致的口…...

外包就干了2个月,技术退步明显....
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

面向对象 汇总(详细内容见Day12—16)
面向对象 汇总(详细内容见Day12—16) 文章目录 面向对象 汇总(详细内容见Day12—16)一、概念二、类三、对象四、成员属性/成员变量五、成员方法六、构造方法七、private - 私有化八、封装九、this - 本对象十、分包十一、static -…...

结构体联合体枚举和位段
文章目录 结构体结构体类型的声明特殊的声明 结构的自引用结构体变量的定义和初始化结构体内存对齐为什么要内存对齐结构体传参结构体实现位段(位段的填充&可移植性)位段位段的内存分配空间如何开辟位段的跨平台问题位段的应用 枚举枚举类型的定义枚…...

人类程序员真要失业?首位“AI软件工程师”亮相引爆科技圈
初创公司Cognition成立不到两个月,但已经拥有十名天才工程师。他们推出了一款名为Devin的人工智能(AI)助手,可以协助人类软件工程师完成各种开发任务。Devin与现有的其他AI编码者不同,它能够从头开始构建网站、自动部署…...

redis的过期策略以及内存淘汰机制
redis采用的是定期删除惰性删除策略。 为什么不用定时删除策略? 定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要 将时间应用在处理请求,而不是删除key,因此没有采用这一策…...

华为数通方向HCIP-DataCom H12-821题库(多选题:161-180)
第161题 以下关于IPv6优势的描述,正确的是哪些项? A、底层自身携带安全特性 B、加入了对自动配置地址的支持,能够无状态自动配置地址 C、路由表相比IPv4会更大,寻址更加精确 D、头部格式灵活,具有多个扩展头 【参考答案】ABD 【答案解析】 第162题 在OSPF视图下使用Filt…...

网络通信与网络协议
网络编程是指利用计算机网络实现程序之间通信的一种编程方式。在网络编程中,程序需要通过网络协议(如 TCP/IP)来进行通信,以实现不同计算机之间的数据传输和共享。在网络编程中,通常有三个基本要素 IP 地址:定位网络中某台计算机端口号port:定…...

【矩阵】240. 搜索二维矩阵 II【中等】
搜索二维矩阵 II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1: 输入:matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22…...

详解uniapp的生命周期
这篇文章主要介绍了 uniapp 的生命周期, 应用生命周期是指应用程序从启动到关闭的整个过程,包括应用程序的启动、前后台切换、退出等, 需要的朋友可以参考下 Uniapp 作为一款跨平台应用开发框架,具有丰富的生命周期,以下是 Uniapp 的生命周期…...

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:PluginComponent)
提供外部应用组件嵌入式显示功能,即外部应用提供的UI可在本应用内显示。 说明: 该组件从API Version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。本组件为系统接口。 子组件 无 接口 PluginComponent(value:…...

mysql笔记:15. 事务和锁
文章目录 一、事务概述二、事务基本操作三、事务保存点四、事务的隔离级别1. READ UNCOMMITTED设置事务的隔离级别 2. READ COMMITTED3. REPEATABLE READ4. SERIALIZABLE 五、MySQL的锁InnoDB的锁类型1. InnoDB的行级锁2. InnoDB的表级锁 死锁 在开发过程中,我们经常…...

Learn OpenGL 15 面剔除
面剔除 尝试在脑子中想象一个3D立方体,数数你从任意方向最多能同时看到几个面。如果你的想象力不是过于丰富了,你应该能得出最大的面数是3。你可以从任意位置和任意方向看向这个球体,但你永远不能看到3个以上的面。所以我们为什么要浪费时间…...

EndeavourOs(arch系)安装sunpinyin输入法(ibus) + 迅雷(xunlei-bin)
输入法 yay -S ibus yay -S ibus-libpinyin yay -S ibus-sunpinyin yay -Q ibus ibus-libpinyin ibus-sunpinyin #验证 # 注销然后打开ibus config... # 在Input Method 添加Chinese->SunPinYin # 使用Ctrl Space, 默认Super Space, 请自行修改 # 再次注销,开…...