IO多路复用(select)实现TCP并发服务器-使用select函数实现超时检测-传输层

使用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 不在   非0int  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;
}

执行结果

在这里插入图片描述

6.非原创

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`select` 函数是一种 I/O 多复用的机制,用于同时监听多个文件描述符的状态变化。它可以使用单个系统调用同时监视多个文件描述符,并在有一个或多个文件描述符就绪时通知应用程序。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:待监视的最大文件描述符加 1。 - `readfds`:可读文件描述符集合。 - `writefds`:可写文件描述符集合。 - `exceptfds`:异常条件文件描述符集合。 - `timeout`:超时时间,如果为 `NULL` 则为阻塞模式,即一直等待直到有文件描述符就绪;如果为零时间(`tv_sec` 和 `tv_usec` 均为 0),则为非阻塞模式,即立即返回;否则为指定超时时间。 `select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则表示超时;如果返回值为 -1,则表示出错。 使用 `select` 函数的一般流程如下: 1. 初始化需要监视的文件描述符集合。 2. 调用 `select` 函数等待文件描述符就绪。 3. 检查返回值确定哪些文件描述符已经就绪。 4. 处理就绪的文件描述符。 5. 重复上述步骤。 需要注意的是,`select` 函数有一些限制,比如只能监视的文件描述符数量有限,一般为 1024 或更小。此外,在某些平台上,使用 `select` 函数可能会有性能上的限制,可以考虑使用更高效的机制,如 `poll` 或 `epoll`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值