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

相关推荐
面向对象World2 分钟前
Z8350 Broadcom SDIO网卡调试Ubuntu 22.04 Server版
linux·运维·ubuntu
是枚小菜鸡儿吖4 分钟前
IT技术员远程修电脑用什么软件好?低延迟高清远控工具横评
网络·智能路由器·电脑
Irissgwe6 分钟前
12、多路转接 select
linux·io多路转接·select
eam0511239 分钟前
BGP反射器及联邦实验
网络
ZFSS18 分钟前
BYOK(自带密钥)使用指南
运维·服务器·前端·人工智能·midjourney
小子想咋滴19 分钟前
bgp联邦实验
网络·智能路由器
无足鸟ICT21 分钟前
【RHCA+】编辑多个文件
linux
遇事不決洛必達24 分钟前
【数据库系列】本地映射云服务器Mysql的方法
服务器·数据库·mysql·定时任务
fengyehongWorld36 分钟前
Linux fd命令
linux
AIMath~37 分钟前
hermes agent安装在Linux centos中
linux·运维·服务器