tcp网络编程4—并发的io多路复用实现(select)

原型:

  int select(int max fdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

功能:

  委托内核检查描述符集是否准备好(即可以使用,用于双方通信)

参数:

  fdp1:最大的fd加1,在三个描述符集(readfds、writefds、wxceptfds)中找出最大的描述符,然后再加1

  readfds、writefds、wxceptfds:指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写、或处于异常条件的各个描述符。

  timeout:指定等待时间

      NULL:永远等待,直到捕捉到信号或文件描述符已经准备好

      具体值:具体等待的时间,若等到超时且描述符集中的文件描述符还没有准备好,就立即返回。

      0:不等待,测试所有指定的描述符并立即返回(不能等到其准备好)

返回值:

  > 0:准备好的文件描述符

  =0:超时了

  < 0(-1):出错

 

传入select的参数是告诉内核:

  我们所关心的描述符;

  对于每个描述符我们所关心的条件(是否可读/可写一个给定的描述符、是否关心一个描述符的异常条件);

  希望等待的时间。

从select返回时内核告诉我们:

  已准备好的描述符数量;

  哪一个文件描述符已准备好读、写或异常条件;

  select函数确定的可用的文件描述符是非阻塞的读写。

 

对文件描述符处理的宏:

  FD_ZERO(fd_set * set);  清除一个文件描述符集

  FD_SET(int fd, fd_set * set);  将一个文件描述符加入到文件描述符集中

  FD_CLR(int fd, fd_set * set);  将一个文件描述符从文件描述符集中清除

  FD_LSSET(int fd, fd_set *set);  测试fd这个文件描述符在文件描述符集中是否可用  

在使用select函数前,先使用FD_SERO和FD_SET来初始化文件描述符集;

在使用select函数时,可循环使用FD_LSSET测试准备好的文件描述符中的某个文件描述符是否可用。

执行完对相关的文件描述符的操作后,用FD_CLR来清除文件描述符

//select_tcp_server.c

//select_tcp_server.c

#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <string.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include "fcntl_vector_fd.h"

//int socket(int domain, int type, int protocol);


//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);

//void perror(const char *s);

VectorFd *vfd = NULL;

int socket_fd;

void sig_fun(int signo)
{
    if(signo == SIGINT){
        write(STDOUT_FILENO, "signal SIGINT", 15);
        destroy_vector_fd(vfd);
        close(socket_fd);
        exit(1);
    }    
}


static void out_addr(struct sockaddr_in *clientaddr)
{
    char ip[10];
    memset(ip, 0, sizeof(ip));
    int port = ntohs(clientaddr->sin_port);
    inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
    printf("%s(%d) connected!\n", ip, port);
}

//如果客户端往socket中写了数据,子线程在遍历fd时,如果能读到数据,那就将数据作相应的处理,如果读不到将不会阻塞,直接返回
void do_service(int fd)
{
    char buffer[512];    
    
    /*相当于不完整管道
    *如果服务器从socket中读取到的数据为0,说明客户端关闭了socket或客户端已经挂掉了(服务器读一个写端关闭了的socket)
    *如果服务器往读端(客户端)关闭了的socket中写数据,将会产生一个SIGPIPE的信号,并且errorno被设置为EPIPE(服务器写一个读端关闭了的socket)
    */
    memset(buffer, 0, sizeof(buffer));
    //采用非阻塞的read,若读不到数据就直接放回。直接服务于下一个客户端,因此不用判断size小于0的情况
    ssize_t size = read(fd, buffer, sizeof(buffer));
    if(size == 0){
        printf("client wtrite closed!\n");
        remove_fd(vfd, fd);
        close(fd);
    }else if(size > 0){
        printf("buffer = %s\n",buffer);
        if(write(fd, buffer, sizeof(buffer)) <0 ){
            if(errno == EPIPE){
                printf("client read closed!\n");
                remove_fd(vfd, fd);
                close(fd);
            }
            perror("write:");
        }
    }

}

//将动态数组的fd放到描述符集中,并返回最大的描述符
int addfd_to_set(VectorFd * vfd, fd_set * set)
{
    int i = 0;
    int max_fd = 0;
    FD_ZERO(set);
    for(i = 0; i < vfd->conter; i++){
        FD_SET(vfd->fd[i], set);
        if(max_fd < vfd->fd[i]){
            max_fd = vfd->fd[i];
        }
    }
    return max_fd;
}


void *th_fun(void *arg)
{
    struct timeval time;
    time.tv_sec = 2;
    time.tv_usec = 0;
    
    fd_set set;
    
    
    int max_fd = addfd_to_set(vfd, &set);
    if(max_fd < 0){
        printf("描述符错误");
    }
    
    int num;
    while((num = select(max_fd+1, &set, NULL, NULL, &time)) >= 0){//每次都要在文件描述符集中检查是否有描述符可用
        if(num > 0){
            int i = 0;
            for(i = 0; i < vfd->conter; i++){
                if(FD_ISSET(vfd->fd[i], &set)){
                    do_service(vfd->fd[i]);
                    FD_CLR(vfd->fd[i], &set);
                }
            }
        }
        time.tv_sec = 2;
        time.tv_usec = 0;//这里要将其重新赋值,因为这里的这些时间被设置为0了
        //sleep(1);
        //printf("sec = %ld, usec = %ld\n",time.tv_sec, time.tv_usec);
        //在使用完准备好的文件描述符或超时后,要清空描述符集,供下次用
        FD_ZERO(&set);
        max_fd = addfd_to_set(vfd, &set);
    }
    return (void *)0;
}

int main(int argc,char *argv[])
{
    if(argc <2){
        perror("argc<2:");
        exit(1);
    }
    
    if(signal(SIGINT,sig_fun) == SIG_ERR){
        perror("signal:");
        exit(1);
    }
    
    /*
    *1. 创建socket
    */
    if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket:");
        exit(1);
    }
    /*
    *2.绑定IP地址和端口号
    *int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    */
    struct sockaddr_in serviceaddr;
    memset(&serviceaddr, 0, sizeof(serviceaddr));
    serviceaddr.sin_family = AF_INET;
    serviceaddr.sin_port = htons(atoi(argv[1]));
    serviceaddr.sin_addr.s_addr = INADDR_ANY;//一台服务器上可能有多块网卡(多个IP地址)
    //这个宏是响应本机所有网卡(IP地址)上连接的客户端请求
    
    if(bind(socket_fd, (struct sockaddr *)&serviceaddr, sizeof(serviceaddr)) < 0){
        perror("bind:");
        exit(1);
    };
    
    /*
    *3.监听绑定的端口
    *通知系统去监听来自客户端的连接请求
    *(将监听到的客户端连接请求放置到对应的队列中)
    *第二个参数:指定队列的长度
    */
    if(listen(socket_fd, 10) < 0){
        perror("listen:");
        exit(1);
    }
    
    //创建动态数组
    vfd = create_vector_fd();
    
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    pthread_t t;    
    /*
        *5.创建子线程循环遍历动态数组     
        *int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        *                  void *(*start_routine) (void *), void *arg);
    */
    if(pthread_create(&t, &attr, th_fun, (void *)0) != 0){
        perror("pthread_create");
        exit(1);
    }
    pthread_attr_destroy(&attr);
    
    /*
    *4.调用accept函数从队列中获得一个客户端的连接请求
    *并返回一个新的socket描述符,这个描述符和和客户端
    *的连接请求对应,即这个描述符和某个客户端对应
    *如果没有客户端连接,调用此函数会阻塞,直到获得一个客户端的连接
    *第二个参数:客户端的地址信息
    */
    struct sockaddr_in clientaddr;
    socklen_t length = sizeof(clientaddr);
    while(1){
        int fd = accept(socket_fd, (struct sockaddr *)&clientaddr, &length);
        if(fd < 0){
            perror("accept:");
            continue;
        }
        out_addr(&clientaddr);
                
        add_fd(vfd, fd);  

    }
    
    return 0;
}

 

posted @ 2023-03-22 19:45  踏浪而来的人  阅读(108)  评论(0编辑  收藏  举报