前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >select实现socket单线程多路复用

select实现socket单线程多路复用

作者头像
DH镔
发布2019-12-20 16:52:20
7340
发布2019-12-20 16:52:20
举报

性能

select版本比阻塞版本的性能起码高了3倍+

# select版本
dhb@dev:~/下载/webbench-1.5$ webbench -c 1000 -t 30 http://127.0.0.1:1500/t
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://127.0.0.1:1500/t
1000 clients, running 30 sec.

Speed=735248 pages/min, 269580 bytes/sec.
Requests: 367610 susceed, 14 failed.

# 阻塞版本
dhb@dev:~/下载/webbench-1.5$ webbench -c 1000 -t 30 http://127.0.0.1:1500/t
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://127.0.0.1:1500/t
1000 clients, running 30 sec.

Speed=261244 pages/min, 95729 bytes/sec.
Requests: 130540 susceed, 82 failed.

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>


using namespace std;

#define BUFFER_SIZE 512
#define SEND_STR "HTTP/1.1 200 OK\r\n\r\nok"
#define BACKLOG 5

int get_line(int, char *, int);

int create_listen(int);

void handler(int, char *, char *);


/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
 * carriage return, or a CRLF combination.  Terminates the string read
 * with a null character.  If no newline indicator is found before the
 * end of the buffer, the string is terminated with a null.  If any of
 * the above three line terminators is read, the last character of the
 * string will be a linefeed and the string will be terminated with a
 * null character.
 * Parameters: the socket descriptor
 *             the buffer to save the data in
 *             the size of the buffer
 * Returns: the number of bytes stored (excluding null)
 * https://github.com/EZLippi/Tinyhttpd/blob/master/httpd.c
 * */
/**********************************************************************/
int get_line(int socket, char *buffer, int size) {
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n')) {
        /*
         * 当没有数据时这里会阻塞
         * */
        n = recv(socket, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0) {
            if (c == '\r') {
                n = recv(socket, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(socket, &c, 1, 0);
                else
                    c = '\n';
            }
            buffer[i] = c;
            i++;
        } else
            c = '\n';
    }
    buffer[i] = '\0';
    return (i);
}


/**********************************************************************/
/*
 * 创建socket连接并监听,使用ipv4协议
 *
 * 参数:
 *      port 端口号
 * */
/**********************************************************************/
int create_listen(int port) {
    int server_fd;

    /*
     * socket函数的方法签名
     * int socket (int __family, int __type, int __protocol)
     * __family     定义协议族
     * __type       定义数据传输方式/套接字类型
     * __protocol   定义传输协议
     *
     * 返回值是文件描述符
     *
     * */
    server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (server_fd < 0) {
        cout << "=> 连接失败" << endl;
    }

    /*
    * 位于netinet/in.h
    * 将套接字和IP、端口判断
    *
    * */
    struct sockaddr_in server_addr{};
    // 使用IPv4
    server_addr.sin_family = AF_INET;
    // 端口
    server_addr.sin_port = htons(port);
    // IP地址
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);

    /*
     * struct sockaddr* 位于socket.h
     * bind()签名
     * int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);
     * 第一个参数:socket文件描述符
     * 第二个参数:struct sockaddr*
     * 第三个参数:struct sockaddr* 长度
     * */
    int bind_error;
    if ((bind_error = bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr))) < 0) {
        cout << "=> 绑定socket异常:" << bind_error << endl;
        close(server_fd);
        exit(1);
    }


    // 设置监听
    listen(server_fd, SOMAXCONN);
    return server_fd;
}

