《TCP多线程通信代码C语言开发流程解析》
TCP多线程通信代码开发流程解析——让步骤清晰明了
在编写 TCP 通信代码时,我经常中途忘记需要实现哪些步骤。为了更好地熟悉整个流程,并提高今后的开发效率,我打算自己写一篇文章,系统地整理并总结编写 TCP 通信所需的关键代码和步骤。
服务端的流程
服务端通过主线程接受客户端连接,并为每个连接创建独立会话线程(管理线程),在会话线程中再启两个子线程分别负责读和写。
1.接收客户端并为其分配线程
-
创建
sockaddr_in
结构体及socket
套接字struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(port); int len_server_addr=sizeof(server_addr);//结构体的大小 //套接字 int serverfd=socket(AF_INET,SOCK_STREAM,0);
-
bind
绑定temp_result=bind(serverfd,(struct sockaddr*)&server_addr,len_server_addr);if(temp_result){handle_error("bind",temp_result);}
-
listen
监听temp_result=listen(serverfd,10);//第二个参数是最多可以接受多少个客户端连接请求if(temp_result){handle_error("listen",temp_result);}
-
创建线程
accept
连接关键点
pthread_t fd;
只是个变量。- 每次调用
pthread_create
,都会把新建线程的 ID 写到fd
里。 - 你紧接着又
pthread_detach(fd)
,把这个线程设置为**“分离态”**,这样它结束时系统能自动回收资源。 - 下一次循环时,
pthread_create
会再次把新的线程 ID 写进fd
(覆盖掉旧的)。
pthread_t fd; struct sockaddr_in client_addr; memset(&client_addr,0,sizeof(client_addr)); int client_addr_len=sizeof(client_addr); while (1) {int *client_fd=malloc(sizeof(int));*client_fd=accept(serverfd,(struct sockaddr*)&client_addr,&client_addr_len);printf("客户端%d,ip:%s,已成功与服务端连接\n",*client_fd,inet_ntoa(client_addr.sin_addr));pthread_create(&fd,NULL,&raw,client_fd);pthread_detach(fd); } //释放资源 close(serverfd);
2.管理该客户端的读写子线程
为什么要传输文件描述符,这就相当于一个通道,没有他我们就找不到目标地址,也就无法进行读写操作,这就相当于一个通讯大门
void *read_func(void *arg)
{int server_fd = *(int *)arg;char *read_buf = malloc(1024);int temp_result;while (1){temp_result = recv(server_fd, read_buf, 1024, 0);if (temp_result == -1){perror("recv");}else if (temp_result == 0){printf("客户端关闭了连接\n");break;}read_buf[temp_result] = '\0'; // 安全添加结束符,防止乱码printf("客户端:%s\n", read_buf);}free(read_buf);return NULL;
}void *write_func(void *arg)
{int server_fd = *(int *)arg;char *write_buf = malloc(1024);int temp_result;while (1){memset(write_buf, 0, 1024);if (fgets(write_buf, 1024, stdin) == NULL){printf("输入有误\n");break;}if (strncmp(write_buf, "exit", 4) == 0){printf("服务端主动退出\n");break;}temp_result = send(server_fd, write_buf, strlen(write_buf), 0);if (temp_result == -1){perror("send");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}else{printf("服务端:%s\n", write_buf);}}free(write_buf);return NULL;
}void *raw(void * argv)
{int sockfd=*(int*)argv;pthread_t read_t,write_t;//创建子线程读取数据并打印到终端pthread_create(&read_t, NULL, read_func, argv);//创建子线程写入数据传到服务端pthread_create(&write_t, NULL, write_func, argv);// 主线程等待子线程退出pthread_join(read_t, NULL);pthread_join(write_t, NULL);free(argv);printf("关闭资源\n");close(sockfd);
}
3.完整代码展示
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>#define port 6666#define handle_error(cmd, result) \if (result < 0) \{ \perror(cmd); \return -1; \}void *read_func(void *arg)
{int server_fd = *(int *)arg;char *read_buf = malloc(1024);int temp_result;while (1){temp_result = recv(server_fd, read_buf, 1024, 0);if (temp_result == -1){perror("recv");}else if (temp_result == 0){printf("客户端关闭了连接\n");break;}read_buf[temp_result] = '\0'; // 安全添加结束符,防止乱码printf("客户端:%s\n", read_buf);}free(read_buf);return NULL;
}void *write_func(void *arg)
{int server_fd = *(int *)arg;char *write_buf = malloc(1024);int temp_result;while (1){memset(write_buf, 0, 1024);if (fgets(write_buf, 1024, stdin) == NULL){printf("输入有误\n");break;}if (strncmp(write_buf, "exit", 4) == 0){printf("服务端主动退出\n");break;}temp_result = send(server_fd, write_buf, strlen(write_buf), 0);if (temp_result == -1){perror("send");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}else{printf("服务端:%s\n", write_buf);}}free(write_buf);return NULL;
}void *raw(void * argv)
{int sockfd=*(int*)argv;pthread_t read_t,write_t;//创建子线程读取数据并打印到终端pthread_create(&read_t, NULL, read_func, argv);//创建子线程写入数据传到服务端pthread_create(&write_t, NULL, write_func, argv);// 主线程等待子线程退出pthread_join(read_t, NULL);pthread_join(write_t, NULL);free(argv);printf("关闭资源\n");close(sockfd);
}int main(int argc, char const *argv[])
{int temp_result;//声明server结构体以及初始化struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(server_addr));server_addr.sin_addr.s_addr=htonl(INADDR_ANY);server_addr.sin_family=AF_INET;server_addr.sin_port=htons(port);int len_server_addr=sizeof(server_addr);//套接字int serverfd=socket(AF_INET,SOCK_STREAM,0);//绑定temp_result=bind(serverfd,(struct sockaddr*)&server_addr,len_server_addr);if(temp_result){handle_error("bind",temp_result);}//监听temp_result=listen(serverfd,10);if(temp_result){handle_error("listen",temp_result);}//接收pthread_t fd;struct sockaddr_in client_addr;memset(&client_addr,0,sizeof(client_addr));int client_addr_len=sizeof(client_addr);while (1){int *client_fd=malloc(sizeof(int));*client_fd=accept(serverfd,(struct sockaddr*)&client_addr,&client_addr_len);printf("客户端%d,ip:%s,已成功与服务端连接\n",*client_fd,inet_ntoa(client_addr.sin_addr));pthread_create(&fd,NULL,&raw,client_fd);pthread_detach(fd);}//释放资源close(serverfd);return 0;
}
客户端的流程
- 创建
sockaddr_in
初始化 connect
连接
1.连接服务端
int main(int argc, char const *argv[])
{// 套接字int client_fd = socket(AF_INET, SOCK_STREAM, 0);// 初始化struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(client_addr));client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");client_addr.sin_family = AF_INET;client_addr.sin_port = htons(6666);int client_addr_len = sizeof(client_addr);int sockfd = connect(client_fd, (struct sockaddr *)&client_addr, client_addr_len);if (sockfd == -1){handle_error("connect", sockfd);}pthread_t read_t, write_t;// 创建子线程读取数据并打印到终端pthread_create(&read_t, NULL, &read_func, (void *)&client_fd);// 创建子线程写入数据传到服务端pthread_create(&write_t, NULL, &write_func, (void *)&client_fd);// 主线程等待子线程退出pthread_join(read_t, NULL);pthread_join(write_t, NULL);printf("关闭资源\n");close(sockfd);return 0;
}
2.客户端的读写子线程
和服务端实现方法一样
void *read_func(void *arg)
{int server_fd = *(int *)arg;char *read_buf = malloc(1024);int temp_result;while (1){temp_result = recv(server_fd, read_buf, 1024, 0);if (temp_result == -1){perror("recv");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}read_buf[temp_result] = '\0'; // 安全添加结束符,防止乱码printf("服务器:%s\n", read_buf);}free(read_buf);return NULL;
}void *write_func(void *arg)
{int server_fd = *(int *)arg;char *write_buf = malloc(1024);int temp_result;while (1){memset(write_buf, 0, 1024);if (fgets(write_buf, 1024, stdin) == NULL){printf("输入有误\n");break;}if (strncmp(write_buf, "exit", 4) == 0){printf("客户端主动退出\n");break;}temp_result = send(server_fd, write_buf, strlen(write_buf), 0);if (temp_result == -1){perror("send");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}else{printf("客户端:%s\n", write_buf);}}free(write_buf);return NULL;
}
3.完整代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>#define handle_error(cmd, result) \if (result < 0) \{ \perror(cmd); \return -1; \}void *read_func(void *arg)
{int server_fd = *(int *)arg;char *read_buf = malloc(1024);int temp_result;while (1){temp_result = recv(server_fd, read_buf, 1024, 0);if (temp_result == -1){perror("recv");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}read_buf[temp_result] = '\0'; // 安全添加结束符,防止乱码printf("服务器:%s\n", read_buf);}free(read_buf);return NULL;
}void *write_func(void *arg)
{int server_fd = *(int *)arg;char *write_buf = malloc(1024);int temp_result;while (1){memset(write_buf, 0, 1024);if (fgets(write_buf, 1024, stdin) == NULL){printf("输入有误\n");break;}if (strncmp(write_buf, "exit", 4) == 0){printf("客户端主动退出\n");break;}temp_result = send(server_fd, write_buf, strlen(write_buf), 0);if (temp_result == -1){perror("send");}else if (temp_result == 0){printf("服务器关闭了连接\n");break;}else{printf("客户端:%s\n", write_buf);}}free(write_buf);return NULL;
}int main(int argc, char const *argv[])
{// 套接字int client_fd = socket(AF_INET, SOCK_STREAM, 0);// 初始化struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(client_addr));client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");client_addr.sin_family = AF_INET;client_addr.sin_port = htons(6666);int client_addr_len = sizeof(client_addr);int sockfd = connect(client_fd, (struct sockaddr *)&client_addr, client_addr_len);if (sockfd == -1){handle_error("connect", sockfd);}pthread_t read_t, write_t;// 创建子线程读取数据并打印到终端pthread_create(&read_t, NULL, &read_func, (void *)&client_fd);// 创建子线程写入数据传到服务端pthread_create(&write_t, NULL, &write_func, (void *)&client_fd);// 主线程等待子线程退出pthread_join(read_t, NULL);pthread_join(write_t, NULL);printf("关闭资源\n");close(sockfd);return 0;
}
总结
TCP 多线程服务端的实现,看似复杂,但本质就是“主线程接收连接、子线程处理读写”。掌握文件描述符和线程管理,你就掌握了网络通信的核心钥匙。希望本文能帮助你对网络编程有更直观的理解。