网络编程:IO多路复用

本文详细介绍了如何在操作系统支持和不支持的情况下,利用select和poll实现TCP并发服务器的多任务并发执行,通过IO多路复用技术处理多个阻塞的网络连接请求。
摘要由CSDN通过智能技术生成

一、使用目的

1.在有操作系统时,想让多任务并发执行,可以使用多进程或多线程完成多任务并发执行

2.在没有操作系统的情况下,就不能使用多进程或多线程了,但是还仍然要让多任务并发执行,此时我们引入了IO多路复用技术

3.原理:将多个阻塞任务的文件描述符,统一放到一个检测容器中,然后用一个阻塞函数进行管理,如果检测容器中有一个或多个文件描述符对应的事件发生,就会解除阻塞,进而去执行相应的函数

二、select实现TCP并发服务器

服务器

#include <head.h>

#define SER_PORT 8888
#define SER_IP "192.168.250.100"
int main(int argc, char const *argv[])
{
    // 1.创建一个套接字
    int sfd = -1;
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("%d success\n", sfd);

    // 端口号快速重用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt");
        return -1;
    }

    // 2.绑定端口号和IP
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    // 2.2绑定
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind success\n");

    // 3.将套接字设置为被动监听
    if (listen(sfd, 128) == -1)
    {
        perror("listen");
        return -1;
    }
    printf("listen success\n");

    // 4.阻塞等待客户端的连接请求
    int newfd = -1;
    // 定义结构体变量接受对方地址信息结构体
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    char sbuf[128] = "0";

    // 定义一个文件描述符集合
    fd_set readfds;
    fd_set tempfds;

    // 将集合清空
    FD_ZERO(&readfds);

    // 将要被检测的文件描述符放入集合
    FD_SET(0, &readfds);
    FD_SET(sfd, &readfds);

    int maxfd = sfd;                  // 记录当前容器中的最大文件描述符
    struct sockaddr_in cin_arr[1024]; // 存储客户端地址信息结构体的数据

    while (1)
    {
        // 将readfds备份
        tempfds = readfds;
        int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        if (res == -1)
        {
            perror("select");
            return -1;
        }
        else if (res == 0)
        {
            printf("time out\n");
            return -1;
        }

        // 当程序执行到此,说明集合中有事件产生,此时集合中只剩下本次触发事件的文件描述符

        // 判断sfd是否触发事件
        for (int i = 0; i <= maxfd; i++)
        {
            // 如果不是触发事件的文件描述符,直接跳过
            if (!FD_ISSET(i, &tempfds))
            {
                continue;
            }

            // 程序执行至此,表示当前i这个文件描述符触发了事件

            if (i == sfd)
            {
                if ((newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen)) == -1)
                {
                    perror("accept");
                    return -1;
                }
                printf("%s %d:发来连接请求\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
                // 将客户端地址信息结构体放入数组容器中
                cin_arr[newfd] = cin;

                // 将newfd放入readfds容器中参与检测
                FD_SET(newfd, &readfds);

                // 可能要更新maxfd
                if (newfd > maxfd)
                {
                    maxfd = newfd;
                }

                // 判断0号文件描述符是否触发事件
            }
            else if (i == 0)
            {
                fgets(sbuf, sizeof(sbuf), stdin); // 从终端输入数据
                sbuf[strlen(sbuf) - 1] = 0;
                // 判断输入的字符串值
                if (strcmp(sbuf, "quit") == 0)
                {
                    printf("服务器已经关闭\n");
                    return -1;
                }
                printf("触发了键盘输入事件:%s\n", sbuf);

                // 将消息发送给所有客户端
                for (int i = 4; i <= maxfd; i++)
                {
                    send(i, sbuf, sizeof(sbuf), 0);
                }
                printf("发送成功\n");
            }
            else
            {
                // 说明某个客户端发来消息,遍历所有的客户端,找哪个发来的消息
                // 收发数据
                char rbuf[128] = "0";
                bzero(rbuf, sizeof(rbuf));

                int res = recv(i, rbuf, sizeof(rbuf) - 1, 0);
                if (res == 0)
                {
                    printf("客户端已经下线\n");
                    // 关闭跟客户端通信的套接字
                    close(i);

                    // 将当前文件描述符移出容器
                    FD_CLR(i, &readfds);

                    // 可能要更新maxfd
                    for (int k = maxfd; k >= sfd; k--)
                    {
                        if (FD_ISSET(k, &readfds))
                        {
                            maxfd = k;
                            break;
                        }
                    }
                    continue;
                }
                printf("%s  %d:%s\n", inet_ntoa(cin_arr[i].sin_addr), ntohs(cin_arr[i].sin_port), rbuf);
                // 加个笑脸回过去
                strcat(rbuf, "*_*");
                send(i, rbuf, strlen(rbuf), 0);
                printf("发送成功\n");
            }
        }
    }
    close(sfd);
    return 0;
}

