linux select 在socket里使用(二)

转自原文链接:https://blog.csdn.net/myselfzhangji/article/details/103931040

一、那么何为多路IO转接呢?
在linux中,有一条准则,是一切皆文件。socket通信也是利用文件的读写来实现的,那么我们就可以检测文件是否发生读写事件,来判断是否有socket通信。
应用程序调用select函数来通知内核来实现检测文件读、写、异常事件,示意图如下:
在这里插入图片描述
select函数原型如下

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数1: 所监听的所有文件描述符中,最大的文件描述符+1
参数2: 所监听的文件描述符"可读"事件,readfds
参数3: 所监听的文件描述符"可写"事件,writefds
参数4: 所监听的文件描述符"异常"事件,
参数5: 超时
返回值:
成功: 所监听的集合中,满足条件的总数;
出错:-1,并设置errno

既然select函数可以分别检测对应文件描述符的读(readfds)、写(writefds)、异常(exceptfds)事件,那么我们就需要首先设置select监测哪些文件描述符的读(readfds)、写(writefds)、异常(exceptfds)事件,linux提供的相关API为:

void FD_ZERO(fd_set *set); //将set清空为0
void FD_CLR(int fd, fd_set *set); //将fd从set集合中清除出去,
int FD_ISSET(int fd, fd_set *set); //判断fd是否在集合中
void FD_SET(int fd, fd_set *set); //将fd设置到set集合中去

FD_SET是设置要监听的文件描述符是哪些,以位图形式表示,假设我们需要监听文件描述符分别是lfd,fd1,fd2,fd3,FD_SET设置之后的readfds如下
在这里插入图片描述
select要监听readfds的文件描述符状态,如果fd1和fd3的读事件发生,此时readfds的文件描述符状态如下:
在这里插入图片描述
程序步骤如下

  1. 初始化 readfds 集合
    fd_set readfds
    FD_ZERO(&readfds);
  2. 将要监听的文件描述符添加到集合中
    FD_SET(fd1, &readfds);
    FD_SET(fd2, &readfds);
    FD_SET(fd3, &readfds);
  3. select开始监听对应文件描述符的事件
    select() -------> 满足条件的总数
  4. 判断文件描述符所属事件类型
    for() {
    FD_ISSET(fd1, &readfds) -----> 返回为1,表示满足
    }

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/shm.h>

#define PORT  8890
#define QUEUE_SIZE   10
#define BUFFER_SIZE 1024

int main(int argc, char **argv)
{
    struct sockaddr_in server_sockaddr, client_addr;
    socklen_t length = sizeof(client_addr);
    char str[INET_ADDRSTRLEN];
    pthread_t pid;
    int listenfd, client[FD_SETSIZE], sockfd;
    int maxfd, maxi, connfd;
    int ret = 0, nready;
    int reuse = 1;
    fd_set rset, allset;
    char buf[BUFFER_SIZE] = {0};
    int recvlen, i;

    //定义IPV4的TCP连接的套接字描述符
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket() fail!\n");
        return -1;
    }

    //使能可以重新使用addr
    ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
        &reuse, sizeof(reuse));
    if (ret < 0) {
        perror("setsockopt error");
        return -1;
    }
    //定义sockaddr_in
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_sockaddr.sin_port = htons(PORT);

    //bind成功返回0,出错返回-1
    ret = bind(listenfd, (struct sockaddr *)&server_sockaddr,
        sizeof(server_sockaddr));
    if(ret < 0) {
        perror("bind");
        return -1;//1为异常退出
    }
    printf("bind success.\n");

    //listen成功返回0,出错返回-1,允许同时帧听的连接数为QUEUE_SIZE
    ret = listen(listenfd, QUEUE_SIZE);
    if(ret < 0) {
        perror("listen");
        return -1;
    }
    printf("listen success.\n");

    maxfd = listenfd;
    maxi = -1;
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    while(1) {
        rset = allset;
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
        if (nready < 0) {
            perror("select error!");
            return -1;
        }

        if (FD_ISSET(listenfd, &rset)) { //监听套接字是否有连接请求
            connfd = accept(listenfd, (struct sockaddr*)&client_addr,&length);//获得当前接入的ip,内核分配的应用fd
            if(connfd < 0) {
                perror("connect error!");
                return -1;
            }
            printf("new client accepted, ip : %s, port : %d.\n",
                inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
                ntohs(client_addr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++) {
                if (client[i] < 0) {
                    client[i] = connfd;//保存当前接入的ip,内核分配的应用fd
                    break;
                }
            }

            if (i >= FD_SETSIZE) {
                printf("too many client\n");
                return -1;
            }

            FD_SET(connfd, &allset);
            if (connfd > maxfd)
                maxfd = connfd;

            if (i > maxi)
                maxi = i;

            if (--nready == 0)
                continue;
        }

        for (i = 0; i <= maxi; i++) {  //监听已经连接的客户端是否有数据发送过来
            if ((sockfd = client[i]) < 0)
                continue;

            if (FD_ISSET(sockfd, &rset)) {
                memset(buf, 0, sizeof(buf));
                recvlen = read(sockfd, buf, sizeof(buf));
                if (recvlen ==0) {
                    printf("client[%d] exit\n", client[i]);
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                } else {
                    printf("now server recv : %s\n", buf);
                    write(sockfd, buf, recvlen);
                }
            }

            if (--nready <= 0)
                 continue;
        }
    }

    printf("closed.\n");
    close(listenfd);
    return 0;
}

