0voice-2.1.1-网络io

bind 和 listen

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET; //`AF_INET` 代表 `IPv4` , `AF_INET6` 代表 `IPv6`。
    servaddr.sin_addr.s_addr =  htonl(INADDR_ANY); //绑定网卡地址,0.0.0.0。
    servaddr.sin_port = htons(2000); //0 - 1023端口是系统默认的,从 1024 开始使用。
    
    if (-1 == bind(sockfd , (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
        printf("bind failed: %s\n",strerror(errno));
    }

    listen(sockfd , 10); //设置待处理连接队列的长度 (backlog = 10)
    printf("listen finshed\n");
  • 总的来说就是,监听本地网络所有可用网络接口上的 2000 端口 。
  • INADDR_ANY 是通配地址 0.0.0.0, 代表监听所有地址,当然这里的参数也可以填写本地的其他 IP 地址 。
  • 注意不能反复绑定同一个端口 。
  • sockfd 代表的是服务器的公共入口,它在等待任何潜在的客户端。它不属于任何一个特定的“用户”或“会话”,其职责职责是产生那些最终会与 userinfo 建立映射关系的 fd(即 clientfd)。后文也会说明一个 sockfd 能对应多个 clientfd
  • 监测端口的命令 netstat -anop | grep 2000

accept 和 recv

  1. 版本 \(1\)
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);

    printf("accept\n");
    int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
    printf("accept finished\n");

    char buffer[1024] = {0};
    int count = recv(clientfd , buffer , 1024 , 0);
    printf("RECV %s\n", buffer);

    count = send(clientfd , buffer , count , 0);
    printf("SEND: %d\n", count);
  • 注意 acceptaccept finished 是不同的,accept() 是会进行阻塞的。
  • accept 产生 clientfd
  • 这个版本的缺陷就是只能建立一次 accept ,如果有第二个客户端请求服务,则无法 accept finished
  1. 版本 \(2\)
    while (1) {

        printf("accept\n");
        int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
        printf("accept finished\n");

        char buffer[1024] = {0};
        int count = recv(clientfd , buffer , 1024 , 0);
        printf("RECV %s\n", buffer);

        count = send(clientfd , buffer , count , 0);
        printf("SEND: %d\n", count);
    }
  • 缺陷:单线程的模式,当有 \(1\) , \(2\) , \(3\)客户顺序请求 accept 的时候,只有当 \(1\) 客户 \(recv\) 完毕的时候,\(2\) , \(3\)客户才能 accept finished, 在之前所有的 acceptrecv 请求只能被阻塞。
  1. 版本 \(3\)
    void *client_thread(void *arg) {
        int clientfd = *(int *)arg; // 从传入的参数中获取 clientfd
        while (1) { //防止只能接受一条
            char buffer[1024] = {0};
            int count = recv(clientfd , buffer , 1024 , 0);
            printf("RECV %s\n", buffer);
            
            if (count == 0) { //discount
                printf("client disconnect: %d\n",clientfd);
                close(clientfd);
                break; 
            }

            count = send(clientfd , buffer , count , 0);
            printf("SEND: %d\n", count);
        }
    }

    while (1) {
        printf("accept\n");
        int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
        printf("accept finished\n");
        pthread_t thid;
        // 创建一个新的线程
        // 参数1: 存储新线程的ID
        // 参数2: 线程属性 (NULL表示使用默认属性)
        // 参数3: 新线程将执行的函数 (这里是 client_thread)
        // 参数4: 传递给 client_thread 函数的参数 (这里是新接受的 clientfd 的地址)
        pthread_create(&thid , NULL , client_thread , &clientfd);
    }
  • 为每一个 clientfd 创造一个新的线程,一个线程服务于一个客户端。

补充

  1. recv 回馈是 0 的时候,代表断开连接。

  2. \(fd\) 的值有依次增加 3,4,5,6 ( 无论是 sockfd 还是 clientfd

    • 0,1,2 在哪里? 系统已经封装好了
    • ls /dev/stdin -l , ls /dev/stdout -lls /dev/stderr -l
    • ulimit -a : 看每一个进程 io 的数量,进程的 fd 是有限的。
    • close(fd) 回收和分配的机制,有回收的时间,分配会分配最小的那个未被使用的 fd

select

  1. 之前的模式:\(1\) 请求 , \(1\) 线程

    • 好处:代码逻辑简单
    • 缺点:不利于并发,1 \(k\) 并发量左右
  2. select 提供文件集合
    fd_set,集合的大小

posted @ 2025-09-18 17:58  xqy2003  阅读(5)  评论(0)    收藏  举报