前面分别用多进程和多路复用完成了TCP网络通信,本文就来讲讲多线程的TCP通信。首先来了解一下线程的概念:
1、线程是进程的执行路线,它是进程内部的控制序列,或者说线程是进程的一部分(进程是一个资源单位,线程是执行单位,线程是进程的一部分,负责真正的执行)
2、线程是轻量级的,没有自己独立的代码段、数据段、bss段、堆、环境变量、命令行参数、文件描述符、信号处理函数、当前目录信息等资源
3、线程有自己独立的栈内存、线程ID、错误码、信号屏蔽掩码
4、一个进程中可以包含多个线程(多个执行路线),但是至少有一个线程在活动,称为主线程
5、ps -T -p <pid> 查看pid进程中的线程情况 或者htop命令也可以查看
6、线程是进程的实体,可以当做系统独立的任务调度和分配的基本单位
7、线程有不同的状态、属性,系统提供了线程的控制接口,例如:创建、销毁、控制
8、进程中的所有线程同在一个虚拟地址空间中,进程中的所有资源对于线程而言都是共享的,因此当多个线程协同工作时需要解决资源竞争问题(加锁)
9、线程的系统开销很小、任务切换快、多个线程之间不需要数据交换、因此不需要类似于XSI的通信机制,因此使用线程简单而高效
10、线程之间有优先级的差异
在了解线程的概念之后,要想代码实现多线程TCP,还需要了解一些函数的基本用法:
1、int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建新线程
thread:输出型参数,用于获取线程ID
attr: 用于设置线程属性,一般写NULL即可
start_routine:线程的入口函数,相当于主线程的main函数
arg:传递给start_routine入口函数的参数
返回值:成功返回0,失败返回错误编码
注意:入口函数的参数、返回值要确保它的可持续性,因此不太适合使用栈内存,可以考虑堆内存、全局变量
2、int pthread_join(pthread_t thread, void **retval);
功能:等待线程结束,并获取该线程结束时的入口函数的返回值,并释放线程资源
thread:要等待的线程的ID
retval:用于存储线程结束时返回值的地址,拿到返回值变量本身
返回值:成功返回0,失败返回错误编码
3、 pthread_t pthread_self(void);
功能:获取当前线程的线程ID 此函数在哪里调用就取哪里的线程ID
4、int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程ID是否一致
返回值:一致返回非零值,不一致返回0
注意:在个别操作系统下,pthread_t 是以结构实现的,大部分是以 unsigned long 呈现,为了可移植性,不能直接使用 == 比较
pthread_t tid; //不要初始化 提高可移植性
下面就来看一下代码实现部分:
服务端:
cs
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
typedef struct Client
{
int cli_fd;
pthread_t tid;
struct sockaddr_in cli_addr;
} Client;
size_t client_count = 0;
void* run(void* arg)
{
//立刻保存,否则新的连接可能会覆盖上一个连接,导致操作的都是最后一个线程
int cli_fd = *(int*)arg;
char buf[4096];
size_t buf_size = sizeof(buf);
while (1)
{
int ret = recv(cli_fd, buf, buf_size, 0);
if (ret <= 0 || strcmp(buf, "quit") == 0)
{
printf("客户端%d退出\n", cli_fd);
close(cli_fd);
return NULL;
}
printf("from %d recv: %s bits: %d tid:%lu\n", cli_fd, buf, ret,pthread_self());
strcat(buf, ":return");
send(cli_fd, buf, strlen(buf) + 1, 0);
if (ret <= 0)
{
close(cli_fd);
printf("客户端%d退出\n", cli_fd);
break;
}
}
close(cli_fd);
pthread_exit(NULL);
}
int main(int argc, const char* argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in addr = {},cli_addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(8866);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t addrlen = sizeof(addr);
if (bind(sockfd, (struct sockaddr*)&addr, addrlen) < 0)
{
perror("bind");
return -1;
}
if (listen(sockfd, 5) < 0)
{
perror("listen");
return -1;
}
//准备服务客户端的结构体50个
Client *client = calloc(50,sizeof(Client));
size_t index = 0;
while (1)
{
//找空闲的client(cli_fd为0,认为是空闲)
while(client[index].cli_fd)
{
//若没有空闲,则等待10s钟再尝试
if(client_count>=50)
{
sleep(10);
}
index = (index+1)%50;
}
//从上面的循环出来。则第index个client是空闲的
client[index].cli_fd = accept(sockfd, (struct sockaddr*)&client[index].cli_addr, &addrlen);
if(client[index].cli_fd<0)
{
perror("accept");
continue;
}
pthread_create(&client[index].tid, NULL, run, (void*)(&client[index].cli_fd));
client_count++;
/*pthread_t tid;
int cli_fd = accept(sockfd, (struct sockaddr*)&cli_addr, &addrlen);
if (cli_fd < 0)
{
perror("accept");
continue;
}
//创建线程处理客户端请求
pthread_create(&tid, NULL, run, (void*)(&cli_fd));
//usleep(1000);
pthread_detach(tid);*/
}
return 0;
}
客户端:
cs
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/un.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
typedef struct sockaddr *SP;
int main(int argc,const char* argv[])
{
//创建socket
int cli_fd=socket(AF_INET,SOCK_STREAM,0);
if(cli_fd<0)
{
perror("socket");
return -1;
}
//准备通信地址
struct sockaddr_in addr={};
addr.sin_family=AF_INET;
addr.sin_port=htons(8866);
addr.sin_addr.s_addr=inet_addr("127.0.0.1");
socklen_t addrlen=sizeof(addr);
//连接服务器
if(connect(cli_fd,(SP)&addr,addrlen))
{
perror("connect");
return -1;
}
char buf[4096];
size_t buf_size=sizeof(buf);
while(1)
{
//发送请求
printf(">>>>>");
scanf("%s",buf);
int ret=send(cli_fd,buf,strlen(buf)+1,0);
//ret=write(cli_fd,buf,strlen(buf)+1);
if(ret<=0)
{
printf("服务器正在升级,请稍后重试\n");
break;
}
if(0==strcmp("quit",buf))
{
printf("通信结束\n");
break;
}
//接收请求
//int ret=read(cli_fd,buf,buf_size);
ret=recv(cli_fd,buf,buf_size,0);
if(ret<=0)
{
printf("服务器正在维护,请稍候重试\n");
break;
}
printf("read:%s bits:%d\n",buf,ret);
}
return 0;
}
over