04--网络属性设置与多路复用

发布于:2025-04-13 ⋅ 阅读:(30) ⋅ 点赞:(0)

一、TCP可靠性分析

二、 scoket 属性设置

1、socket 属性设置表

NAME
       getsockopt, setsockopt - get and set options on sockets
                                获取 和 设置 套接字属性 
SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
       sockfd:网络描述符 
       level: 层次 👉SOL_SOCKET
       optname:属性  
       optval:可变数据,根据不同的属性类型会改变
       optlen:可变数据的大小  
       返回值: 设置/获取 成功  0   设置/获取 失败  -1

2,开启地址复用属性

// 使套接字sockfd关联的地址在套接字关闭后立即释放
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
SO_REUSEADDR:端口与地址复用属性
on = 1:开启    on = 0:关闭

3.UDP 广播属性

只有给192.168.63.255 IP发送UDP广播数据包,当前局域网的所有相同端口的主机都可以接收到数据!

// 设定套接字的广播属性为真
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
SO_BROADCAST:广播属性 💡只有UDP通信协议才支持广播
on = 1:开启    on = 0:关闭

----------------------------UDP发送广播例子--------------------------
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
    // 1.创建UDP 通信对象
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket < 0)
    {
        perror("创建UDP对象失败\n");
        return -1;
    }
    else
    {
        printf("创建UDP对象成功\n");
    }

    // 开启广播功能
    int on = 1;
    int ret = setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
    if (ret != 0)
    {
        printf("开启广播失败\n");
        return -1;
    }
    else
    {
        printf("开启广播成功\n");
    }

    // 2.发送UDP数据包
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;                          // IPV4网络协议
    addr.sin_port = htons(7777);                        // 设置端口号
    addr.sin_addr.s_addr = inet_addr("192.168.63.255"); // 设置广播地址

    char buf[1024] = {"hello"};
    int size = sendto(udp_socket, buf, 5, 0, (struct sockaddr *)&addr, sizeof(addr));

    printf("发送广播数据包成功: %d\n", size);

    // 3.关闭UDP 对象
    close(udp_socket);
}

4.发送/接收缓存区属性

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
    // 1.创建UDP 通信对象
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket < 0)
    {
        perror("创建UDP对象失败\n");
        return -1;
    }
    else
    {
        printf("创建UDP对象成功\n");
    }

    // UDP发送缓存区大小:163840   UDP接收缓存区大小:163840

    // 设置发送与接收缓存区大小
    int rev_size = 2048;
    int snd_size = 4096;
    setsockopt(udp_socket, SOL_SOCKET, SO_RCVBUF, &rev_size, sizeof(rev_size)); // 获取接收缓存区大小
    setsockopt(udp_socket, SOL_SOCKET, SO_SNDBUF, &snd_size, sizeof(rev_size)); // 获取发送缓存区大小

    // 获取网络发送与接收缓存区大小
    int len = sizeof(rev_size);
    getsockopt(udp_socket, SOL_SOCKET, SO_RCVBUF, &rev_size, &len); // 获取接收缓存区大小
    int len1 = sizeof(snd_size);
    getsockopt(udp_socket, SOL_SOCKET, SO_SNDBUF, &snd_size, &len1); // 获取发送缓存区大小

    printf("UDP发送缓存区大小:%d   UDP接收缓存区大小:%d\n", snd_size, rev_size);

    // 3.关闭UDP 对象
    close(udp_socket);

    //tip:设置的缓存区大小,Linux系统会自动 * 2 
}

 

5.超时属性设置

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
    // 1.创建UDP 通信对象
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket < 0)
    {
        perror("创建UDP对象失败\n");
        return -1;
    }
    else
    {
        printf("创建UDP对象成功\n");
    }

    // 设置UDP超时属性
    struct timeval val;
    val.tv_sec = 3;  // 设置3秒
    val.tv_usec = 0; // 设置0毫秒

    if (setsockopt(udp_socket, SOL_SOCKET, SO_RCVTIMEO, &val, sizeof(val)) != 0)
    {
        perror("超时属性设置失败\n");
        return -1;
    }
    else
    {
        printf("超时属性设置成功3秒\n");
    }

    // 2.发送UDP数据包
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;         // IPV4网络协议
    addr.sin_port = htons(7777);       // 设置端口号
    addr.sin_addr.s_addr = INADDR_ANY; // 设置广播地址
    while (1)
    {
        char buf[1024] = {0};
        printf("等待客户端发送数据包.....\n");
        int size = recvfrom(udp_socket, buf, 1024, 0, NULL, NULL);

        printf("size=%d\n", size);
    }

    // 3.关闭UDP 对象
    close(udp_socket);
}

二、多路复用

多路复用的作用:实现Linux多个阻塞IO接口的数据读取操作,无需多线程或多进行处理阻塞IO,提高处理效率。

1.Linux系统的阻塞IO

阻塞IO就是在读写文件描述时会产生阻塞的状态,这种文件描述符就是阻塞IO。 
1.从键盘获取数据  scanf 
2.读取管道文件    read -> pipe 
3.读取网络socket  read -> tcp_socekt/udp_socket 
4.接收客户端连接请求 accpet  
...... 

