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

相关推荐
IT摆渡者8 分钟前
1panel 配置申请手动解析网站SSL证书
服务器·网络协议·ssl
mounter62510 分钟前
Linux 7.2 引入更灵活的块设备错误注入机制
linux·linux kernel·kernel·disk·error injection
疯狂打码的少年28 分钟前
【计算机网络】子网掩码与子网划分计算(含CIDR表示法)
服务器·笔记·计算机网络·php
制造业的搬运工30 分钟前
智能家居工控PCB可靠性检测规范与制程管控实践
大数据·网络·人工智能·制造·pcb工艺
cpolar技术支持32 分钟前
Docker 容器启动失败怎么查?端口、日志、权限与网络排障完整教程
linux·docker·容器·cpolar·网络排障
想要入门的程序猿1 小时前
回调函数学习
java·网络·学习
网硕互联的小客服1 小时前
怎样在CMD里用chkntfs /x C: 命令来取消磁盘自检?——原理与实践全解
运维·服务器·网络
布莱克6051 小时前
Linux 常用命令详解:从入门到实战
linux·运维·服务器
朽棘不雕2 小时前
可执行程序的格式
linux
制冷技术咨询与服务3 小时前
纯电牵引车高压电源侧介绍
网络·汽车·纯电牵引车高压电源侧介绍