欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网络编程 26_阻塞 I/O 线程模型

程序员文章站 2024-02-29 20:29:04
...

目标

使用轻量级的线程处理多个连接,为每一个连接创建一个独立的线程去服务

一、POSIX 线程模型

POSIX 线程是现代 UINX 系统提供的处理线程的标准接口

1.1 创建线程

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

参数

  • thread:线程 ID(tid),类型为 pthread_t,输出参数
  • attr:线程属性(如:优先级、守护进程等),通过 pthread_attr_t 设置,一般置为 NULL
  • start_routine:线程入口函数
  • arg:线程入口函数结构体参数

返回值

  • 0:成功
  • 非 0:错误码

在线程入口函数中,调用 pthread_self() 函数获取线程 tid

pthread_t pthread_self(void);

1.2 终止线程

父线程终止所有子线程

父线程等待其它所有子线程终止,然后父线程终止

void pthread_exit(void *retval);

父线程终止某个子线程

父线程终止某个子线程

int pthread_cancel(pthread_t thread);

回收已终止线程资源

调用后主线程阻塞,直到子线程自然终止,不强迫子线程终止

int pthread_join(pthread_t thread, void **retval);

1.3 分离线程

分离线程不能被其他线程杀死或回收资源,当父线程不需要对每个子线程进行关闭时,可在线程入口函数一开始时将线程设置成分离的,当线程终止后会自动回收相关的线程资源

int pthread_detach(pthread_t thread);

二、阻塞 I/O 线程模型

服务端

thread_server.c

#include "common.h"

char rot13_char(char c) {
	if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M')) {
		return c + 13;
	} else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z')) {
		return c - 13;
	}
        return c;
}

void loop_echo(int fd) {
	char outbuf[512];
	int i;
	ssize_t result;

	while (1) {
		result = recv(fd, &outbuf, sizeof(outbuf), 0);

		if (result == 0) {
			break;
		} else if (result == -1) {
			error(1, errno, "recv failed");
			break;
		}

		for (i = 0; i < result; i++) {
			outbuf[i] = rot13_char(outbuf[i]);

			if (outbuf[i] == '\n') {
				send(fd, outbuf, result, 0);
				break;
			}
		}
	}
}

// 线程入口函数
void *thread_run(void *arg) {
	// 设置线程成分离的
	pthread_detach(pthread_self());
	int fd = *((int *)arg);
	loop_echo(fd);
}

int main(int argc, char **argv) {
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	// 端口复用
	int on = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	socklen_t servlen = sizeof(servaddr);
	int bind_rt = bind(listenfd, (struct sockaddr *)&servaddr, servlen);
	if (bind_rt < 0) {
		error(1, errno, "bind failed");
	}

	int listen_rt = listen(listenfd, LISTENQ);
	if (listen_rt < 0) {
		error(1, errno, "listen failed");
	}

	pthread_t tid;

	while (1) {
		struct sockaddr_storage ss;
		socklen_t slen = sizeof(ss);
		int fd = accept(listenfd, (struct sockaddr *)&ss, &slen);
		if (fd < 0) {
			error(1, errno, "accept failed");
		} else {
			// 为每一个连接创建一个线程去服务
			pthread_create(&tid, NULL, thread_run, &fd);
		}
	}
}

头文件 common.h

#ifndef CHAP_26_COMMON_H
#define CHAP_26_COMMON_H

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <strings.h>
#include    <sys/socket.h>    /* basic socket definitions */
#include    <netinet/in.h>    /* sockaddr_in{} and other Internet defns */
#include    <arpa/inet.h>    /* inet(3) functions */
#include    <errno.h>

#include    <unistd.h>

#include    <pthread.h>

void error(int status, int err, char *fmt, ...);

#define    SERV_PORT      43211
#define    LISTENQ        1024

#endif //CHAP_26_COMMON_H

三、CMake 管理当前项目

① 代码组成

-CMakeLists.txt
-include:存放头文件
-src:存放源代码
网络编程 26_阻塞 I/O 线程模型

CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
ADD_SUBDIRECTORY(src)

include 目录:include/common.h(common.h 上面有)
网络编程 26_阻塞 I/O 线程模型

src 目录(thread_server.c 上面有)
网络编程 26_阻塞 I/O 线程模型

src/CmakeLists.txt

ADD_EXECUTABLE(thread_server thread_server.c)
TARGET_LINK_LIBRARIES(thread_server pthread)

② 创建并进入 build 目录

mkdir build && cd build

网络编程 26_阻塞 I/O 线程模型

③ 外部编译

cmake .. && make

网络编程 26_阻塞 I/O 线程模型

四、测试

可以使用一个或多个 telnet 客户端连接服务器,检验交互是否正常

测试步骤
① 打开三个命令行窗口
② 其中一个窗口先执行服务器命令,输入命令 ./thread_server 后回车
③ 其余窗口执行客户端命令,输入命令 ./telnet-client 127.0.0.1 43211 后回车

左:服务端;右上、右下:客户端
网络编程 26_阻塞 I/O 线程模型

上面的阻塞 I/O 线程模型的服务端程序,可以并发处理多个不同的客户端连接,互不干扰

总结

  • 每一个进程都会产生一个线程,一般称主线程,主线程可以再产生子线程
  • 在同一进程下,线程上下文切换的开销比进程要小得多
  • 线程上下文切换
    CPU 从一个计算场景切换到另一个计算场景,程序计数器、寄存器等值都要重新载入新场景的值,这个过程就是线程的上下文切换
相关标签: 网络编程