思维导图 以select函数模型为例
![](https://i-blog.csdnimg.cn/direct/da9a013900a14710b96db009b6be72b8.png)
思维导图2 对应 epoll模型 应使用的函数
![](https://i-blog.csdnimg.cn/direct/b7986643afdd446989d69a39aa091670.png)
题目
使用epoll函数实现 两个客户端 通过服务器 实现聊天
思路
在原先代码基础上
实现 服务器 发向 客户端 使用客户端在服务器上的 套接字描述符
实现 客户端 接收 服务器消息 发向服务器的描述符 也可以用于读取服务器传来数据
实现通信【1】客户端与服务器连接后,会被分配一个唯一的、不变的id号,直到本次连接断开前,此id号不会被改变或者销毁;
【2】服务器根据 收到的 PACK包中的 pack.type 参数,确定转发目标;
【3】通过这个唯一id,服务器可以实现数据向 指定目标 的发送;
代码
服务器
struct PACK
{
int size; //数据包大小
int type; //对数据的操作
char buf[1500]; //数据
int conut; //数据已经占用多少字节
};
enum
{
//以下参数适用于 client_id_ctl() 函数的 int cmd 参数
insect_clinet=1, //分配客户端id号 的指令
get_client_id, //获取客户端id号 的指令
get_clinetfd, //获取客户端描述符 的指令
remove_clinet, //移除客户端 的指令
//以下参数适用于 数据包的 pack.type 值
send_to_server=-1, //说明数据是发送向服务器的
};
//客户端id号 操作函数
//根据cmd不同,实现分配id 获取id 获取描述符 移除id等功能
int client_id_ctl(int arr[],int len,int cmd,int client);
//解析数据
void unlood(struct PACK *pack);
int main(int argc, const char *argv[])
{
int client_arr[10]; //用于存放 客户端在服务器 上的文件描述符
memset(client_arr,-1,sizeof(client_arr));
//创建服务器套接字
int serverfd=socket(AF_INET,SOCK_STREAM,0);
//创建并设置 "网络通信结构体"
struct sockaddr_in addr;
addr.sin_family=AF_INET; //ipv4
int port=atoi(argv[1]);
addr.sin_port=htons(port); //端口号 (大端存储)
addr.sin_addr.s_addr=inet_addr("0.0.0.0");
//套接字 绑定 ip 和 port
int res=bind(serverfd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
{
printf("服务器 绑定ip和port 失败\n");
perror("bind");
return 0;
}
printf("服务器已就绪\n");
//监听
listen(serverfd,10);
//创建动态的监视列表
int epfd=epoll_create1(EPOLL_CLOEXEC);
//设定 需要监视的描述符 以及 激活形式
struct epoll_event event_serverfd={.events=EPOLLIN,.data.fd=serverfd};
struct epoll_event event_stdinfd={.events=EPOLLIN,.data.fd=0};
//添加到 [监视列表]
epoll_ctl(epfd,EPOLL_CTL_ADD,serverfd,&event_serverfd);
epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event_stdinfd);
while(1)
{
struct epoll_event active_list[10]; //用于存放被激活的描述符
//开始监视 [监视列表]中描述符
int len=epoll_wait(epfd,active_list,10,-1); //被激活的描述符 在 active_list[10]数组中
//遍历被激活的描述符
for(int i=0;i<len;i++)
{
int active_fd=active_list[i].data.fd; //获取被激活的描述符fd
//如果是服务器可读 说明有用户尝试连接
if(active_fd==serverfd)
{
//接收客户端连接
int clientfd=accept(serverfd,NULL,NULL);
//为客户端 分配id号
int id=client_id_ctl(client_arr,10,insect_clinet,clientfd);
// //将客户端 添加到 [监视列表]
struct epoll_event event_client={.events=EPOLLIN,.data.fd=clientfd};
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event_client);
printf("服务器提示: 有新的客户端连接到服务器 分配id号:%d\n\n",id);
}
//如果数据来自 终端
else if(active_fd==0)
{
printf("服务器提示:NULL");
while(getchar()!=10);
printf("\n");
}
//其他情况:数据来自客户端
else
{
/***********************************数据读取部分***********************************/
//获取该客户端id号
int active_id=client_id_ctl(client_arr,10,get_client_id,active_fd);
struct PACK pack={0};
int res=read(active_fd,&pack.size,4);
if(res==0)
{
printf("服务器提示:%d#用户断开连接\n",active_id);
//[监视列表] 中 删除该描述夫
epoll_ctl(epfd,EPOLL_CTL_DEL,active_fd,&event_serverfd);
//移除该用户id
client_id_ctl(client_arr,10,remove_clinet,active_id);
close(active_fd);
continue;
}
read(active_fd,(char *)&pack+4,pack.size-4);
/***********************************数据处理部分***********************************/
//根据PACK包中的 tyoe 决定对数据的操作
if(pack.type==send_to_server)
{
//说明 数据发向服务器 在服务器解析
printf("服务器提示:%d#用户发来数据\n",active_id);
unlood(&pack); //解析数据
}
else if(pack.type>=0&&pack.type<10)
{
//说明 数据需要转发给其他客户端
printf("服务器提示:%d#用户发来数据 数据转发给%d#用户\n",active_id,pack.type);
//获取 转发目标 的描述符
int target_fd=client_id_ctl(client_arr,10,get_clinetfd,pack.type);
//数据转发
pack.type=active_id;
write(target_fd,&pack,pack.size); //数据发送给 转发目标
}
}
}
}
return 0;
}
int client_id_ctl(int arr[],int len,int cmd,int client)
{
static int sum_client=0;
//分配客户端id号
if(cmd==insect_clinet)
{
static int new_client_id=0; //循环未使用的方式 给新增的客户端编号
if(sum_client==10)
{
printf("连接的客户端达到上限 \n");
return -1;
}
//定位到 未使用的下标 未使用则数组的值为-1
while (arr[new_client_id]%len!=-1)
{
new_client_id=(new_client_id+1)%len; //循环查找
}
arr[new_client_id]=client; //将客户端描述符存入数组
sum_client++;
return new_client_id;
}
//获取客户端id号
else if (cmd==get_client_id)
{
for(int i=0;i<len;i++)
{
if(arr[i]==client)
{
return i; //返回该客户端id号
}
}
return -1;
}
//获取指定客户端描述符 根据id号查找
else if (cmd==get_clinetfd)
{
int id=client;
if(id>=len||id<0)
{
printf("id号不合法\n");
return -1; //删除失败 返回-1
}
return arr[id]; //成功返回该id号的描述符
}
//移除客户端id号 根据id号销毁
else if (cmd==remove_clinet)
{
int id=client;
if(id>=len||id<0)
{
printf("%d#客户端移除失败 id号不合法\n",id);
return -1; //删除失败 返回-1
}
arr[client]=-1;
sum_client--;
return id; //删除成功 返回删除的id号
}
}
void unlood(struct PACK *pack)
{
int len;
while(1)
{
//读取2个字节
len=*(short *)(pack->buf+pack->conut);
if(len==0)
{
printf("数据解析完毕\n\n");
return;
}
pack->conut+=2;
//读取字符串
char data[len+1];
memset(data,0,sizeof(data));
memcpy(data,pack->buf+pack->conut,len);
printf("数据:%s\n",data);
pack->conut+=len;
}
printf("数据解析完毕\n");
}
客户端
struct PACK
{
int size; //数据包大小
int type; //对数据的操作
char buf[1500]; //数据
int conut; //数据已经占用多少字节
};
//数据附加 放入数据
void append(struct PACK* pack,const char *data);
//解析数据
void unlood(struct PACK *pack);
int main(int argc, const char *argv[])
{
//创建套接字
int clientfd=socket(AF_INET,SOCK_STREAM,0); //ipv4 tcp形式 自动选择协议
//ip地址 和 potr端口号 放入"网络通信结构体"
struct sockaddr_in addr;
addr.sin_family=AF_INET; //ipv4
int port=atoi(argv[1]);
addr.sin_port=htons(port); //端口号
addr.sin_addr.s_addr=inet_addr("127.0.0.1"); //服务器ip地址
//结构体数据 写入 套接字
int res=connect(clientfd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
{
printf("套接字 绑定ip和port 失败\n");
perror("bind");
return 0;
}
int epfd=epoll_create1(EPOLL_CLOEXEC); //创建动态的监视列表
//设定 需要监视的描述符 以及 激活形式
struct epoll_event event_serverfd={.events=EPOLLIN,.data.fd=clientfd};
struct epoll_event event_stdinfd={.events=EPOLLIN,.data.fd=0};
//添加到 [监视列表]
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event_serverfd);
epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event_stdinfd);
while(1)
{
printf("开始监视\n");
struct epoll_event active_list[10]; //用于存放被激活的描述符
//开始监视
int len=epoll_wait(epfd,active_list,10,-1); //被激活的描述符 在 active_list[10]数组中
//遍历被激活的描述符
for(int i=0;i<len;i++)
{
int active_fd=active_list[i].data.fd; //获取被激活的描述符fd
if(active_fd==0)
{
//如果是终端可读
struct PACK pack={0};
pack.type=-1; //默认发往终端
char buf[20]="";
scanf("%19s",buf);
printf("\n客户端数据:%s\n",buf);
while(getchar()!=10);
append(&pack,buf);
printf("-----------------------------------\n");
printf("对该数据的操作:\n");
printf(" >= 0 转发到指定客户端\n");
printf(" ==-1 数据发向服务器 服务器显示数据\n");
printf(" ==-2 向服务器分配给自己的id号\n");
printf("-----------------------------------\n");
printf("该数据需要:");
scanf("%d",&pack.type);
while(getchar()!=10);
pack.size=pack.conut+8; //包实际大小
write(clientfd,&pack,pack.size); //发送给服务器 将接受多少字节
pack.conut=0; //发送完毕 conut 重置
}
if(active_fd==clientfd)
{
//如果是描述符可读 说明服务器发来消息
struct PACK pack={0};
int res=read(clientfd,&pack.size,4);
if(res==0)
{
printf("与服务器断开连接\n");
return 0;
}
read(clientfd,(char *)&pack+4,pack.size-4);
printf("服务器传来一条数据,数据转发自%d#用户\n",pack.type);
unlood(&pack); //解析数据
}
}
}
return 0;
}
//数据附加 放入数据
void append(struct PACK* pack,const char *data)
{
int len=strlen(data);
*(short *)(pack->buf+pack->conut)=len;
pack->conut+=2;
memcpy(pack->buf+pack->conut,data,len);
pack->conut+=len;
}
//解析数据
void unlood(struct PACK *pack)
{
int len;
while(1)
{
//读取2个字节
len=*(short *)(pack->buf+pack->conut);
if(len==0)
{
printf("数据解析完毕\n\n");
return;
}
pack->conut+=2;
//读取字符串
char data[len+1];
memcpy(data,pack->buf+pack->conut,len);
printf("数据:%s\n",data);
pack->conut+=len;
}
printf("数据解析完毕\n");
}
效果
![](https://i-blog.csdnimg.cn/direct/e63cde8a250b48faa641d5061ee550f9.png)