客户端

#include <head.h>

#define SER_PORT 8888
#define SER_IP "192.168.250.100"
#define CLI_PORT 9999
#define CLI_IP "192.168.250.100"
int main(int argc, char const *argv[])
{

    // 1.创建套接字
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("套接字创建成功\n");
    // 设置端口号快速重用
    int reuse = 1;
    if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("端口号快速重用成功\n");

    // 2.绑定(不必写)
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;
    cin.sin_port = htons(CLI_PORT);
    cin.sin_addr.s_addr = inet_addr(CLI_IP);
    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind success\n");

    // 3.连接服务器
    // 3.1填充要连接服务器的地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);          // 服务器的端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); // 服务器的IP地址

    // 3.2连接服务器
    if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("connect");
        return -1;
    }
    printf("连接成功\n");

    // 定义一个文件描述符集合
    fd_set readfds;
    fd_set tempfds;

    // 将集合清空
    FD_ZERO(&readfds);

    // 将要被检测的文件描述符放入集合
    FD_SET(0, &readfds);
    FD_SET(cfd, &readfds);

    int maxfd = cfd;                  // 记录当前容器中的最大文件描述符
    struct sockaddr_in sin_arr[1024]; // 存储服务器地址信息结构体的数据

    // 4.收发数据
    char buf[128] = "0";
    while (1)
    {
        // 将readfds备份
        tempfds = readfds;
        int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        if (res == -1)
        {
            perror("select");
            return -1;
        }
        else if (res == 0)
        {
            printf("time out\n");
            return -1;
        }
        // 当程序执行到此,说明集合中有事件产生,此时集合中只剩下本次触发事件的文件描述符

        // 判断sfd是否触发事件
        for (int i = 0; i <= maxfd; i++)
        {
            // 如果不是触发事件的文件描述符,直接跳过
            if (!FD_ISSET(i, &tempfds))
            {
                continue;
            }

            if (i == 0)
            {
                fgets(buf, sizeof(buf), stdin); // 从终端上获取一个字符串
                buf[strlen(buf) - 1] = '\0';

                // 判断输入的字符串值
                if (strcmp(buf, "quit") == 0)
                {
                    printf("客户端已经退出\n");
                    return -1;
                }
                printf("触发了键盘输入事件:%s\n", buf);
                // 将数据发送给服务器
                send(cfd, buf, strlen(buf), 0);
                printf("发送成功\n");
            }
            else if (i == cfd)
            {

                // 将客户端地址信息结构体放入数组容器中
                sin_arr[cfd] = sin;

                // 将newfd放入readfds容器中参与检测
                FD_SET(cfd, &readfds);

                // 可能要更新maxfd
                if (cfd > maxfd)
                {
                    maxfd = cfd;
                }

                // 将字符数组清空
                bzero(buf, sizeof(buf));
                int res = recv(i, buf, sizeof(buf) - 1, 0);
                if (res == 0)
                {
                    printf("服务器已经关闭\n");
                    // 关闭跟服务器通信的套接字
                    close(i);

                    // 将当前文件描述符移出容器
                    FD_CLR(i, &readfds);

                    // 可能要更新maxfd
                    for (int k = maxfd; k >= cfd; k--)
                    {
                        if (FD_ISSET(k, &readfds))
                        {
                            maxfd = k;
                            break;
                        }
                    }
                    continue;
                }
                printf("%s  %d:%s\n", inet_ntoa(sin_arr[i].sin_addr), ntohs(sin_arr[i].sin_port), buf);
            }
        }
    }
    // 5.关闭套接字
    close(cfd);
    return 0;
}

三、poll实现TCP多路复用

服务器

