TCP网络通信——多线程

前面分别用多进程和多路复用完成了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

相关推荐
意疏22 分钟前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
虚拟网络工程师27 分钟前
【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)
运维·服务器·网络·数据库·mariadb
BLEACH-heiqiyihu30 分钟前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
勤奋的小王同学~1 小时前
项目虚拟机配置测试环境
服务器
007php0071 小时前
GoZero 上传文件File到阿里云 OSS 报错及优化方案
服务器·开发语言·数据库·python·阿里云·架构·golang
JosieBook1 小时前
【网络工程】查看自己电脑网络IP,检查网络是否连通
服务器·网络·tcp/ip
我的K84092 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900432 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo2 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器
TheITSea2 小时前
云服务器宝塔安装静态网页 WordPress、VuePress流程记录
java·服务器·数据库