基于I/O复用的服务器端搭建

一、基本原理

        I/O复用(I/O Multiplexing)是一种允许单个进程或线程同时监视多个文件描述符(通常是网络套接字)的机制,以检测一个或多个文件描述符是否处于可读、可写或异常状态。这样可以实现在一个线程中处理多个I/O操作,提高系统的并发性能。

        多进程服务器端:针对每一个客户端创建一个进程提供服务,很消耗计算机性能

        I/O复用服务器端:无论连接多少客户端,提供服务的进程只有一个。

二、select()函数实现IO复用技术

        运用select()函数是最具代表性的实现复用服务器端的方法。使用select函数可以将多个文件描述符集中到一起统一监视。

        1、设置文件描述符

FD_ZERO(fd_set*fdset):将fd_set变量的所有位初始化为0。
FD_SET(int fd, fd_set*fdset):在参数fdset指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set*fdset):在参数fdset指向的变量中清除文件描述符fd的信息。
FD_ISSET((int fd, fd_set*fdset):若参数fdset指向的变量中包含文件描述符fd的信息,则返回真(true)。

        使用示例:

int main()
{
    fd_set set;//定义fd_set变量
    FD_ZERO(&set);//将set变量全部置零   0000000000.........
    FD_SET(1, &set);//将set变量的1位置一,即将set变量的1位置为监视对象。 010000000000.......
    FD_SET(2, &set);//将set变量的2位置一,即将set变量的2位置为监视对象。 011000000000.......
    FD_CLR(2, &set);//将set变量的2位置零,即取消对set变量的2位的监视状态。 010000000000......
}

 2、select()函数

        函数原型:

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfd, fd_set*readset, fd_set*writeset, fd_set*exceptset, const struct timeval *timeout);
/*
   成功返回大于0的值,失败返回-1,该值是发生事件的文件描述符数
   maxfd:监视对象文件描述符的数量
   readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值
   writeset:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
   exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
   timeout:调用select函数后,为防止陷入无限阻塞的状态,传递超时信息
*/

struct timeval
{
    long tv_sec;//seconds   秒
    long tv_usec;//microseconds   微秒
}

 三、实现I/O复用服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 1024

int main()
{
    char buf[BUF_SIZE ];//接收缓存区
    //1、创建服务器套接字信息
    int ser_sock = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in seraddr;
    memset(&seraddr, 0, sizeof(seraddr));
    seraddr.sin_family = AF_INET;//指定IPV4地址族
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(8888);//指定服务器端口
    int seraddr_len = sizeof(seraddr);
    //2、绑定服务器信息
    if(bind(ser_sock, (struct sockaddr*)&seraddr, seraddr_len) == -1)
    {
        printf("server bind error\n");
        return -1;
    } 
    //3、监听客户端连接  5个客户端
    if(listen(ser_sock, 5) == -1)
    {
        printf("server listen error\n");
        return -1;
    }
    //4、初始化客户端信息
    int cli_sock;
    struct sockaddr_in cliaddr;
    memset(&cliaddr, 0, sizeof(cliaddr));
    int cliaddr_len = sizeof(cliaddr);
    //初始化I/O信息
    struct timeval timeout;
    fd_set reads,cpy_reads;  //默认大小1024 bit
    int fd_max, fd_num; //fa_max:最大文件描述符的值  fd_num:文件描述符的数量
    FD_ZERO(&reads);//都置零
    FD_SET(ser_sock, &reads);//将文件描述符ser_sock对应位置一,即监视该文件描述符
    fd_max = ser_sock;//如果没有其他文件描述符被创建,则该文件描述符最大,且为3,0:标准输入(stdin), 1:标准输出 (stdout),2:标准错误(stderr)。文件描述符范围3 到 1023(通常)
     
    while(1)
    {
        cpy_reads = reads;//cpy_reads:变化后, reads:变化前
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;
        if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1)//返回文件描述符数量,因为文件描述符从0开始,所以加一返回的才是数量
            break;
        if(fd_num == 0)//没有监视的文件描述符
            countinue;
        for(int i = 0; i < fd_max+1; i++)
        {
            if(FD_ISSET(i, &cpy_reads))//第i位包含监视的文件描述符信息 如果发生变化
            {
                if(i == ser_sock)//如果是服务器文件描述符,表示有新客户端连接请求
                {
                    cli_sock = accept(ser_sock, (struct sockaddr*)&cliaddr, &cliaddr_len);
                    FD_SET(cli_sock, &reads);//添加监视信息到变化前的fd_set型变量
                    if(fd_max < cli_sock)
                    {
                        fd_max = cli_sock;
                    }
                    printf("connected client: %d\n", cli_sock);
                }
                else
                {
                    int len = read(i, buf, BUF_SIZE);//读取对应文件描述符数据
                    if(len == 0)
                    {
                        FD_CLR(i, &reads);//移除该客户端的文件描述符
                        close(i);//关闭该客户端文件描述符连接
                        printf("closed client: %d\n", i);
                    }
                    else
                    {
                          //可执行打印数据等操作,此处为接收数据之后的操作
                    }

                }
                
            }

        }
    }
    close(ser_sock);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值