#include <head.h>
#define SER_PORT 8888
#define SER_IP "192.168.250.100"
int main(int argc, char const *argv[])
{
    // 1.创建一个套接字
    int sfd = -1;
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("套接字创建成功\n");

    // 端口号快速重用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt");
        return -1;
    }

    // 2.绑定端口号和IP
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    // 2.2 绑定
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind success\n");

    // 3.将套接字设置为被动监听
    if (listen(sfd, 128) == -1)
    {
        perror("listen");
        return -1;
    }
    printf("listen success\n");

    // 4.阻塞等待客户端的连接请求
    int newfd = -1;
    // 定义结构体变量接受对方地址信息结构体
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    char buf[128] = "0";

    // 使用poll完成0号文件描述符和cfd文件描述符的多路复用
    // 准备文件描述符容器
    struct pollfd pfds[3];
    pfds[0].fd = 0;          // 文件描述符
    pfds[0].events = POLLIN; // 检测读事件

    pfds[1].fd = sfd;        // 文件描述符
    pfds[1].events = POLLIN; // 检测读事件

    pfds[2].fd = newfd;
    pfds[2].events = POLLIN;

    int maxfd = sfd;                  // 记录当前容器中的最大文件描述符
    struct sockaddr_in cin_arr[1024]; // 存储客户端地址信息结构体的数据

    while (1)
    {
        int res = poll(pfds, 3, -1);
        //           文件描述符,要检测的文件描述符个数,-1表示永久的等待
        if (res == -1)
        {
            perror("poll");
            return -1;
        }
        else if (res == 0)
        {
            printf("time out\n");
            return -1;
        }
        // 程序执行至此,说明检测的文件描述符集合中有事件产生
        // 判断是否为0号文件描述符产生事件

        // 判断释放为sfd文件描述符中产生事件
        if (pfds[1].revents == POLLIN)
        {

            if ((newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen)) == -1)
            {
                perror("accept");
                return -1;
            }
            printf("%s %d:发来连接请求\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
            cin_arr[newfd] = cin;
            // 可能要更新maxfd
            if (newfd > maxfd)
            {
                maxfd = newfd;
            }
        }
       /*if (pfds[2].revents == POLLIN)
        {
            for (int i = 3; i <= maxfd; i++)
            {
                // 说明某个客户端发来消息,遍历所有的客户端,找哪个发来的消息
                // 收发数据
                char buf[128] = "0";
                bzero(buf, sizeof(buf));

                printf("接收成功\n");
                int res = recv(i, buf, sizeof(buf), 0);

                if (res == 0)
                {
                    printf("客户端已经下线\n");
                    // 关闭跟客户端通信的套接字
                    close(newfd);
                }
                printf("%s  %d:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
                // 加个笑脸回过去
                strcat(buf, "*_*");
                send(i, buf, strlen(buf), 0);
                printf("发送成功\n");
            }
        }*/
        if (pfds[0].revents == POLLIN)
        {
            fgets(buf, sizeof(buf), stdin); // 从终端上获取一个字符串
            buf[strlen(buf) - 1] = '\0';
            // 判断输入的字符串值
            if (strcmp(buf, "quit") == 0)
            {
                printf("客户端已经关闭\n");
                break;
            }
            printf("触发了键盘输入事件:%s\n", buf);
            // 将消息发送给所有客户端

            send(newfd, buf, sizeof(buf), 0);

            printf("发送成功\n");
        }
    }
    close(sfd);
    return 0;
}

客户端

#include <head.h>
#define SER_PORT 6666
#define SER_IP "192.168.250.100"
#define CLI_PORT 7777
#define CLI_IP "192.168.250.100"

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

    // 1.创建套接字
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("套接字创建成功\n");
    // 设置端口号快速重用
    int reuse = 1;
    if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("端口号快速重用成功\n");

    // 2.绑定(不必写)
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;
    cin.sin_port = htons(CLI_PORT);
    cin.sin_addr.s_addr = inet_addr(CLI_IP);

    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind");
        return -1;
    }

    // 3.连接服务器
    // 3.1填充要连接服务器的地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);          // 服务器的端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); // 服务器的IP地址

    // 3.2连接服务器
    if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("connect");
        return -1;
    }
    printf("连接成功\n");

    // 使用poll完成0号文件描述符和cfd文件描述符的多路复用
    // 准备文件描述符容器
    struct pollfd pfds[2];
    pfds[0].fd = 0;          // 文件描述符
    pfds[0].events = POLLIN; // 检测读事件

    pfds[1].fd = cfd;        // 文件描述符
    pfds[1].events = POLLIN; // 检测读事件

    // 4.收发数据
    char buf[128] = "0";
    while (1)
    {
        int res = poll(pfds, 2, -1); // 阻塞检测集合中是否有事件产生
        if (res == -1)
        {
            perror("poll");
            return -1;
        }
        else if (res == 0)
        {
            printf("time out\n");
            return -1;
        }

        // 程序执行至此,说明检测的文件描述符集合中有事件产生

        // 判断是否为0号文件描述符产生事件
        if (pfds[0].revents == POLLIN)
        {
            fgets(buf, sizeof(buf), stdin); // 从终端上获取一个字符串
            buf[strlen(buf) - 1] = '\0';
            // 判断输入的字符串值
            if (strcmp(buf, "quit") == 0)
            {
                printf("客户端已经关闭\n");
                break;
            }
            printf("触发了键盘输入事件:%s\n", buf);

            // 将数据发送给服务器
            send(cfd, buf, strlen(buf), 0);
        }

        // 判断释放为cfd文件描述符中产生事件
        if (pfds[1].revents == POLLIN)
        {
            // 将字符数组清空
            bzero(buf, sizeof(buf));
            recv(cfd, buf, sizeof(buf), 0);
            printf("收到服务器的消息为:%s\n", buf);
        }
    }
    // 5.关闭套接字
    close(cfd);

    return 0;
}

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值