IO模型:阻塞IO模型的多线程形式

61 阅读2分钟

阻塞IO模型的多线程形式

前言

之前的文章中讲了《IO模型:阻塞IO模型的多进程形式)》,但是进程的内存占用和一系列的操作如创建、销毁和切换的代价都比较大,相比于多进程,多线程更适合http服务器(一个客户端连接对应一个线程)这种频繁创建和断开连接的需求。

进程和线程的概念以及区别

《进程和线程的概念以及区别》

代码

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

#define PORT 8080
#define BACKLOG 5
#define BUFFERSIZE 4089

#define handle_error_en(en, msg) \
               do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
               do { perror(msg); exit(EXIT_FAILURE); } while (0)


void *process_request(void *arg) {

    int connfd = *(int *) arg;

    char buffer[BUFFERSIZE];

    ssize_t n = read(connfd, buffer, BUFFERSIZE);

    if (n > 0) {
        buffer[n] = '\0';

        printf("收到的请求信息:\n%s\n", buffer);

        char *response = "HTTP/1.1 200 OK\r\n"
                         "Content-Type: text/html;charset=utf-8\r\n"
                         "\r\n"
                         "<html><body><h1>Hello, World!</h1></body></html>\r\n";
        write(connfd, response, strlen(response));
    }

    close(connfd);

    pthread_exit(NULL);
}

int main() {
    int s, listenfd, connfd;
    struct sockaddr_in serv_addr, client_addr;
    socklen_t addr_size;


    pthread_t tid;
    pthread_attr_t attr;
    int stack_size = 120 * 1024;


    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT);

    if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
        handle_error("bind");
    }

    if (listen(listenfd, BACKLOG) == -1) {
        handle_error("listen");
    }

    addr_size = sizeof(client_addr);

    printf("服务器启动, 监听端口 %d\n", PORT);

    while (1) {
        connfd = accept(listenfd, (struct sockaddr *) &client_addr, &addr_size);

        int port = ntohs(client_addr.sin_port);
        struct in_addr in = client_addr.sin_addr;
        char str[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &in, str, sizeof(str));

        printf("客户端信息: ip:port = %s:%d\n", str, port);

        if (connfd == -1) {
            handle_error("accept");
        }
        s = pthread_attr_init(&attr);

        if (s != 0) {
            handle_error_en(s, pthread_attr_init);
        }

        if (stack_size > 0) {
            s = pthread_attr_setstacksize(&attr, stack_size);
            if (s != 0) {
                handle_error_en(s, "pthread_attr_setstacksize");
            }
        }
        s = pthread_create(&tid, &attr, process_request, &connfd);

        if (s != 0) {
            handle_error_en(s, "pthread_create");
        }
    }
    close(listenfd);
    return 0;
}

代码解释

上面的代码逻辑相对简单,我就拿这个多线程模型中重要的pthread_create函数来讲讲。

pthread_create函数是 POSIX 线程库中的一个函数,用于创建一个新线程。它的定义如下:

 #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

其中,thread 参数是一个指针,用于返回新线程的标识符;attr 参数是一个指向线程属性的指针,通常设为 NULLstart_routine 参数是一个函数指针,表示新线程的入口点;arg 参数是传递给新线程的参数。

返回值:如果成功,将返回0;如果出错,它将返回错误号,并且*thread的内容是未定义的。

新线程会以下列其中的一种方式终止:

  • 调用pthread_exit,指定一个退出状态值,该值可供调用pthread_join的同一进程中的另一个线程使用。
  • start_routine返回。这等效于使用return语句中提供的值调用pthread_exit
  • 被取消(pthread_cancel)。
  • 进程中的任何线程调用exit,或者主线程执行从main()返回。这会导致进程中的所有线程终止。