Linux网络编程之select

使用select多路复用技术的非阻塞模型

select多路复用通常具有很好的跨平台性,也能提供不错的并发性能,但是在通常情况下有最大监听文件描述符的限制(通常1024),如果不需要达到C10K这种前端高性能服务器的要求,采用select+nonblocking的方式能降低编程的难度



用到的接口
FD_SETSIZE;
FD_SET(<#fd#>, <#fdsetp#>);
FD_ISSET(<#fd#>, <#fdsetp#>);
FD_ZERO(<#fdsetp#>);
select(<#(int)__nfds#>, <#(fd_set*)__readfds#>, <#(fd_set*)__writefds#>, <#(fd_set*)__exceptfds#>, <#(struct timeval*)__timeout#>)


//select-nonblocking
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/select.h>

#define MAX_READ_SIZE  20*1024*1024
#define MAX_WRITE_SIZE 20*1024*1024
#define MAX_CHUNCK_SIZE 2*1024*1024
#define KEEP_ALIVE 0

/*
 *
 * a simple server using select
 * feilengcui008@gmail.com
 *
 */

void run();

int main(int argc, char *argv[])
{
    run();
    return 0;
}

//struct for storing read-write buffer
struct fd_state {
    char buffer[MAX_READ_SIZE];
    char write_buffer[MAX_WRITE_SIZE];
    int writing;
    size_t read_len;
    size_t written_len;
    size_t write_upto_len;
};

//alloc fd_state
struct fd_state *alloc_fd_state(void)
{
    struct fd_state *p = (struct fd_state *) malloc(sizeof(struct fd_state));
    if (!p) return NULL;
    p->read_len = p->writing = p->written_len = p->write_upto_len = 0;
    return p;
};

//free fd_state pointer
void free_fd_state(struct fd_state *p)
{
    free(p);
}

//handle error
void error_exit()
{
    perror("errors happen");
    exit(EXIT_FAILURE);
}
//set sock non-blocking
void made_nonblock(int fd)
{
    if(fcntl(fd, F_SETFL, O_NONBLOCK)==-1){
        error_exit();
    }
}

//handle read event
int do_read(int fd,struct fd_state *state)
{
    char buf[MAX_CHUNCK_SIZE];
    int i;
    ssize_t result;
    while (1){
        result = recv(fd, buf, sizeof(buf), 0);
        /*printf("readlen:%d\n",(int)result);
        fflush(stdout);*/
        if (result<=0)
            break;
        //read buf to fd_state.buffer until "\n"
        for (int j = 0; j < result; ++j) {
            if (state->read_len< sizeof(state->buffer))
                state->buffer[state->read_len++] = buf[j];
            /*read until "\n"
            todo:
            handle the read buffer and set the write buffer
            if (buf[j]=='\n'){
                state->writing = 1;
                state->write_upto_len = state->read_len;
                break;
            }*/
        }

    }

    state->writing = 1;
    state->write_upto_len = state->read_len;

    //write(1, state->buffer, state->read_len);
    //nothing read
    if (result==0){
        return 1;
    }

    if (result<0){
        //nonblocking
        if (errno== EAGAIN)
            return 0;
        else
            return -1;
    }
    return 0;
}

int do_write(int fd,struct fd_state *state)
{
    //we borrow the readbuffer just for test
    //we should have a write buffer to store our http response body
    ssize_t result;
    while (state->written_len<state->write_upto_len){
        result = send(fd, state->buffer+state->written_len, state->write_upto_len-state->written_len, 0);
        if (result<=0){
            break;
        }
        state->written_len+=result;
    }

    if (state->written_len==state->read_len)
        state->written_len = state->write_upto_len = state->read_len = 0;
    state->writing = 0;

    if (result<0){
        if (errno== EAGAIN)
            return 0;
        else
            return -1;
    }
    if (result==0)
        return 1;

    return 0;
}

void do_select(int serverfd)
{

    //set init fd_state
    struct fd_state *state[FD_SETSIZE];
    for (int i = 0; i < FD_SETSIZE; ++i) {
        state[i] = NULL;
    }
    fd_set read_set,write_set,error_set;
    FD_ZERO(&read_set);
    FD_ZERO(&write_set);
    FD_ZERO(&error_set);
    //main loop
    while(1){
        int maxfd = serverfd;
        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_ZERO(&error_set);

        FD_SET(serverfd, &read_set);

        //server accept operation just alloc the fd_state struct
        //do_read and do_write just change the fd_state's state
        //here we add server sock and client socks to fd_set
    //we don not pass the readset/writeset to do_read or do_write func
    //so we add read write event here
    //when use poll,we can direct change the state in do_read and do_write use the events status
        for (int i = 0; i < FD_SETSIZE; ++i) {
            if (state[i]){
                if (i>maxfd){
                    maxfd = i;
                }
                FD_SET(i, &read_set);
                if (state[i]->writing){
                    FD_SET(i, &write_set);
                }
            }
        }
        //block until event happens
        if(select(maxfd+1, &read_set, &write_set, &error_set, NULL)<0){
            error_exit();
        }

        // if server becomes readable then accept the client sock
        // and alloc the client sock state
        if(FD_ISSET(serverfd, &read_set)){
            struct sockaddr_storage ss;
            socklen_t slen = sizeof(ss);
            int client = accept(serverfd, (struct sockaddr *)&ss, &slen);
            if(client<0){
            error_exit();
            }else if(client> FD_SETSIZE){
                close(client);
            }else{
                made_nonblock(client);
                state[client] = alloc_fd_state();
            }
        }

        //handle the fd_set
        for (int j = 0; j < maxfd + 1; ++j) {
            int flag = 0;
            if (j==serverfd) continue;
            //handle read
            if (FD_ISSET(j, &read_set)) flag = do_read(j,state[j]);
            //handle write
            if (flag==0&& FD_ISSET(j, &write_set)){
                flag = do_write(j, state[j]);
                //no matter what flag is,we close after write ops
                if(!KEEP_ALIVE){
                    free_fd_state(state[j]);
                    state[j] = NULL;
                    close(j);
                }
            }
            //handle error
            if (flag){
                free_fd_state(state[j]);
                state[j] = NULL;
                close(j);
            }
        }

    }
}

int create_server_socket()
{

    int serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if(serverfd<0){
        error_exit();
    }
    int reuse = 1;
    if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0){
        error_exit();
    }

    return serverfd;
}

void run()
{
    int serverfd = create_server_socket();
    made_nonblock(serverfd);
    //server sockaddr
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(8000);
    if(bind(serverfd, (struct sockaddr *)&sin, sizeof(sin))<0){
        error_exit();
    }
    if(listen(serverfd, 50)<0){
        error_exit();
    }
    do_select(serverfd);
}
时间: 2024-07-29 08:36:02

Linux网络编程之select的相关文章

linux 网络编程之TIME_WAIT状态

                                                         Linux 网络编程之TIME_WAIT状态                                                               刚刚开始看TCP socket的4次握手终止流程图的时候,对于最后的TIME_WAIT状态不是很理解.现在在回过头来研究,发现TIME_WAIT状态是一个很微妙状态.之所以设计TIME_WAIT状态的原因有2个原因:

Linux网络编程之UDP Socket程序示例_C 语言

在网络传输协议中,TCP协议提供的是一种可靠的,复杂的,面向连接的数据流(SOCK_STREAM)传输服务,它通过三段式握手过程建立连接.TCP有一种"重传确认"机制,即接收端收到数据后要发出一个肯定确认的信号,发送端如果收到接收端肯定确认的信号,就会继续发送其他的数据,如果没有,它就会重新发送. 相对而言,UDP协议则是一种无连接的,不可靠的数据报(SOCK_DGRAM)传输服务.使用UDP套接口不用建立连接,服务端在调用socket()生成一个套接字并调用bind()绑定端口后就可

linux网络编程之socket(十二) select函数的并发限制和poll函数应用举例

一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制. 这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, 但一个系统所能打开的最大数也是有 限的,跟内存大小有关,可以通过cat /proc/sys/fs/file-max 查看 2.select中的fd_set集合容量的限制 (FD_SETSIZE,一般为1024) ,这需要重新编译内核. 可以写个测试程序,只建立连接,看看最多能够建立多少个 连接,客

linux网络编程之socket(九) 使用select函数改进客户端/服务器端程序

一.当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出 现一个有趣的现象,先来看输出: 先运行服务器端,再运行客户端, simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_recv_peek recv connect ip=127.0.0.1 port=54005 simba@ubuntu:~/Documents/code/linux_prog

linux网络编程之socket(十一)套接字I/O超时设置方法和用select实现超时

一.使用alarm 函数设置超时 void handler(int sig) { } signal(SIGALRM, handler); alarm(5); int ret = read(fd, buf, sizeof(buf)); if (ret == -1 && errno == EINTR) errno = ETIMEOUT; else if (ret >= 0) alarm(0); ................. 程序大概框架如上所示,如果read在5s内被SIGALRM

linux网络编程之socket(十三) epoll系列函数简介与select、poll的区别

一.epoll 系列函数简介 #include <sys/epoll.h> int epoll_create(int size); int epoll_create1(int flags); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);    

linux网络编程之socket(八) 五种I/O模型和select函数简介

一.五种I/O模型 1.阻塞I/O 我们在前面所说的I/O模型都是阻塞I/O,即调用recv系统调用,如果没有数据则阻塞等待,当数据到来则将数据从内核 空间(套接口缓冲区)拷贝到用户空间(recv函数提供的buf),然后recv返回,进行数据处理. 2.非阻塞 I/O 我们可以使用 fcntl(fd, F_SETFL, flag | O_NONBLOCK); 将套接字标志变成非阻塞,调用recv,如果设备暂时 没有数据可读就返回-1,同时置errno为EWOULDBLOCK(或者EAGAIN,这

linux网络编程之socket(四)

使用fork并发处理多个client的请求和对等通信p2p 一.在前面讲过的回射客户/服务器程序中,服务器只能处理一个客户端的请求,如何同时服务多个客户端呢?在未讲到 select/poll/epoll等高级IO之前,比较老土的办法是使用fork来实现.网络服务器通常用fork来同时服务多个客户端,父 进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端.但是子进程退出时会产 生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程,最

linux网络编程之posix 线程(三)

posix 信号量与互斥锁 示例生产者--消费者问题 一.posix 信号量 信号量的概念参见这里(http://www.bianceng.cn/OS/Linux/201308/37243.htm).前面也讲过system v 信号量,现在来说说posix 信号量. system v 信号量只能用于进程间同步,而posix 信号量除了可以进程间同步,还可以线程间同步.system v 信号量每次PV操作可以是N,但Posix 信号量每次PV只能是1.除此之外,posix 信号量还有命名和匿名之分