--------💡如何解决多个IO的阻塞问题-------
1.利用并发技术(多线程/多进程),每一个阻塞IO都开一个线程或者进程去读取。  ✔️

2.把IO接口设置为非阻塞状态,并轮询读取 
long state = fcntl(sockfd, F_GETFL);
state |= O_NONBLOCK;  //设置为非阻塞状态  
fcntl(sockfd, F_SETFL, state);
while(1)
{
    read(socket,); //频繁系统调用,浪费资源
    read(socket1,);
    read(socket2,);
}

3.异步信号,注册一个信号处理函数,当有数据可读时,发送信号,触发读取函数。

4.select 多路复用去监听,活跃的描述符,当一个描述符活跃时,则读取数据。  ✔️

2.多路复用的设计流程图

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       #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:超时检测
        返回值: 成功  大于   0   
               超时  等于    0   
               失败  小于    0 
            
       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);         //清空监听集合

例子:多路复用设计TCP客户端

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>

int main()
{
    // 1.创建TCP通信对象
    int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (tcp_socket < 0)
    {
        perror("创建对象失败\n");
        return -1;
    }
    else
    {
        printf("创建对象成功\n");
    }

    // 2.设置服务器地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;                        // IPV4网络协议
    addr.sin_port = htons(7777);                      // 设置端口号
    addr.sin_addr.s_addr = inet_addr("192.168.63.1"); // 设置IP地址
    int ret = connect(tcp_socket, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("连接失败\n");
        return -1;
    }
    else
    {
        printf("连接成功\n");
    }

#if 0
    // 读取网络数据
    while (1)
    {
        char buf[1024] = {0};
        read(tcp_socket, buf, 1024); // 阻塞-> tcp_socket描述 3
        printf("recv:%s\n", buf);
    }

    // 发送数据到网络中
    while (1)
    {
        printf("请输入需要发送的网络数据\n");
        char buf[1024] = {0};
        scanf("%s", buf); // 阻塞  -> 标准输入描述 0
        write(tcp_socket, buf, strlen(buf));
    }
#endif

    while (1)
    {
        // 1.清空集合
        fd_set set;
        FD_ZERO(&set);

        // 2.添加阻塞IO描述符到集合中
        FD_SET(0, &set);          // 添加标准输入描述符
        FD_SET(tcp_socket, &set); // 添加网络描述符 3

        // 3.select 开启监听
        printf("开启监听集合中的描述符......\n");
        int ret = select(tcp_socket + 1, &set, NULL, NULL, NULL); // 监听读集合

        if (ret > 0) // 有活跃描述符 /描述符有数据可读
        {
            printf("描述符有数据,请处理\n");

            // 判断是否为输入描述符活跃
            if (FD_ISSET(0, &set))
            {
                printf("0号描述符活跃\n");
                char buf[1024] = {0};
                read(0, buf, 1024);

                // 发送数据到网络中
                write(tcp_socket, buf, strlen(buf));
            }

            // 判断是否为TCP_SOCKET活跃
            if (FD_ISSET(tcp_socket, &set))
            {
                printf("tcp_socket描述符活跃\n");
                char buf[1024] = {0};
                read(tcp_socket, buf, 1024);

                printf("recv:%s\n", buf);
            }
        }
    }
}

3.select超时检测

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>

int main()
{
    // 1.创建TCP服务器对象
    int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (tcp_socket < 0)
    {
        perror("创建对象失败\n");
        return -1;
    }
    else
    {
        printf("创建对象成功\n");
    }

    // 2.设置服务器地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;   // IPV4网络协议
    addr.sin_port = htons(1688); // 设置端口号
    // addr.sin_addr.s_addr = inet_addr("192.168.63.1"); // 设置IP地址
    addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
    int ret = bind(tcp_socket, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("绑定失败\n");
        return -1;
    }
    else
    {
        printf("绑定成功\n");
    }

    // 3.开启监听
    ret = listen(tcp_socket, 10);
    if (ret < 0)
    {
        perror("监听失败\n");
        return -1;
    }
    else
    {
        printf("监听成功\n");
    }

    while (1)
    {

        // 1.清空集合
        fd_set set;
        FD_ZERO(&set);

        // 2.添加阻塞IO描述符到集合中
        FD_SET(tcp_socket, &set); // 添加服务器描述到集合中

        // 设置监听的时间
        struct timeval timeout;
        timeout.tv_sec = 10; // 设置10秒
        timeout.tv_usec = 0;

        // 3.开始监听
        int ret = select(tcp_socket + 1, &set, NULL, NULL, &timeout);

        if (ret == 0) // 超时
        {
            printf("超时未有客户端连接\n");
        }

        if (ret > 0) // 有客户端连接
        {
            if (FD_ISSET(tcp_socket, &set))
            {
                printf("有新客户端连接,开启处理\n");

                int new_scoekt = accept(tcp_socket, NULL, NULL);

                printf("新客户端 %d\n", new_scoekt);
            }
        }
    }
}