socket编程:多路复用之select模型

系统提供select函数来实现多路复用输入/输出模型。

select函数让我们的程序监视多个文件描述符的状态变化。程序会停在select这里等待,直到被监视的文件描述符中有一个或多个发生了状态变化

函数原型如下:

返回值:   成功返回就绪描述符的个数,超过timeout时间且没有任何事件发生返回0,失败返回-1

参数解释:

nfds:    被监视的文件描述符中值最大描述符值加1(描述符是从0开始的,描述符0、1、2...nfds-1均将被测试)

下面三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符。如果对某一个的条件不感兴趣,就可以把它设为空指针。

readfds:   读描述符集(所有关心读事件的描述符被用户添加进来),这个参数是个输入输出型参数

writefds:  写描述符集

exceptfds: 异常描述符集

struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

void FD_CLR(int fd, fd_set *fdset);   //把给定的文件描述符从指定集合删除

int FD_ISSET(int fd, fd_set *fdset);  //判断指定描述符时候被加入了指定集合中(是否可读/可写)

void FD_SET(int fd, fd_set *fdset);   //把一个给定的文件描述符加入集合之中

void FD_ZERO(fd_set *fdset);           //清空集合

timeout:   设置一段时间

(1)timeout=NULL时,当前进程一直等待,直到有一个描述符准备好I/O(永远等下去)

(2)timeout指向timeval结构体且时间设置不为0时,当前进程挂起,如果到了设定的时间(timeout)还没有任何事件发生就返回0,进程继续执行后面的代码(等待一段固定时间)

(3)timeout指向timeval结构体且时间设置为0时,不会等待,立即返回(根本不等待)

struct timeval{

long tv_sec;   //seconds

long tv_usec;  //microseconds

};

server_select.c

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

#define BLOCK 6

int fds[64];

void usage(char * proc)
{
    printf("%s [ip] [port]\n",proc);
}

int create_sock(char *port,const char * inaddr)
{
    //1.
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd<-1){
        perror("listenfd");
        exit(1);
    }
    //2.
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    int _port=atoi(port);
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(inaddr);

    struct linger lig;
    int iLen;
    lig.l_onoff=1;
    lig.l_linger=0;
    iLen=sizeof(struct linger);
    setsockopt(listenfd,SOL_SOCKET,SO_LINGER,(char *)&lig,iLen);
    //3.
    if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0){
        perror("bind");
        exit(2);
    }
    //4.
    if(listen(listenfd,BLOCK)<0){
        perror("listen");
        exit(3);
    }

    return listenfd;
}

int main(int argc,char* argv[])
{
    if(argc!=3){
        usage(argv[0]);
        exit(1);
    }

    //create listenfd
    int listen_fd=create_sock(argv[2],argv[1]);

    struct sockaddr_in client;
    socklen_t len=sizeof(client);

    int max_fd=listen_fd;

    char buf[1024];

    //initialize fds[]
    int fds_num=sizeof(fds)/sizeof(fds[0]);
    int i=0;
    for(i=0;i<fds_num;++i){
        fds[i]=-1;
    }
    fds[0]=listen_fd;

    int done=0;
    while(!done){
        //define and initialize parameter of select() 
        fd_set readset;
        FD_ZERO(&readset);
        FD_SET(listen_fd,&readset);
        
        struct timeval timeout;
        timeout.tv_sec=5;
        timeout.tv_usec=0;

        //重新添加描述符到readset中
        for(i=1;i<fds_num;++i){
            if(fds[i]>0){
                FD_SET(fds[i],&readset);
                if(fds[i]>max_fd)
                    max_fd=fds[i];
            }
            else
                break;
        }

        switch(select(max_fd+1,&readset,NULL,NULL,&timeout)){
            case -1:
                perror("select");
                break;
            case 0:
                printf("timeout...\n");
                break;
            default:
                    //遍历所有的描述符
                for(i=0;i<fds_num;++i){
                    //listen_fd happen
                    if(fds[i]==listen_fd && FD_ISSET(fds[i],&readset)){
                        int connfd=accept(listen_fd,(struct sockaddr*)&client,&len);
                        if(connfd<0){
                            perror("accept");
                            break;
                        }else{
                            printf("get a connect...\n");
                            for(i=0;i<fds_num;++i){
                                if(fds[i]==-1){
                                    fds[i]=connfd;
                                    FD_SET(connfd,&readset);
                                    break;
                                }
                            }
                        }
                    }
                    //normal event happen
                    else if(fds[i]>0 && FD_ISSET(fds[i],&readset)){
                            ssize_t _size=read(fds[i],buf,sizeof(buf)-1);
                            if(_size<0){
                                perror("read");
                            }else if(_size==0){//client closed
                                printf("client shutdown...\n");
                                close(fds[i]);
                                fds[i]=-1;
                                continue;
                            }else{
                                buf[_size]=‘\0‘;
                                printf("client# %s",buf);
                                while(write(fds[i],buf,sizeof(buf)-1)<0){
                                    perror("write");
                                }
                            }
                    }
                    else{
                    }
                }
                break;
        }
    }
    return 0;
}

