引言
接着上一篇笔记的POLL函数实现TCP服务器的多路复用,本篇笔记主要是以SELECT函数实现服务器的多路复用。
实现思路
- socket中有四个描述符,本质上都是阻塞,并且阻塞在一个端点上
- 因此,使用文件描述符集合fd_set将关心的描述符放入集合中
- 然后通过使用Select函数监听这个集合中的描述符
- Select函数会不断的变化,因此当有连接来访问以及数据同时访问的时候,listenfd 以及 fd2就有反应了
- Select函数中的描述符集合只留下了listenfd,fd2,然后通过使用FS_ISSET()检测Select函数中的描述符集合中的有效描述符
- 通过检测得到listenfd,fd2,描述符有效。就会返回accept以及read。
select函数解剖
- select函数总共有三个描述符集合:读就绪,写就绪,异常就绪
- 然后将描述符关心的状态填入对应级集合即可。比如,关心listenfd的读状态,就把listenfd放入读就绪集合,如果fd2的读,写都关心,那么就要把fd2放入读就绪集合的同时也要放入写就绪的集合。如此类推
代码实现
server.c:
#include "head4sock.h"
int main(int argc, char **argv)
{
if (argc !=2)
{
printf("Usage:%s <PORT>\n", argv[0]);
exit(0);
}
int fd = Socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in srvaddr;
socklen_t len =sizeof(srvaddr);
bzero(&srvaddr,len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvaddr.sin_port = htons(atoi(argv[1]));
Bind(fd,(struct sockaddr *)&srvaddr,len);
Listen(fd,3);
int connfd = Accept(fd,NULL,NULL);
fd_set rset; //定义读就绪集合
char buf[SIZE];
while (1)
{
FD_ZERO(&rset); //集合清零
FD_SET(connfd,&rset);//将connfd加入读就绪集合
FD_SET(STDIN_FILENO,&rset);//将标准读操作加入读就绪集合
select(connfd+1,&rset,NULL,NULL,NULL); //最大文件描述符+1,读就绪描述符集合,写就绪,异常就绪,超时就绪
bzero(buf,SIZE);
if(FD_ISSET(connfd,&rset)) //判断是否依然存在connfd,有无客户端数据
{
if(Read(connfd,buf,SIZE)==0)
break;
printf("from client:%s", buf);
}
if (FD_ISSET(STDIN_FILENO,&rset)) //判断键盘是否输入数据
{
fgets(buf,SIZE,stdin);
write(connfd,buf,strlen(buf));
}
}
close(fd);
close(connfd);
return 0;
}
client.c:
#include "head4sock.h"
int main(int argc, char *argv[])
{
if (argc !=3)
{
printf("Usage:%s <IP> <PORT>\n", argv[0]);
exit(0);
}
int fd = Socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in srvaddr;
socklen_t len =sizeof(srvaddr);
bzero(&srvaddr,len);
srvaddr.sin_family = AF_INET;
inet_pton(AF_INET,argv[1],&srvaddr.sin_addr);
srvaddr.sin_port = htons(atoi(argv[2]));
Connect(fd,(struct sockaddr *)&srvaddr,len);
fd_set rset; //定义读就绪集合
char buf[SIZE];
while (1)
{
FD_ZERO(&rset); //集合清零
FD_SET(fd,&rset);//将connfd加入读就绪集合
FD_SET(STDIN_FILENO,&rset);//将标准读操作加入读就绪集合
select(fd+1,&rset,NULL,NULL,NULL); //最大文件描述符+1,读就绪描述符集合,写就绪,异常就绪,超时就绪
bzero(buf,SIZE);
if(FD_ISSET(fd,&rset)) //判断是否依然存在fd,有无服务器数据
{
if(Read(fd,buf,SIZE)==0)
break;
printf("from client:%s",buf);
}
if (FD_ISSET(STDIN_FILENO,&rset)) //判断键盘是否输入数据
{
fgets(buf,SIZE,stdin);
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}