目录
使用select函数实现超时检测
网络超时检测概念
阻塞:
以读阻塞为例,如果缓冲区中有内容,则程序正常执行
如果缓冲区没有内容,程序会一直阻塞,直到有内容,读取内容继续向下运行。
非阻塞 :
以读阻塞为例,如果缓冲区中有内容,则程序正常执行
如果缓冲区没有内容,程序会立即返回,然后继续向下运行。
超时检测:
介于阻塞和非阻塞之间,需要设置一定的时间,如果时间到达之前,缓冲区中没有数据程序会阻塞,
如果时间到了,缓冲区中还没有数据,则程序立即返回,继续向下执行。
select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:
nfds: 要监视的最大的文件描述符+1
readfds: 要监视的读文件描述符集合,不想监视可以 传NULL
writefds: 要监视的写文件描述符集合,不想监视可以 传NULL
exceptfds: 要监视的异常文件描述符集合,不想监视可以 传NULL
timeout: 就是用于设定超时时间的:
使用下面的结构体
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
timeout:
0 非阻塞
NULL 永久阻塞
struct timeval 阻塞一定的时间
返回值:
-1 出错
0 超时
>0 就绪的文件描述符的个数
注意事项:
1.select返回时会将没有就绪的文件描述符擦除,所以循环中反复调用select时 每次需要重置集合
2.select可以监视的最大文件描述符 FD_SETSIZE 1024
3.一般情况下,只关心读文件描述符集合(readfds)
select监视的文件描述符,存放在128位的数组中s[128],1字节=8bit,128*8=1024,每个bit位表示一个文件描述符
位运算接口
1.将fd从集合中删除
void FD_CLR(int fd, fd_set *set);
2.测试fd是否还在集合中 返回0 不在 非0 在
int FD_ISSET(int fd, fd_set *set);
3.将fd添加到集合中
void FD_SET(int fd, fd_set *set);
4.清空集合
void FD_ZERO(fd_set *set);
使用IO多路复用(select)实现TCP并发服务器
无超时检测版:IO多路复用(select)实现TCP并发
代码实现
服务器—01server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
/*select*/
#include <sys/select.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int sockfd = socket_bind_listen(argv);
//创建文件描述符表
fd_set readfds; //母本
FD_ZERO(&readfds); //清空
//每次给select使用,因为select会擦除掉一部分
fd_set readfds_temp; //用来备份原集合的
FD_ZERO(&readfds_temp); //清空
//记录表中最大的文件描述符
int max_fd = 0;
//将要监视的文件描述符放到表中
FD_SET(sockfd, &readfds);
max_fd = (max_fd > sockfd ? max_fd : sockfd); //记录最大文件描述符
int ret, ret1 = 0;
int accept_fd;
char buff[128] = {0};
int i;
//设置超时时间!!!!!!!!!!!!!!!!!!!!!!!!!
struct timeval tm;
while (1)
{
// select 每次返回后 会将没有就绪的文件描述符擦除,所以
//每次调用都需要重置这个集合
readfds_temp = readfds;
//重置超时时间!!!!!!!!!!!!!!!!!!!!!!!
tm.tv_sec = 10; //秒
tm.tv_usec = 0; //微秒
ret = select(max_fd + 1, &readfds_temp, NULL, NULL, &tm);
if (ret == -1)
{
ERRLOG("select error");
}
else if (ret == 0)
{
//超时之后的处理。。。。。。。。
printf("超时,等待刷新....\n");
}
else
{
//说明有文件描述符准备就绪
// && ret > 0 是为了提高效率:
// select返回几个就绪的,就只处理几个就绪的就行了
//对于其他没有就绪的文件描述符 无需判断处理
for (i = 0; i < max_fd + 1 && ret > 0; i++)
{
if (FD_ISSET(i, &readfds_temp)) //存在
{ //说明i就绪了
if (i == sockfd)
{
//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
accept_fd = accept(sockfd, NULL, NULL);
if (-1 == accept_fd)
ERRLOG("accept error");
printf("客户端 [%d] 连接了\n", accept_fd);
//将acceptfd也加入到要监视的集合中
FD_SET(accept_fd, &readfds);
//记录最大文件描述符
max_fd = (max_fd > accept_fd ? max_fd : accept_fd);
}
else
{ //说明有客户端发来消息了
accept_fd = i;
//接收客户端发来的数据
if (0 > (ret1 = recv(accept_fd, buff, sizeof(buff), 0)))
{
perror("recv error");
break;
}
else if (0 == ret1) //客户端CTRL+C
{
printf("客户端 [%d] 断开连接\n", accept_fd);
//将该客户端在集合中删除
FD_CLR(accept_fd, &readfds);
//关闭该客户端的套接字
close(i);
continue; //结束本层本次循环
}
else
{
if (0 == strcmp(buff, "quit"))
{
printf("客户端 [%d] 退出了\n", accept_fd);
//将该客户端在集合中删除
FD_CLR(accept_fd, &readfds);
//关闭该客户端的套节字
close(i);
continue; //结束本层本次循环
}
printf("客户端 [%d] 发来数据:[%s]\n", i, buff);
//组装回复给客户端的应答
strcat(buff, "---996");
//回复应答
if (0 > (ret1 = send(accept_fd, buff, sizeof(buff), 0)))
{
perror("send error");
break;
}
}
}
ret--;
}
}
}
}
//关闭监听套接字 一般不关闭
close(sockfd);
return 0;
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
// 1.创建套接字 //IPV4 //TCP
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); //清空
server_addr.sin_family = AF_INET; // IPV4
//端口号 填 8888 9999 6789 ...都可以
// atoi字符串转换成整型数
// htons将无符号2字节整型 主机-->网络
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
// inet_addr字符串转换成32位的网络字节序二进制值
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
// 3.将套接字和网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("bind error");
//将套接字设置成被动监听状态
if (-1 == listen(sockfd, 10))
ERRLOG("listen error");
return sockfd;
}
客户端—02client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERRLOG(errmsg) do{\
printf("%s--%s(%d):", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if(3 != argc){
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
ERRLOG("socket error");
//2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//端口号 填 8888 9999 6789 ...都可以
server_addr.sin_port = htons(atoi(argv[2]));
//ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
//与服务器建立连接
if(-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("connect error");
printf("---连接服务器成功---\n");
char buff[128] = {0};
while(1){
scanf("%s", buff);
int ret = 0;
if(0 > (ret = send(sockfd, buff,sizeof(buff), 0)))
ERRLOG("send error");
if(0 == strcmp(buff, "quit"))
break;
if(0 > (ret = recv(sockfd, buff,sizeof(buff), 0)))
ERRLOG("recv error");
printf("收到服务器回复:[%s]\n", buff);
}
//关闭套接字
close(sockfd);
return 0;
}