网络编程 26(上)阻塞 I/O 线程模型

目标

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

一、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:存放源代码
代码组成

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 上面有)
include 目录

src 目录(thread_server.c 上面有)
src目录

src/CmakeLists.txt

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

② 创建并进入 build 目录

mkdir build && cd build

创建并进入 build 目录

③ 外部编译

cmake .. && make

外部编译

四、测试

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

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

左:服务端;右上、右下:客户端
测试

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

总结

  • 每一个进程都会产生一个线程,一般称主线程,主线程可以再产生子线程
  • 在同一进程下,线程上下文切换的开销比进程要小得多
  • 线程上下文切换
    CPU 从一个计算场景切换到另一个计算场景,程序计数器、寄存器等值都要重新载入新场景的值,这个过程就是线程的上下文切换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值