阻塞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参数是一个指向线程属性的指针,通常设为NULL;start_routine参数是一个函数指针,表示新线程的入口点;arg参数是传递给新线程的参数。返回值:如果成功,将返回
0;如果出错,它将返回错误号,并且*thread的内容是未定义的。新线程会以下列其中的一种方式终止:
- 调用
pthread_exit,指定一个退出状态值,该值可供调用pthread_join的同一进程中的另一个线程使用。- 从
start_routine返回。这等效于使用return语句中提供的值调用pthread_exit。- 被取消(
pthread_cancel)。- 进程中的任何线程调用
exit,或者主线程执行从main()返回。这会导致进程中的所有线程终止。