服务器模型--IO多路复用--select--并发服务器

要求:用select创建并发服务器,可以与多个客户端进行通信(监听键盘、socket、多个acceptfd)

其(select)基本思想是:

○ 先构造一张有关描述符的表(最大1024),然后调用一个函数。

○ 当这些文件描述符中的一个或多个已准备好进行 I/O时函数才返回。

○ 函数返回时告诉进程哪个描述符已就绪,可以进行 I/O操作。

  1.  构造一张关于文件描述符的表 fd_set
  2. 清空表 FD_ZERO
  3. 将关心的文件描述符添加到表中 FD_SET
  4. 调用 select函数,监听
  5. 判断是哪一个或者哪些文件描述符发生了事件 FD_ISSET
  6. 做对应的逻辑处理

思想:

创建一个表,监听服务器的变化,只要有客户端请求连接,就建立连接,同时把建立连接的客户端文件描述符加入到表中。

同时监听客户端的文件描述符,当客户端发送消息时,文件描述符发生变化,此时服务器就响应对应的文件描述符,接收客户端的消息并输出。

但是因为select的特点,每次都会清空未发生变化的文件描述符,所以当一个客户端的文件描述符发生变化时,当服务器响应完之后,其他没有发送消息的客户端对应的文件描述符就会被清除掉,导致下次无法进行监听。

所以我们创建两个表,其中一个专门用来备份(rfds),专门监听客户端的连接请求,并把建立连接的文件描述符添加到这个表里,循环监听客户端时,每次监听完客户端发送的消息清空其他的客户端之后,下次循环开始,把rfds表中的的内容赋值给cli表,这样就会做到每次都可以监听到所有客户端发送的消息。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>

/*
创建并发服务器,可以与多个客户端进行通信
*/

int main(int argc, char const *argv[])
{

    int res;
    char buf[128] = "";

    // 1.创建流式套接字
    int serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if (serverfd < 0)
    {
        perror("serverfd socket error");
        return -1;
    }
    printf("server socket scuess\n");

    // 2.指定本地的网络信息  struct sockaddr_in
    struct sockaddr_in myaddr;

    // 长度
    socklen_t addrlen = sizeof(myaddr);
    memset(&myaddr, 0, addrlen);
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    myaddr.sin_port = htons(atoi(argv[1]));

    // 3.绑定套接字  bind()
    // 绑定自己的地址
    res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
    if (res < 0)
    {
        perror("bind error");
        return -1;
    }
    printf("bind scuess\n");

    // 4.监听套接字  listen()
    res = listen(serverfd, 5);
    if (res < 0)
    {
        perror("listen error");
        return -1;
    }
    printf("listen scuess\n");

    // select部分
    // 创建表
    fd_set rfds;
    fd_set cli;
    int acceptfd;

    // 清空表
    FD_ZERO(&rfds);
    FD_ZERO(&cli);
    // 将关系你的文件描述符加到表中
    FD_SET(0, &rfds);
    FD_SET(serverfd, &rfds);
    // 最大文件描述符
    int maxfd = serverfd;

    while (1)
    {
        // rfds作为表的备份
        // cli每次监听都会清空表中没有响应的文件描述符,等下次循环的时候,表中的文件描述符就变化了
        // 每次循环开始前,都把备用表rfds的文件描述符给cli,这样就可以做到每次cli都可以监听所有的客户端acceptfd
        cli = rfds;
        // 调用select,启动监听
        int sel = select(maxfd + 1, &cli, NULL, NULL, NULL);

        if (sel < 0)
        {
            perror("server select error");
            return -1;
        }

        if (FD_ISSET(0, &cli)) // 判断终端
        {
            fgets(buf, sizeof(buf), stdin);
            printf("stdin: %s\n", buf);
        }

        if (FD_ISSET(serverfd, &cli)) // 判断客户端
        {

            // 5.链接客户端的请求  accept()
            struct sockaddr_in acceptaddr;
            acceptfd = accept(serverfd, (struct sockaddr *)&acceptaddr, &addrlen);
            if (acceptfd < 0)
            {
                perror("accept error");
                return -1;
            }
            printf("acceptfd: %d \n", acceptfd);
            printf("新的连接过来了\n");
            printf("ip = %s, port = %d\n", inet_ntoa(acceptaddr.sin_addr),
                   ntohs(acceptaddr.sin_port));

            // 把新连接的客户端的文件描述符加到备用表中,
            FD_SET(acceptfd, &rfds);
            // 选择新的最大的文件描述符
            if (acceptfd > maxfd)
            {
                maxfd = acceptfd;
            }
        }

        // 循环判断客户端的文件描述符是否发生事件,客户端文件描述符发生事件,即客户端发送了消息
        for (int i = serverfd + 1; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &cli)) // 判断客户端
            {
                // 6.接收/发送数据  recv()/send()
                char buf[128] = "";
                ssize_t num;
                // 每次接收前清空buf
                memset(buf, 0, sizeof(buf));
                num = recv(i, buf, sizeof(buf), 0);
                // num为接收到的字符个数
                if (num < 0) // 接收失败
                {
                    perror("recv error");
                    return -1;
                }
                else if (num == 0) // 客户端退出
                {
                    perror("client exit");
                    // 客户端退出之后,关闭对应的文件描述符,并从表中删除对应文件描述符
                    close(i);
                    FD_CLR(i, &rfds);
                    if (i == maxfd)
                    {
                        maxfd--;
                    }
                }
                else // 正常接收到字符
                {
                    printf(" acceptfd:%d buf: %s \n", i, buf);
                }
            }
        }

        memset(buf, 0, sizeof(buf));
    }

    close(serverfd);
    close(acceptfd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值