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

相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao3 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
大树884 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush44 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5204 天前
Linux 11 动态监控指令top
linux