回顾
上一节 介绍了最简单的 Server 端 与 Client 端的代码。
但是实际应用中并不是单个client 端去连接 Server 端。Server 端需要同时处理来自多个Client 端 的 请求。
方案1:
多线程处理
方案2:
使用IO 多路复用的 Select
代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <time.h>
#define BUFFER_LENGTH 128
#define BACKLOG 10
void *routine(void *arg)
{
int clientfd = *(int *)arg;
while(1)
{
unsigned char buffer[BUFFER_LENGTH] ={0};
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if(ret == 0 )
{
close(clientfd);
break;
}
printf("buffer: %s,ret:%d\n",buffer,ret);
ret = send(clientfd,buffer,ret,0);
}
}
int main()
{
int listenfd;//server 端 需要监听的 fd
int clientfd; // 连接进来的 客户端 fd
struct sockaddr_in servaddr;//IPV4的 socket 地址结构
struct sockaddr_in client;//IPV4的 socket 地址结构
socklen_t len;//地址结构的长度
int ret;//返回值
unsigned char buffer[BUFFER_LENGTH] = {0};
listenfd = socket(AF_INET,SOCK_STREAM,0);// 创建server 端的socket
if(listenfd == -1)
{
printf("create socket error\n"); // 使用 perror最好
return -1;
}
//组装socket 地址结构
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
// 绑定 IP 端口 这样 client 端就 知道 IP 端口信息就可以连接了
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
{
return -2;
}
#if 0 //设置阻塞非阻塞
int flag = fcntl(listenfd,F_GETFL,0);
flag |= O_NONBLOCK;
fcntl(listenfd,F_SETFL,flag);
#endif
listen(listenfd,BACKLOG);//监听 等待 client连接
//
#if 0 //单个连接
len = sizeof(client);
clientfd = accept(listenfd,(struct sockaddr* )&client,&len);
ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if(ret == 0 )
{
close(clientfd);
}
printf("buffer: %s,ret:%d\n",buffer,ret);
ret = send(clientfd,buffer,ret,0);
#elif 0 // 循环处理
while(1)
{
len = sizeof(client);
clientfd = accept(listenfd,(struct sockaddr* )&client,&len);
printf("clientfd:%d\n",clientfd);
ret= recv(clientfd,buffer,BUFFER_LENGTH,0);
printf("buffer:%s\n",buffer);
send(clientfd,buffer,ret,0);
pthread_t threadid;
pthread_create(&threadid,NULL,routine,&clientfd);
}
#else // select 处理
fd_set rfds, wfds, rset, wset;
// 如何标识一个 io 的事件.
FD_ZERO(&rfds);
FD_SET(listenfd,&rfds);
FD_ZERO(&wfds);
int maxfd = listenfd;
while(1){
rset = rfds;
wset = wfds;
int nready = select(maxfd+1,&rset,&wset,NULL,NULL);
printf("nready:%d\n",nready);
if(FD_ISSET(listenfd,&rset))
{
printf("listenfd-->%d\n",listenfd);
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientfd = accept(listenfd,(struct sockaddr*)& client,&len);
printf("clientfd-->%d\n",clientfd);
FD_SET(clientfd,&rfds);
if(clientfd > maxfd) maxfd = clientfd;
printf("maxfd:%d\n",maxfd);
}
for( int i = listenfd + 1; i <= maxfd; i++)
{
printf("====Enter For Loop fd:%d\n",i);
if(FD_ISSET(i,&rset)){
printf("%d:read set\n",i);
ret = recv(i,buffer,BUFFER_LENGTH,0);
if(ret == 0 )
{
printf("received 0 close and clear\n");
close(i);
FD_CLR(i,&rfds);
}else if(ret > 0)
{
printf("buffer: %s,ret:%d\n",buffer,ret);
FD_SET(i,&wfds);
}
}
if(FD_ISSET(i,&wset)){
printf("%d:write set\n",i);
ret = send(i,buffer,ret,0);
FD_CLR(i,&wfds);
FD_SET(i,&rfds);
}
printf("=====exit for loop fd:%d\n",i);
}
//sleep(10);
}
#endif
return 0;
}
注意点
- 代码中使用了 readfdset 和 writefdset 的副本
- rset/wset 供 select 和FD_ISSET 读取使用
- rfds/wfds 作为 FD_CLR 和 FD_SET 写使用
- 一次while 循环后再重新赋值
- 通过检测 listenfd 的 readset ,然后accept 获取clientfd ,再把新获取的 clientfd 加入 select 的 readset 里面。
- for 循环遍历新加入的clientfd 检查 rset 和 wset
调试记录
### 一开始 server 启动,阻塞在select 处
nready:1
listenfd-->3
clientfd-->4
maxfd:4
====Enter For Loop fd:4
=====exit for loop fd:4
## 以上是客户端连接上的打印信息 accept 获取新的Clientfd 4
## 然后进入for 循环 rset 和 wset 都没有消息.
## 下面 客户端 发送了消息 select 检测到消息返回 1
# 检查到 clientfd=4 有数据,进入到if 分支
nready:1
====Enter For Loop fd:4
4:read set
buffer: http://www.cmsoft.cn QQ:10865600,ret:32
=====exit for loop fd:4
##### 读取了消息,然后 设置了 wfds,继续循环
### 进入到 write set 中,发送消息
nready:1
====Enter For Loop fd:4
4:write set
=====exit for loop fd:4
###### 清空写wfds,set 读 rfds
### 检测到listenfd 可以读取了
nready:1
listenfd-->3
clientfd-->5
maxfd:5
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
=====exit for loop fd:5
## 通过accept 获取了新的 clientfd=5,for 循环了 fd=4,fd=5
# fd=5 客户端发来消息了.
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:read set
buffer: http://www.cmsoft.cn QQ:10865600,ret:32
=====exit for loop fd:5
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:write set
=====exit for loop fd:5
### 客户端5 退出
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:read set
received 0 close and clear
=====exit for loop fd:5
# 客户端4 退出
nready:1
====Enter For Loop fd:4
4:read set
received 0 close and clear
=====exit for loop fd:4
====Enter For Loop fd:5
=====exit for loop fd:5
问题
- 同时接收客户端消息的时候,永远是 先连接 的客户端 最先响应
可以把 结合多线程??
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:
服务器高级架构体系:
https://ke.qq.com/course/417774?flowToken=1010783