网络编程(学习)2024.8.30

目录

IO多路复用  select、poll、epoll

IO多路复用机制 

一.select

1.函数

2.流程

3.案例使用select创建全双工客户端

4.并发服务器

5.案例使用select创建全双工服务端

二.poll

1.函数

2.流程

3.案例使用poll创建全双工客户端

4.案例使用poll创建全双工服务端

三、epoll

1.流程

2.案例使用epoll创建全双工服务端

select,poll和epoll的特点:

1.select特点

2.poll特点

3.epoll特点

IO多路复用  select、poll、epoll

案例分析:键盘鼠标事件

同时对键盘和鼠标进行监听,当敲击键盘按下回车,就打印键盘输入的东西,动鼠标就要打印鼠标写入的内容。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#define N 64
int main(int argc, char const *argv[])
{
    int mouse = open("/dev/input/mouse0", O_RDONLY);
    if (mouse < 0)
    {
        perror("open失败");
        return -1;
    }
    char buf[N];
    while (1)
    {
        memset(buf, 0, N);
        gets(buf);
        printf("buf:%s\n", buf);
        int ret = read(mouse, buf, N);
        if (ret < 0)
        {
            perror("read失败");
            return -1;
        }
        else
        {
            printf("mouse:%s\n", buf);
        }
    }
}

IO多路复用机制 

使用I/O多路复用技术。其基本思想是:

1.先构造一张有关描述符的表,然后调用一个函数。
2.当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
3.函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。

一.select

1.函数

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

 int select(int nfds(轮询的文件描述符个数), fd_set *readfds(读文件), fd_set *writefds(写文件),fd_set *exceptfds(异常文件), struct timeval *timeout(超时时长));

一般写:select(int nfds,fd_set *readfds,NULL,NULL,NULL);

void FD_CLR(int fd, fd_set *set);        //将某一文件描述符在表里去除
int  FD_ISSET(int fd, fd_set *set);        //判断某一文件描述符是否在表里
void FD_SET(int fd, fd_set *set);        //将某一文件描述符放入表中
void FD_ZERO(fd_set *set);        //将表置零

2.流程

第一步:建表初始化
第二步:填表
第三步:监听表
第四步:判断,操作

e26c46e3e64c4269ba46d4f5e45ce5d6.png

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>

#define N 64
int main(int argc, char const *argv[])
{
    int mouse = open("/dev/input/mouse0", O_RDONLY);
    if (mouse < 0)
    {
        perror("open失败");
        return -1;
    }
    char buf[N];

    // 第一步:建表初始化
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    // 第二步:填表
    FD_SET(0, &readfds);
    FD_SET(mouse, &readfds);
    // 第三步:监听表
    while (1)
    {
        memset(buf, 0, N);
        temphfds = readfds;
        int ret = select(4, &tempfds, NULL, NULL, NULL);
        // 第四步:判断,操作
        if (ret == -1)
        {
            perror("select失败");
            return -1;
        }
        if (FD_ISSET(0, &tempfds))
        {
            gets(buf);
            printf("buf:%s\n", buf);
        }
        if (FD_ISSET(mouse, &tempfds))
        {
            int n = read(mouse, buf, N);
            if (n < 0)
            {
                perror("read失败");
                return -1;
            }
            else
            {
                printf("mouse:%s\n", buf);
            }
        }
    }

    return 0;
}

3.案例使用select创建全双工客户端

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

int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);

    // 2.连接
    unsigned short post = 0;
    char ip[15];
    printf("请输入ip地址");
    scanf("%s", ip);
    getchar();
    printf("请输入端口号");
    scanf("%hd", &post);
    getchar();
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(post);
    saddr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(saddr);
    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect失败\n");
        return -1;
    }

    // 3.接收
#define N 64
    char buf[N];
    while (1)
    {
        // 第一步:建表初始化
        fd_set readfds, tempfds;
        FD_ZERO(&readfds);
        // 第二步:填表
        FD_SET(0, &readfds);
        FD_SET(sockfd, &readfds);
        // 第三步:监听表
        while (1)
        {
            memset(buf, 0, N);
            tempfds = readfds;
            int ret = select(4, &tempfds, NULL, NULL, NULL);
            // 第四步:判断,操作
            if (ret == -1)
            {
                perror("select失败");
                return -1;
            }
            if (FD_ISSET(0, &tempfds))
            {
                scanf("%s", buf);
                send(sockfd, buf, N, 0);
            }
            if (FD_ISSET(sockfd, &tempfds))
            {
                int ret = recv(sockfd, buf, N, 0);
                printf("服务端:%s\n", buf);
            }
        }
    }
    close(sockfd);
    return 0;
}

4.并发服务器

可以同时接收多个客户端的连接

5.案例使用select创建全双工服务端

