文章目录
- 前言
- 一、select是什么?
- 1.1 高级IO模型
- 1.2 select实现
- 1.3 select缺点:
- 二、使用select实现TCP并发服务器模型
- 1.引入库
- 2.TCP服务器端
- 3. TCP服务器端
- 3. 运行结果
- 总结
前言
本期主要分享的是对于select的使用,使用select实现TCP并发服务器模型,由于之前所用到的技术知识只能够支撑我们进行单个访问,但是有了select之后呢,我们就能够实现多用户进行访问;这也是非常符合客观需求的;
一、select是什么?
1.1 高级IO模型
(1)阻塞IO
效率高,等待数据过程中不占用CPU资源
(2)非阻塞IO
能够解决多个文件描述符来数据的情况
效率低,等待数据过程中CPU不断轮询所有文件描述符是否有数据
(3)异步IO
当监听文件描述符有数据时,发送信号,收到信号接收数据
只能应用在文件描述符比较少的情况下
(4)多路复用IO
select
poll
epoll
这次呢我们重点来使用一下select;
1.2 select实现
1.函数接口
(1)select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能:
监听文件描述符集合
参数:
nfds:最大文件描述符个数+1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其余文件描述符集合
timeout:设定超时时间
NULL:永远等待,直到有数据发生
返回值:
成功返回产生事件的文件描述符个数
失败返回-1
2. void FD_CLR(int fd, fd_set *set);
功能:将fd从文件描述符集合中移除
3. int FD_ISSET(int fd, fd_set *set);
功能:判断fd是否仍在文件描述符集合中
4. void FD_SET(int fd, fd_set *set);
功能:将fd加入文件描述符集合
5. void FD_ZERO(fd_set *set);
功能:将文件描述符集合清0
1.3 select缺点:
(1)select监听文件描述符个数上限为1024
(2)select监听的文件描述符集合在应用层,事件发时,内核数据需要传递给应用层,造成资源开销
(3)select需要手动检测产生事件的文件描述符
(4)select只能工作在水平触发模式(低速模式)
二、使用select实现TCP并发服务器模型
1.引入库
用到的头文件如下:
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#endif
2.TCP服务器端
我在前面不做过多的文字陈述,大家看代码的时候一定得看注释(非常详细),认真分析,代码如下(示例):
#include "head.h"
int main(int argc, const char *argv[])
{
int sockfd = 0;
struct sockaddr_in recvaddr;
int ret = 0;
fd_set readfds;
fd_set tmpfds;
int maxfd;
int i = 0;
int confd = 0;
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
int nready = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.209.128");
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("fail to listen");
return -1;
}
FD_ZERO(&readfds); //清空文件描述符集合
FD_SET(sockfd, &readfds); //将sockfd加入到文件描述符集合中
maxfd = sockfd; //此时sockfd就是最大的文件描述符
while (1)
{
tmpfds = readfds; //每次循环最开始必须是所有目前已经加入的所有文件描述符
nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL); //进行事件监听
if (-1 == nready)
{
perror("fail to select");
return -1;
}
if (FD_ISSET(sockfd, &tmpfds)) //判断是否有tcp通信文件描述符
{
confd = accept(sockfd, NULL, NULL); //进行三次握手链接
if (-1 == confd)
{
perror("fail to accept");
close(sockfd);
FD_CLR(sockfd, &readfds);
break; //出错后直接退出,因为sockfd只有一个,是server通信的文件描述符,而confd是不同的客户端请求就会对应产生一个
}
FD_SET(confd, &readfds);
maxfd = maxfd > confd ? maxfd : confd; //防止0,1,2三个文件描述符在前面关闭,confd去替补了,所以这里使用三目运算符对confd和maxfd进行判断
}
for (i = sockfd + 1; i <= maxfd;++i) //只判断confd的事件,也就是客户端的事件(客户端用的是confd通信)
{
if (FD_ISSET(i, &tmpfds))
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(i, tmpbuff, sizeof(tmpbuff), 0); //接收客户端消息
if (-1 == nsize)
{
perror("fail to recv");
close(i);
FD_CLR(i, &readfds);
continue; //继续判断下一个
}
else if (0 == nsize)
{
close(i);
FD_CLR(i, &readfds);
continue; //继续判断下一个
}
printf("recv = %s\n", tmpbuff); //打印来自于客户端的消息
sprintf(tmpbuff, "%s----recv", tmpbuff); //回复消息
nsize = send(i, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
close(i);
FD_CLR(i, &readfds);
continue; //发送完毕后继续轮询看别的客户端是否有事件发生
}
}
}
}
close(confd);
close(sockfd);
return 0;
}
3. TCP服务器端
服务器端比较简单,就是发送消息给服务器等待解接收回复,select主要还是体现在server端;
#include "head.h"
int main(int argc, const char *argv[])
{
int sockfd = 0;
struct sockaddr_in senaddr;
char tmpbuff[1024] = {0};
int count = 0;
ssize_t nsize = 0;
int ret = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to spckfd");
return -1;
}
senaddr.sin_family = AF_INET;
senaddr.sin_port = htons(50000);
senaddr.sin_addr.s_addr = inet_addr("192.168.209.128");
ret = connect(sockfd, (struct sockaddr *)&senaddr, sizeof(senaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
sprintf(tmpbuff, "hello ------->%d", count);
count++;
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0); //发送给服务器
if (-1 == nsize)
{
perror("fail to send");
return -1;
}
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (nsize <= 0)
{
break;
}
printf("%s\n", tmpbuff);
}
close(sockfd);
return 0;
}
3. 运行结果
最后,我们来看一下运行结果:
总结
本期分享的这个模型确实能够实现我们这个多任务并发,每当客户端有消息发来时,select监听到才会向下执行,这是它的优点;希望各位小伙伴一定手动练习起来哦!
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!