void handler(int client, char* key, char *buffer) {

    int len;
    int content_length = -1;
    char method[255];
    char url[255];
    /*
     * i: 数组偏移量
     * j: 行数偏移量
     * */
    size_t i, j;
    i = 0;

    /* 读取报文第一行 */
    len = get_line(client, buffer, BUFFER_SIZE);
    /*================== 解析请求方法 ==================*/
    while (!isspace(buffer[i]) && i < sizeof(method) - 1) {
        method[i] = buffer[i];
        i++;
    }
    j = i;
    method[i] = '\0';
    cout << "请求方法:" << method << endl;
    /*================== 解析请求方法 ==================*/


    /*================== 解析url ==================*/

    i = 0;
    /* 跳过空格 */
    while (isspace(buffer[j]) && (j < len))
        j++;

    while (!isspace(buffer[j]) && (i < sizeof(url) - 1) && (j < len)) {
        url[i] = buffer[j];
        i++;
        j++;
    }
    url[i] = '\0';
    cout << "URL:" << url << endl;
    /*================== 解析url ==================*/

    while ((len = get_line(client, buffer, BUFFER_SIZE))) {
        if (buffer[0] != '\n') {
            cout << buffer;
            /* 设置Content-Length:的长度,截断数组取Content-Length的值 */
            buffer[15] = '\0';
            if (strcasecmp(buffer, "Content-Length:") == 0) {
                char *stop;
                content_length = strtol(&(buffer[16]), &stop, 10);
            }
            memset(buffer, 0, len);
        } else {
            break;
        }
    }
    /*================== 解析请求数据 ==================*/
    // 置零,下面读取请求数据
    len = 0;
    if (content_length > 0) {
        do {
            int tmp = recv(client, buffer, BUFFER_SIZE, 0);
            len += tmp;
            cout << buffer << endl;
            memset(buffer, 0, tmp);
        } while (len != content_length);
    }
    /*================== 解析请求数据 ==================*/
    if (strcmp(key, url) == 0) {
        system("git pull");
    }
    send(client, SEND_STR, sizeof(SEND_STR), 0);
    close(client);
}


/**********************************************************************/
/*
 * 功能:监听端口,接收http协议,当url与参数2匹配时执行git pull
 *
 * 注:写这个程序主要是为了学习socket和http协议,进而了解tomcat的io模型
 *
 * 参数1: 端口号
 * 参数2: url地址
 * */
/**********************************************************************/
int main(int argc, char **argv) {
    int server_fd = create_listen(atoi(argv[1]));

    int max_socket = server_fd;
    struct sockaddr_in client_addr{};
    struct timeval tv{};
    socklen_t client_addr_len = sizeof(client_addr);
    int client;
    char buffer[BUFFER_SIZE];
    int connect_amount = 0;

    fd_set fd_read_set;
    int fd_set[BACKLOG] = { 0 };
    int ret;
    int tmp;
    while (true) {
        cout << "i running" << endl;
        FD_ZERO(&fd_read_set);
        FD_SET(server_fd, &fd_read_set);

        tv.tv_sec = 30;
        tv.tv_usec = 0;

        for (int i = 0; i < BACKLOG; ++i) {
            if (fd_set[i] != 0) {
                FD_SET(fd_set[i], &fd_read_set);
            }
        }


        ret = select(max_socket + 1, &fd_read_set, NULL, NULL, &tv);

        if (ret < 0) {
            cout << "select" << endl;
            break;
        } else if (ret == 0) {
            cout << "timeout" << endl;
            continue;
        }

        tmp = 0;
        for (int j = 0; j < connect_amount; ++j) {
            if (FD_ISSET(fd_set[j], &fd_read_set)) {
                handler(fd_set[j], argv[2], buffer);
                FD_CLR(fd_set[j], &fd_read_set);
                fd_set[j] = 0;
                tmp++;
            }
        }
        connect_amount -= tmp;


        if (FD_ISSET(server_fd, &fd_read_set)) {
            client = accept(server_fd, (struct sockaddr *) &client_addr, &client_addr_len);
            if (client <= 0) {
                cout << "=> accept 错误" << endl;
                continue;
            }

            if (connect_amount < BACKLOG) {
                fd_set[connect_amount++] = client;
                if (client > max_socket) {
                    max_socket = client;
                }
            }
        }

    }
}

分析

有空再写

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 性能
  • 代码
  • 分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档