sockfd只要创建并监听listen,就可以接收连接请求,即只要sockfd的读缓冲区可读,就可以创建连接。
客户端的连接请求会发给sockfd,如果可以建立连接的话,会在sockfd的缓冲区内保存
accept其实就是去sockfd的缓冲区里取连接

select创建并发服务器过程

创建并发服务器
创建连接      accept  --> sockfd
发送消息         gets    --> 0
接收          recv    --> 所有已连接的acceptfd

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

#define N 64
char buf[N];
#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    // saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
#if 0
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#else
    saddr.sin_addr.s_addr = INADDR_ANY;
#endif

    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        ERR_MSG("bind失败");
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        return -1;
    }
    printf("listen成功\n");

    // 第一步:建表初始化
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    FD_SET(0, &readfds);
    int max = sockfd;

    while (1)
    {
        memset(buf, 0, N);
        tempfds = readfds;
        int ret = select(max + 1, &tempfds, NULL, NULL, NULL);
        if (ret == -1)
        {
            perror("select失败");
            return -1;
        }
        if (FD_ISSET(sockfd, &tempfds))
        {
            // 4.accept阻塞等待链接
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
            if (acceptfd < 0)
            {
                ERR_MSG("accept失败\n");
                return -1;
            }
            printf("acceptfd:%d\n", acceptfd);
            printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
            FD_SET(acceptfd, &readfds);
            if (max < acceptfd)
            {
                max = acceptfd;
            }
        }
        // 5.发送
        else if (FD_ISSET(0, &tempfds))
        {
            scanf("%s", buf);
            for (int i = 4; i <= max; i++)
            {
                if (FD_ISSET(i, &readfds))
                {
                    send(i, buf, N, 0);
                }
            }
        }
        // 6.接收
        for (int n = 4; n <= max; n++)
        {
            if (FD_ISSET(n, &tempfds))
            {
                int ret = recv(n, buf, N, 0);
                if (ret < 0)
                {
                    perror("recv失败");
                    return -1;
                }
                else if (ret > 0)
                {
                    printf("客户端:%s\n", buf);
                }
                else
                {
                    printf("客户端acceptfd:%d退出\n", n);
                    FD_CLR(n, &readfds);
                    close(n);
                    while (!FD_ISSET(max, &readfds))
                    {
                        max--;
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

二.poll

1.函数

#include <poll.h>

int poll(suct trpollfd *fds, nfds_t nfds, int timeout);

参数:

        fds:创建的pollfd结构体类型的数组

        nfds:数组的大小

        timeout:超时检测的时间,一般不用的话设置为-1

返回值:

        成功:0

        失败:-1

struct pollfd {
        int   fd;        //第一个成员变量 fd是向poll说明要监听哪个文件描述符
        short events;        //第二个成员变量 events 是向poll说明要对这个文件描述符的哪种事件进行监听,一般设置为POLLIN
        short revents;         //第三个成员变量 revents 是poll函数自动生成,当fd发生了events事件时,poll函数会将events(POLLIN)写入revents
};

2.流程

3.案例使用poll创建全双工客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>

int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);

    // 2.连接
    unsigned short post = 0;
    char ip[15];
    printf("请输入ip地址");
    scanf("%s", ip);
    getchar();
    printf("请输入端口号");
    scanf("%hd", &post);
    getchar();
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(post);
    saddr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(saddr);
    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect失败\n");
        return -1;
    }

    // 3.接收
#define N 64
    char buf[N];
    while (1)
    {
        // 第一步:建表初始化
        struct pollfd fds[2];

        // 第二步:填表
        fds[0].fd = 0;
        fds[0].events = POLLIN;
        fds[1].fd = sockfd;
        fds[1].events = POLLIN;

        // 第三步:监听表
        while (1)
        {
            memset(buf, 0, N);
            poll(fds, 2, -1);
            // 第四步:判断,操作
            // if (ret == -1)
            // {
            //     perror("select失败");
            //     return -1;
            // }
            for (int i = 0; i <= 1; i++)
            {
                if (fds[i].revents == POLLIN)
                {
                    if (i == 0)
                    {
                        scanf("%s", buf);
                        send(sockfd, buf, N, 0);
                    }
                    if (i == 1)
                    {
                        int ret = recv(sockfd, buf, N, 0);
                        if (ret < 0)
                        {
                            perror("recv失败");
                            return -1;
                        }
                        else
                        {
                            printf("服务端:%s\n", buf);
                        }
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

4.案例使用poll创建全双工服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>

#define N 64
char buf[N];
#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
    // #if 0
    //     saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    // #else
    //     saddr.sin_addr.s_addr = INADDR_ANY;
    // #endif

    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        ERR_MSG("bind失败");
        close(sockfd);
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        close(sockfd);
        return -1;
    }
    printf("listen成功\n");

    // 第一步:建表初始化
    struct pollfd fds[100];
    int last = -1;

    // 第二步:填表
    fds[++last].fd = 0;
    fds[last].events = POLLIN;
    fds[last].revents = 0;
    fds[++last].fd = sockfd;
    fds[last].events = POLLIN;
    fds[last].revents = 0;

    while (1)
    {
        memset(buf, 0, N);
        int po = poll(fds, last + 1, -1);
        if (po == -1)
        {
            perror("select失败");
            close(sockfd);
            return -1;
        }
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == sockfd)
                {
                    // 4.accept阻塞等待链接
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
                    if (acceptfd < 0)
                    {
                        ERR_MSG("accept失败\n");
                        return -1;
                    }
                    printf("acceptfd:%d\n", acceptfd);
                    printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
                    fds[++last].fd = acceptfd;
                    fds[last].events = POLLIN;
                    fds[last].revents = 0;
                }
                else if (fds[i].fd == 0)
                {
                    scanf("%s", buf);
                    for (int j = 2; j <= last; j++)
                    {
                        send(fds[j].fd, buf, N, 0);
                    }
                }
                else
                {
                    int ret = recv(fds[i].fd, buf, N, 0);
                    if (ret < 0)
                    {
                        perror("recv失败");
                        close(sockfd);
                        return -1;
                    }
                    else if (ret > 0)
                    {
                        printf("客户端%s:%s\n", inet_ntoa(caddr.sin_addr), buf);
                    }
                    else
                    {
                        printf("客户端acceptfd:%d退出\n", fds[i].fd);
                        close(fds[i].fd);
                        fds[i] = fds[last];
                        last--;
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

三、epoll

1.流程

2.案例使用epoll创建全双工服务端

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("usage: <port>\n");
        return -1;
    }

    // 1.创建套接字-->tcp  流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sockfd:%d\n", sockfd);
    // 2.绑定IP和端口号
    // 填充通信结构体
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;            // 选定ipv4协议族
    saddr.sin_port = htons(atoi(argv[1])); // 绑定端口
    // saddr.sin_addr.s_addr = inet_addr(argv[1]); // 绑定ip地址
#if 1
    saddr.sin_addr.s_addr = INADDR_ANY; // 绑定ip地址
#else
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 绑定ip地址
#endif
    socklen_t len = sizeof(saddr);
    // 绑定
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind ok\n");
    // 3.启动监听 将主动套接字变成被动套接字
    if (listen(sockfd, 8) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen ok\n");

    // 1.创建红黑树,拿到根节点---》建表
    int epfd = epoll_create(99);
    // 2.将关心的文件描述符挂载到树上---》填表
    struct epoll_event event;
    struct epoll_event events[10];

    event.data.fd = sockfd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    // 3.去链表中拿事件
    char buf[128];
    while (1)
    {
        int ret = epoll_wait(epfd, events, 10, -1);
        if (ret < 0)
        {
            perror("errr");
            return -1;
        }
        else if (ret == 0)
        {
            printf("nothing \n");
        }
        else
        {
            for (int i = 0; i < ret; i++)
            {

                if (events[i].data.fd == sockfd)
                {
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                    if (acceptfd < 0)
                    {
                        perror("accept err");
                        return -1;
                    }
                    printf("%d,login\n", acceptfd);
                    event.data.fd = acceptfd;
                    event.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event);
                }
                else
                {
                    int ret = recv(events[i].data.fd, buf, sizeof(buf), 0);
                    if (ret < 0)
                    {
                        perror("recv err");
                        return -1;
                    }
                    else if (ret > 0)
                    {
                        printf("%d:%s\n", events[i].data.fd, buf);
                    }
                    else
                    {
                        printf("%d exit\n", events[i].data.fd);
                        close(events[i].data.fd);
                        event.data.fd = events[i].data.fd;
                        event.events = EPOLLIN | EPOLLET;
                        epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
                    }
                }
            }
        }
    }
    return 0;
}

select,poll和epoll的特点:

1.select特点

优点:可跨平台,Linux、macos、windows都可用
      可以监听多个文件描述符
      轻量级

缺点:最大监听1024个文件描述符,最大监听1020个客户端连接
      每次都要进行轮询,消耗CPU
      每次都要拷贝一遍表

2.poll特点

优点:
1、优化了文件描述符的数量,监听的文件描述符数取决于数组的大小,数组大小受内存容量的限制。
2、不需要每次都拷贝一遍表
缺点:
1、需要轮询
2、只能用在UNIX原生系统下,不支持跨平台

3.epoll特点

优点:
1、超高并发,百万级并发
2、不需要轮询,因为有异步通知机制
3、不需要拷贝表
缺点:
    只能跑在Linux

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值