一.1.搭建好TCP的通信模型 2.创建红黑树根节点 3.将套接字事件添加到红黑树中,使其被监听 4.当套接字事件发生,表示有客户端连接,将连接事件加入到红黑树节点当中 5.每当连接事件发生时,表示客户端发送信息到服务器 6.每当有事件准备就绪时,将对应的红黑树节点信息放入到构建的events数组中 7.通过节点信息判断对应事件并处理,实现IO多路复用。
更多详细信息在代码注释
1.服务器代码
#include "server.h"
// TCP协议的多人聊天室服务器,创建服务器后使用epoll实现IO多路复用,分别进行客户端的连接,信息的接受
int main(int argc, char const *argv[])
{
// 一.创建套接字1.AF_INET默认为ip(7)协议 2.SOCK_STREAM默认为TCP
// 3.0表示使用type对应的默认协议
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
{
ERR_MSG("socket");
goto OUT1;
}
// 设置允许端口快速被重用
int resue = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("socket success _%d_\n", __LINE__);
// 填充服务器的信息结构体
my_ser.sin_family = AF_INET; // IPv4协议指向填充
my_ser.sin_port = htons(PORT); // 将端口转换成网络字节
my_ser.sin_addr.s_addr = inet_addr(IP); // IP地址转换成网络字节序
// 三.绑定
if (bind(sock_fd, (struct sockaddr *)&my_ser, sizeof(my_ser)) < 0)
{
ERR_MSG("bind");
goto OUT2;
}
printf("bind success _%d_\n", __LINE__);
// 将套接字设置为监听状态
if (listen(sock_fd, 128) < 0)
{
ERR_MSG("listen");
goto OUT2;
}
printf("listen success _%d_\n", __LINE__);
// 创建一个epoll句柄/红黑树根节点
int epfd = epoll_create(10);
if (epfd < 0)
{
printf("epoll_create on_success _%d_", __LINE__);
goto OUT2;
}
// 将套接字的信息存入到event结构体中,为事件放入红黑树中做准备
event.events = EPOLLIN; // 关注可读事件,套接字有数据可读时触发
event.data.fd = sock_fd;
// 将event存放到套接字信息放入到红黑树中
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &event) < 0)
{
printf("epoll_ctl on_success _%d_", __LINE__);
goto OUT2;
}
int newfd; // 客户端连接
int minfd = -1, key = 1; // 保存最小的newfd,用于循环向整个服务器发送信息
// 循环监听是否有客户端连接
addrlen = sizeof(my_cin);
while (1)
{
// ret返回就绪事件的个数,并将就绪的事件放入到
// events这个结构体中,参3表示最多放入10个事件,
// 参4的-1表示不关心是否超时
int ret = epoll_wait(epfd, events, 10, -1);
printf("ret=%d\n", ret);
if (ret < 0)
{
printf("epoll_wait on_success");
goto OUT2;
}
/*1.走到这里表示有事件发生,等待处理,循环判断每一个事件*/
for (int i = 1; i <= ret; i++)
{
if (events[i - 1].data.fd == sock_fd) // 表示有客户端连接服务器
{
printf("触发客户端连接事件%d,i=%d\n", __LINE__, i);
newfd = accept(sock_fd, (struct sockaddr *)&my_cin, &addrlen);
if (newfd < 0)
{
ERR_MSG("accept");
return -1;
}
if (key == 1)
{
minfd = newfd;
key--;
}
printf("newfd=%d 客户端连接成功\n", newfd);
// 客户端连接成功后,将事件添加到红黑树中
event.events = EPOLLIN; // 关注可读事件,套接字有数据可读时触发
event.data.fd = newfd;
// 添加到红黑树中
if (epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &event) < 0)
{
printf("epoll_ctl on_success _%d_", __LINE__);
goto OUT3;
}
}
// 因为红黑树中只存放了sock_fd和newfd两种文件描述符,因此当不是客户端连接事件
// 就一定是客户端发送数据到服务器,将客户端发送来的消息广播都所有客户端
else
{
printf("触发客户端交互事件%d,i=%d,newfd=%d\n", __LINE__, i, events[i-1].data.fd);
memset(buf, '\0', sizeof(buf)); // 清空消息队列
// 接收客户端发送来的消息(参1:对应客户端,参4:阻塞等待数据)
int res = recv(events[i - 1].data.fd, buf, sizeof(buf), 0);
if (res < 0)
{
ERR_MSG("recv");
goto OUT3;
}
else if (res == 0)
{
printf("客户端%d已经关闭%d\n", events[i - 1].data.fd, __LINE__);
goto OUT3;
}
else // 想所有客户端展示信息
{
sprintf(buf_ID, "%s%d%s%s", "客户端_",events[i-1].data.fd, ":", buf);
for (int i = minfd; i <= newfd; i++) // 循环向所有客户端发送信息
{
printf("midfd=%d,newfd=%d\n",minfd,newfd);
if (send(i, buf_ID, strlen(buf_ID)+1, 0) < 0)
{
ERR_MSG("send");
goto OUT3;
}
}
printf("%s\n", buf_ID); // 展示到终端
}
}
}
}
return 0;
// 注销等级列
OUT3:
for (int i = minfd; i <= newfd; i++)
{
close(i);
}
OUT2:
close(sock_fd);
OUT1:
return sock_fd;
}
2.客户端代码
正常搭建客户端TCP模型文章来源:https://www.toymoban.com/news/detail-546817.html
#include "client.h"
int main(int argc, char const *argv[])
{
// 创建套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd < 0)
{
ERR_MSG("socket");
goto OUT1;
}
// 连接服务器
// 1.填充服务器的信息
my_ser.sin_family = AF_INET;
my_ser.sin_port = htons(PORT);
my_ser.sin_addr.s_addr = inet_addr(IP);
// 连接
if (connect(cfd, (struct sockaddr *)&my_ser, sizeof(my_ser)) < 0)
{
ERR_MSG("connet");
goto OUT2;
}
printf("connect server success\n");
while (1)
{
memset(cli_buf,'0',sizeof(cli_buf));
printf("输入你要发送的信息:");
scanf("%s",cli_buf);
while(getchar()!=10);//清除缓存区的残留字符
// 发送信息给服务器
if(send(cfd,cli_buf,sizeof(cli_buf),0)<0)
{
ERR_MSG("send");
goto OUT2;
}
// 接收服务器的信息,并展示到终端
memset(cli_buf,'0',sizeof(cli_buf));
int res=-1;
res=recv(cfd,cli_buf,sizeof(cli_buf),0);
if(res<0)//接收失败
{
ERR_MSG("recv");
goto OUT2;
}
else if(0==res)//服务器关闭
{
printf("服务器关闭\n");
goto OUT2;
}
//展示到终端
printf("%s\n",cli_buf);
}
// 关闭套接字
close(cfd);
return 0;
OUT2:
close(cfd);
OUT1:
return cfd;
}
!!!文章来源地址https://www.toymoban.com/news/detail-546817.html
到了这里,关于使用Linux系统IO多路复用中eopll创建基于TCP通信协议的多人聊天室的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!