client.c

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

#define PORT  8890
#define BUFFER_SIZE 1024
#define LOCAL_LOOP_BACK_IP "127.0.0.1"

int main(int argc, char **argv)
{
    struct sockaddr_in servaddr;
    char sendbuf[BUFFER_SIZE] = {0};
    char recvbuf[BUFFER_SIZE] = {0};
    int client_fd;

    //定义IPV4的TCP连接的套接字描述符
    client_fd = socket(AF_INET,SOCK_STREAM, 0);

    //set sockaddr_in
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(LOCAL_LOOP_BACK_IP);
    servaddr.sin_port = htons(PORT);  //服务器端口

    //连接服务器,成功返回0,错误返回-1
    if (connect(client_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }
    printf("connect server(IP:%s).\n",LOCAL_LOOP_BACK_IP);

    //客户端将控制台输入的信息发送给服务器端,服务器原样返回信息
    while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    {
        send(client_fd, sendbuf, strlen(sendbuf),0); ///发送
        if(strcmp(sendbuf,"exit\n")==0)
        {
            printf("client exited.\n");
            break;
        }
        recv(client_fd, recvbuf, sizeof(recvbuf),0); ///接收
        printf("client receive: %s\n", recvbuf);
 
        memset(sendbuf, 0, sizeof(sendbuf));
        memset(recvbuf, 0, sizeof(recvbuf));
    }

    close(client_fd);
    return 0;
}

(1)我们将各个文件描述符状态以及一些参数标示如下,初始时,我们要一直监听连接事件,监听连接事件是通过server_listen_fd来实现的,所以初始状态如下:
在这里插入图片描述
(2)假设此时只有一个client连接事件请求,如下,此时监听到一个连接,会将连接到的文件描述符加入进来,以监听将来是否发生读写数据事件
在这里插入图片描述
(3)假设此时有fd2,fd3,fd4都发生连接事件,如下。因为都检测到了连接事件,所以要将这些事件描述符添加进来,以便监测将来可能会发生的读写数据请求
在这里插入图片描述
(4)假设此时fd2,fd4发生了数据通信(对客户端而言是发送数据,对服务器端是接收数据,请注意,我们这里的fd1,fd2,fd3,fd4都是server端的文件描述符,因此它们都表示读数据事件发生了),并且同时有fd5的连接请求
在这里插入图片描述

发现server还是阻塞等待,这是因为在select函数中,我们没有设置超时等待机制,默认还是阻塞的方式。实际上select函数是可以设置为超时等待的,修改下面章节将。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值