一、问题引入
Linux网络编程:socket & fork()多进程 实现clients/server通信 随笔介绍了通过fork()多进程实现了服务器与多客户端通信。但除了多进程能实现之外,多线程也是一种实现方式。
重要的是,多进程和多线程是涉及操作系统层次。随笔不仅要利用pthread_create()实现多线程编程,也要理解线程和进程的区别。
二、解决过程
client 代码无需修改,请参考 Linux网络编程:socket & fork()多进程 实现clients/server通信
2-1 server 代码
#include #include #include #include #include #include #include #include #include #include #include #include #define IP "10.8.198.227"#define PORT 8887#define gettid() syscall(__NR_gettid)#define PTHREAD_MAX_SIZE 3 // 允许最大客户端连接数typedef struct PTHREAD_DATA_ST{ pthread_t pthread_id; int connfd; char socket[128]; struct sockaddr_in cliaddr;}PTHREAD_DATA_ST;//static int g_pthread_num = 3; // 允许最大客户端连接数static struct PTHREAD_DATA_ST g_pthread_data[PTHREAD_MAX_SIZE];static void pthread_data_index_init(void){ for (int i = 0; i < PTHREAD_MAX_SIZE; i++) { memset(&g_pthread_data[i], 0 , sizeof(struct PTHREAD_DATA_ST)); g_pthread_data[i].connfd = -1; }}static int pthread_data_index_find(void){ int i; for (i = 0; i < PTHREAD_MAX_SIZE; i++) { if (g_pthread_data[i].connfd == -1) break; } return i;}static int string_toupper(const char *src, int str_len, char *dst){ int count = 0; for (int i = 0; i connfd; int recv_len, send_len; pid_t tid = gettid(); char read_buf[1024], write_buf[1024]; while (1) { memset(read_buf, 0, sizeof(read_buf)); memset(write_buf, 0, sizeof(write_buf)); recv_len = read(connfd, read_buf, sizeof(read_buf)); if (recv_len socket, tid); close(connfd); pthread->connfd = -1; pthread_exit(NULL); } printf("%s:%s(%d Byte)\n", pthread->socket, read_buf, recv_len); send_len = string_toupper(read_buf, strlen(read_buf), write_buf); write(connfd, write_buf, send_len); if (strcmp("exit", read_buf) == 0) { printf("%s exit, child %d terminated\n", pthread->socket, tid); close(connfd); pthread->connfd = -1; pthread_exit(NULL); } }}int main(void){ int listenfd, connfd; struct sockaddr_in server_sockaddr; struct sockaddr_in client_addr; char buf[1024]; char client_socket[128]; socklen_t length; int idx; int opt = 1; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = inet_addr(IP); listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { perror("socket error"); exit(1); } // 设置端口复用 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) < 0) { perror("bind error"); exit(1); } if (listen(listenfd, 5) < 0) { perror("listen error"); exit(1); } pthread_data_index_init(); while (1) { // 接受来自客户端的信息 printf("accept start \n"); memset(&client_addr, 0, sizeof(client_addr)); length = sizeof(client_addr); if ((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &length)) < 0) { if (errno == EINTR) continue; else { perror("accept error"); exit(1); } } idx = pthread_data_index_find(); if (idx == PTHREAD_MAX_SIZE) { printf("client connected upper limit, refused connect\n"); close(connfd); continue; } memset(&client_socket, 0, sizeof(client_socket)); printf("client addr:%s por:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)), ntohs(client_addr.sin_port)); snprintf(client_socket, sizeof(client_socket), "client socket (%s:%d)", inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)), ntohs(client_addr.sin_port)); g_pthread_data[idx].connfd = connfd; g_pthread_data[idx].cliaddr = client_addr; strcpy(g_pthread_data[idx].socket, client_socket); pthread_create(&(g_pthread_data[idx].pthread_id), NULL, pthread_handle, &(g_pthread_data[idx])); pthread_detach(g_pthread_data[idx].pthread_id); } close(listenfd); return EXIT_SUCCESS;}
2-2 编译运行
? 编译server.c时,出现 对‘pthread_create’未定义的引用
原因:pthread.h库不是linux系统的默认库,添加编译参数:-lpthread
即可 (或 pthread
也行)
- 编译源文件
gcc server.c -g -std=gnu99 -lpthread -o server
- client 1 和client 2连接server
- client 1 断开连接
- client 2 断开连接
? 在server未设置端口复用,server主动断开,重启报错: bind error: Adress already in use
解决方法:
int opt = 1;// 设置端口复用setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2-3 多线程处理
- 线程id
? 同一进程下的进程号pid相同,各个线程的线程号tid不相同
在linux中无法直接使用gettid()获取线程id,正确解决办法在源文件中添加如下宏:
#include #define gettid() syscall(__NR_gettid)
- 多线程
通过宏 PTHREAD_MAX_SIZE
定义线程最大数量,毕竟计算机资源是有限的。
accept()成功返回,此时某一个client已经完成TCP三次握手,接下来可以准备创建线程通信。但不能无脑创建线程,应检测当前已创建的线程数量,只有未超越线程最大数量才能创建线程。
1、父线程负责监听新的客户端连接,并创建新的线程
2、子线程负责和客户端进行通信
? 注意:使用多线程要将子线程设置为分离属性, 让线程在退出之后自己回收资源
// pthread_create(), pthread_detach(), pthread_exit()原型声明在如下头文件中#include /* * @param *thread 新线程创建成功后的线程指针 * @param *attr 创建线程时,设置属性。默认可设置为NULL * @param *(*start_routine) (void *) 线程运行函数指针 * @param *arg 线程运行函数指针的形参 * @return 若成功,返回值为0,否则失败 */int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);/* pthread_detact()在线程结束后,回收资源 * @param thread 新线程创建成功后的线程指针 * @return 若成功,返回值为0,否则失败 */int pthread_detach(pthread_t thread);//Compile and link with -pthread.
线程调用 | 描述 |
---|---|
pthread_create | 创建一个新线程 |
pthread_exit | 结束调用的线程 |
pthread_join | 等待一个特定的线程退出 |
pthread_yield | 释放CPU来运行另外一个线程 |
pthread_attr_init | 创建并初始化一个线程的属性结构 |
pthread_attr_destroy | 删除(销毁)一个线程的属性结构 |
三、反思总结3-1 进程
在进程模型中,计算机上所有可运行的软件,通常也包括草错系统,被组织成若干顺序进程,简称进程(process)。
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。
3-2 线程
线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由一个或多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。
四、参考引用
UNIX网络编程 卷1:套接字联网API 第3版
现代操作系统(第四版)
Unix网络编程学习笔记