client_select.c

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

void usage(char *proc)
{
    printf("%s [ip] [port]\n",proc);
}

int creat_socket()
{
    int fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd<0){
        perror("socket");
        exit(1);
    }   
    return fd;
}

int main(int argc,char* argv[])
{
    if(argc!=3){
        usage(argv[0]);
        exit(1);
    }

    int fd=creat_socket();

    int _port=atoi(argv[2]);
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(_port);
    inet_aton(argv[1],&addr.sin_addr);
    socklen_t addrlen=sizeof(addr);
    if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){
        perror("connect");
        exit(2);
    }
    
    char buf[1024];
    while(1){
        memset(buf,‘\0‘,sizeof(buf));
        printf("Please Enter:");
        fgets(buf,sizeof(buf)-1,stdin);
        if(send(fd,buf,sizeof(buf)-1,0)<0){
            perror("send");
            continue;
        }

        //回显
        ssize_t _size=recv(fd,buf,sizeof(buf)-1,0);
        if(_size>0){
            buf[_size]=‘\0‘;
            printf("echo->%s\n",buf);
        }
    }   

    return 0;
}

运行结果:(可把服务器端和客户端结果对比起来看)

服务器端

客户端

 

select模型的缺点:

1.我的机器测试

printf("fd_set=%d\n",sizeof(fd_set));  值为128,说明能监视的描述符的最大值为128*8=1024个

2.每次都要把所关心的描述符重新添加一次,即从用户态拷贝到内核态(因为select返回时没有发生事件的描述符将被清空),如果描述符较多的时候,开销是很大的

3.当select返回的时候,即使只有一个事件发生了,也要把整个集遍历一次,同样,如果描述符较多的时候,开销也很大

时间: 2024-10-27 12:09:10

socket编程:多路复用之select模型的相关文章

Socket编程实践(10) --select的限制与poll的使用

select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或者使用setrlimit函数设置,但一个系统所能打开的最大数也是有限的,跟内存大小有关,可以通过cat /proc/sys/fs/file-max 查看 /**示例: getrlimit/setrlimit获取/设置进程打开文件数目**/ int main() { struct rlimit rl;

Socket编程实践(11) --Select I/O复用

Select函数 Man-Page /* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,

Socket编程实践(12) --select实现超时I/O API[比较难于理解/代码较多]

read_timeout函数封装 //读超时函数,但不包含读操作 int read_timeout(int fd, long waitSec) { int returnValue = 0; if (waitSec > 0) { fd_set readSet; FD_ZERO(&readSet); //清零 FD_SET(fd,&readSet); //添加 struct timeval waitTime; waitTime.tv_sec = waitSec; waitTime.tv_

Python(七)Socket编程、IO多路复用、SocketServer

本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 功能: sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一:地址簇 socket.AF_INET IPv4(默认)

windows socket编程select模型使用

int select( int nfds,            //忽略 fd_ser* readfds,    //指向一个套接字集合,用来检测其可读性 fd_set* writefds,   // 指向一个套接字结合,用来检测其可写性 fd_ser* exceptfds, //指向一个套接字集合,用来检测错误 const struct timeval * timeout   //指定此函数等待的最长时间,如果为NULL,则最长时间为无限大. ); 参数说明: (1)   nfds  win

socket编程的select模型

在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的监听客户端的请求,有新的请求到达时,开辟一个新的线程去和该客户端进行后续处理,但是这样针对每一个客户端都需要去开辟一个新的线程,效率必定底下. 其实,socket编程提供了很多的模型来处理这种情形,我们只要按照模型去实现我们的代码就可以解决这个问题.主要有select模型和重叠I/o模型,以及完成端

socket编程:多路复用I/O服务端客户端。

其实在之前的TCP之中,我们编程实现了多进程,多线程机制下的TCP服务器,但是对于这种的TCP服务器而言,存在太大的资源局限性.所以我们可以是用I/0模型中的多路复用I/O模型来进行编程. 他的具体思想就是:当前进程可以处理多个相应时间,记录多个描述符,然后控制轮询时间态,当有响应产生的时候我们就去保存当前响应文件描述符,对他进行连接处理/数据传输就OK了.在一个进程进行多个响应时间的答复情况下,可以大大的节约我们系统所消耗的资源. 对这个进行操作的函数就是select(): 他的函数原型如下:

Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型

Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 SocketChannel 的关系 事件监听类型 字节缓冲 ByteBuffer 数据结构 场景 接着上一篇中的站点访问问题,如果我们需要并发访问10个不同的网站,我们该如何处理? 在上一篇中,我们使用了java.net.socket类来实现了这样的需求,以一线程处理一连接的方式,并配以线程池的控制

socket编程以及select、epoll、poll示例详解

socket编程socket这个词可以表示很多概念,在TCP/IP协议中“IP地址 + TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP + 端口号”就称为socket.在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么两个socket组成的socket pair就唯一标识一个连接. 预备知识 网络字节序:内存中多字节数据相对于内存地址有大端小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分.网络数据流同样有大端小端之分,所以发送主机通常将发送缓冲