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);
}   
相关文章
|
10天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
10天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
10天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
18天前
|
Linux
Linux中centos桌面消失网络图标
Linux中centos桌面消失网络图标
13 0
|
28天前
|
运维 网络协议 安全
【Shell 命令集合 网络通讯 】Linux 网络抓包工具 tcpdump命令 使用指南
【Shell 命令集合 网络通讯 】Linux 网络抓包工具 tcpdump命令 使用指南
44 0
|
28天前
|
监控 Linux Shell
【Shell 命令集合 网络通讯 】Linux 配置和管理网络流量的形状 shapecfg命令 使用指南
【Shell 命令集合 网络通讯 】Linux 配置和管理网络流量的形状 shapecfg命令 使用指南
38 0
|
28天前
|
网络协议 Shell Linux
【Shell 命令集合 网络通讯 】Linux 设置和配置PPP pppsetup命令 使用教程
【Shell 命令集合 网络通讯 】Linux 设置和配置PPP pppsetup命令 使用教程
36 0
|
28天前
|
Shell Linux C语言
【Shell 命令集合 网络通讯 】Linux 查看系统中的UUCP日志文件 uulog命令 使用指南
【Shell 命令集合 网络通讯 】Linux 查看系统中的UUCP日志文件 uulog命令 使用指南
29 0
|
8天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
24 6
|
28天前
|
监控 Shell Linux
【Shell 命令集合 网络通讯 】Linux 共享文件和打印机 Samba 命令 使用指南
【Shell 命令集合 网络通讯 】Linux 共享文件和打印机 Samba 命令 使用指南
39 0