TCP/IP协议学习笔记(五)Windows下多线程多客户端的TCP服务端的实现

2023-05-16

使用多线程来实现可与多个客户端通信的服务端。
当客户端连接上服务端之后,为该客户端创建一个新的线程,在该线程中与客户端进行通信。服务端程序中的主线程负责监听并接受客户端的连接请求,创建与客户端通信的线程。
另外,这是一个在windows下实现的回音(客户端发啥服务端就回传给客户端啥)服务端。

服务端

注意下多线程下共享内存,还有printf的使用,需要线程之间同步,共享资源同一时间只能有一个线程使用。

#include <stdio.h>
#include <windows.h>
#include <process.h>
#pragma comment(lib,"ws2_32.lib")


/* 服务端相关配置 */
#define SERVER_IP		"127.0.0.1"	//服务端IPV4地址
#define SERVER_PORT		1234		//服务端端口
#define MAX_CLIENT_NUMS	50U			//最大可连接客户端数量

SOCKET serverSocket = { 0 };			//服务端套接字句柄
SOCKADDR_IN serverSocketAddr = { 0 };	//服务端套接字配置结构体
volatile unsigned int currentClientNums = { 0 };	//当前连接的客户端数量


/* 客户端相关配置 */
typedef struct
{
	char isOnline;					//客户端是否在线 1则在线  0则不在线
	SOCKET clientSocket;			//客户端SOCKET套接字句柄
	SOCKADDR_IN clientSocketAddr;	//客户端套接字配置结构体
	HANDLE threadFunc;				//客户端对应的线程句柄
}CLIENT_t, * PCLIENT_t;

CLIENT_t client[MAX_CLIENT_NUMS] = { 0 };

void ClientThreadFunc(void*);		//客户端线程函数

#define RECEIVE_BUFF_SIZE 200U		//接收缓冲区字节大小


/* 线程同步相关 */
CRITICAL_SECTION cs_printf;//关键段(临界区)
CRITICAL_SECTION cs_currentClientNums;

int main()
{
	/* 初始化DLL */
	WSADATA wsadata = { 0 };
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)//版本2.2
	{
		printf("初始化DLL失败!\n");
		return 0;
	}

	/* 创建SOCKET */
	serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP套接字
	if (serverSocket == INVALID_SOCKET)
	{
		printf("创建服务端TCP套接字失败,错误代码为:%d\n", WSAGetLastError());
		goto END;
	}

	/* 配置服务端套接字信息 */
	serverSocketAddr.sin_family = AF_INET;//IPV4
	serverSocketAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);//IP地址
	serverSocketAddr.sin_port = htons(SERVER_PORT);//端口 
	if (bind(serverSocket, &serverSocketAddr, sizeof(serverSocketAddr)) == SOCKET_ERROR)//绑定服务端套接字
	{
		printf("绑定服务端套接字失败,错误代码为:%d\n", WSAGetLastError());
		goto END;
	}

	/* 服务端进入监听状态 */
	if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR)
	{
		printf("服务端进入监听状态失败,错误代码为:%d\n", WSAGetLastError());
		goto END;
	}

	/* 初始化关键段(临界区) */
	if (!InitializeCriticalSectionAndSpinCount(&cs_printf, 4000))
	{
		printf("关键段(临界区)cs_printf初始化失败!\n");
		DeleteCriticalSection(&cs_printf);//销毁关键段
		goto END;
	}
	InitializeCriticalSection(&cs_currentClientNums);

	/* 等待客户端连接服务端 */
	printf("服务端已经开启,IP:%s,端口:%d\n", inet_ntoa(serverSocketAddr.sin_addr), ntohs(serverSocketAddr.sin_port));

	while (1)
	{
		static SOCKET clientSocket = { 0 };//客户端套接字句柄
		static SOCKADDR_IN clientSocketAddr = { 0 };//客户端套接字配置结构体
		static unsigned int cLength = sizeof(clientSocketAddr);//结构体长度
		static unsigned int temp = { 0 };

		/* 接收客户端的连接请求 */
		/*
		* 没有新的连接程序会阻塞在这,accept是阻塞式函数
		* 直到出现异常,或者有新的连接
		*/
		clientSocket = accept(serverSocket, &clientSocketAddr, &cLength);
		if (clientSocket == SOCKET_ERROR)
		{
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("客户端连接错误,错误代码为:%d\n", WSAGetLastError());
			LeaveCriticalSection(&cs_printf);//离开关键段
			continue;//继续等待下一次连接
		}
		else
		{
			/* 判断是否超过允许连接的客户端数量 */
			EnterCriticalSection(&cs_currentClientNums);//进入关键段
			temp = currentClientNums + 1;
			LeaveCriticalSection(&cs_currentClientNums);//离开关键段
			if (temp > MAX_CLIENT_NUMS)
			{
				send(clientSocket, "连接已断开,已经超过可连接的最大数量!\n", strlen("连接已断开,已经超过可连接的最大数量!\n"), 0);
				EnterCriticalSection(&cs_printf);//进入关键段
				printf("有新的客户端连接请求,IP:%s,端口:%d,但已经超过可连接的最大数量!\n", inet_ntoa(clientSocketAddr.sin_addr), ntohs(clientSocketAddr.sin_port));
				LeaveCriticalSection(&cs_printf);//离开关键段
				closesocket(clientSocket);//断开与客户端的连接
			}
			else
			{
				for (size_t i = 0; i < MAX_CLIENT_NUMS; i++)
				{
					/* 遍历哪个客户端断开连接了,如果断开连接则将相应的线程句柄关闭 */
					if (client[i].isOnline == 0)
					{
						client[i].isOnline = 1;//设置客户端在线
						client[i].clientSocket = clientSocket;//保存客户端SOCKET套接字
						memcpy(&client[i].clientSocketAddr, &clientSocketAddr, sizeof(clientSocketAddr));//保存客户端SOCKET配置结构体
						/* 创建客户端线程 */
						//注意:_endthread 会自动关闭线程句柄。 (该行为与 Win32 ExitThread API 不同。) 
						//因此,当你使用 _beginthread 和 _endthread 时,不要通过调用 Win32 CloseHandle API 来显式关闭线程句柄。
						client[i].threadFunc = _beginthread(ClientThreadFunc, 0, &client[i]);

						EnterCriticalSection(&cs_currentClientNums);//进入关键段
						EnterCriticalSection(&cs_printf);//进入关键段
						currentClientNums++;//当前在线客户端数量加一
						printf("当前已有 %d 个客户端连接,最多可连接 %d 个客户端\n", currentClientNums, MAX_CLIENT_NUMS);
						LeaveCriticalSection(&cs_printf);//离开关键段
						LeaveCriticalSection(&cs_currentClientNums);//离开关键段
						break;
					}
				}
			}
		}
	}

	DeleteCriticalSection(&cs_currentClientNums);//销毁关键段

END:
	/* 断开服务端套接字 */
	closesocket(serverSocket);

	/* 终止DLL的调用 */
	WSACleanup(wsadata);

	return 0;
}


/* 客户端线程 */
void ClientThreadFunc(void* arg)
{
	PCLIENT_t client = (PCLIENT_t)arg;
	char receiveBuff[RECEIVE_BUFF_SIZE] = { 0 };//接收来自客户端的数据缓冲区
	int result = { 0 };//recv函数返回值
	unsigned short clientPort = ntohs(client->clientSocketAddr.sin_port);//客户端端口
	IN_ADDR clientIP = client->clientSocketAddr.sin_addr;//客户端IP

	EnterCriticalSection(&cs_printf);//进入关键段
	printf("有新的客户端连接,IP:%s,端口:%d\n", inet_ntoa(clientIP), clientPort);
	LeaveCriticalSection(&cs_printf);//离开关键段

	/* 发送欢迎信息至客户端 */
	send(client->clientSocket, "Hello i am server\n", strlen("Hello i am server\n"), 0);

	while (1)
	{
		/* 接收来自客户端的数据 */
		/*
		* recv函数 的实质就是从socket的缓冲区里拷贝出数据
		* 返回值就是拷贝出字节数的大小。
		* 当缓冲区内没有内容的时候,会处于阻塞
		* 状态,这个while函数会停在这里。直到新的数据进来或者出现异常。
		*/
		result = recv(client->clientSocket, receiveBuff, RECEIVE_BUFF_SIZE, 0);
		if (result > 0)
		{
			//添加字符串结束标志,便于用字符串输出
			if (result >= RECEIVE_BUFF_SIZE)
			{
				receiveBuff[RECEIVE_BUFF_SIZE - 1] = '\0';
			}
			else
			{
				receiveBuff[result] = '\0';
			}
			send(client->clientSocket, receiveBuff, strlen(receiveBuff), 0);//数据回传
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("收到来自IP:%s,端口:%d客户端的信息:%s\n", inet_ntoa(clientIP), clientPort, receiveBuff);
			LeaveCriticalSection(&cs_printf);//离开关键段
		}
		else if (result == 0)//result == 0 说明客户端断开连接
		{
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("客户端断开连接,IP:%s,端口:%d\n", inet_ntoa(clientIP), clientPort);
			LeaveCriticalSection(&cs_printf);//离开关键段
			break;
		}
		else//result < 0 说明出现异常
		{
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("与客户端通信时发生异常,IP:%s,端口:%d\n", inet_ntoa(clientIP), clientPort);
			LeaveCriticalSection(&cs_printf);//离开关键段
			break;
		}
	}

	closesocket(client->clientSocket);//断开连接
	client->isOnline = 0;//设置客户端不在线
	EnterCriticalSection(&cs_currentClientNums);//进入关键段
	currentClientNums--;//当前在线客户端数量减一
	LeaveCriticalSection(&cs_currentClientNums);//离开关键段
	_endthread();//终止当前线程
}

客户端

#include <stdio.h>
#include <windows.h>
#include <process.h>
#pragma comment(lib,"ws2_32.lib")


/* 服务端信息 */
#define SERVER_IP			"127.0.0.1"	//服务端IPV4地址
#define SERVER_PORT			1234		//服务端端口
SOCKADDR_IN serverSocketAddr = { 0 };	//服务端套接字配置结构体

/* 客户端相关配置 */
SOCKET clientSocket = { 0 };			//客户端SOCKET套接字
#define BUFF_SIZE 200U					//缓冲区字节大小
char sendBuff[BUFF_SIZE] = { 0 };		//发送缓冲区
char receiveBuff[BUFF_SIZE] = { 0 };	//接收缓冲区
void RecevieThreadFunc(void*);			//客户端接收来自服务端的数据线程函数
volatile char isOnline = { 0 };			//客户端是否在线 1则在线  0则不在线

/* 线程同步相关 */
CRITICAL_SECTION cs_printf;//关键段(临界区)
CRITICAL_SECTION cs_isOnline;


int main()
{
	/* 初始化DLL */
	WSADATA wsadata = { 0 };
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)//版本2.2
	{
		printf("初始化DLL失败!\n");
		return 0;
	}

	/* 创建SOCKET */
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP套接字
	if (clientSocket == INVALID_SOCKET)
	{
		printf("创建客户端TCP套接字失败,错误代码为:%d\n", WSAGetLastError());
		goto END;
	}

	/* 配置服务端套接字信息并连接服务端 */
	serverSocketAddr.sin_family = AF_INET;//IPV4
	serverSocketAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);//IP地址
	serverSocketAddr.sin_port = htons(SERVER_PORT);//端口
	if (connect(clientSocket, &serverSocketAddr, sizeof(serverSocketAddr)) == SOCKET_ERROR)//连接服务端
	{
		printf("连接服务端失败,错误代码为:%d\n", WSAGetLastError());
		goto END;
	}

	/* 初始化关键段(临界区) */
	if (!InitializeCriticalSectionAndSpinCount(&cs_printf, 4000))
	{
		printf("关键段(临界区)cs_printf初始化失败!\n");
		DeleteCriticalSection(&cs_printf);//销毁关键段
		goto END;
	}
	InitializeCriticalSection(&cs_isOnline);

	/* 客户端连接上了服务端 */
	printf("已经连接上服务端,服务端IP:%s,端口:%d\n", inet_ntoa(serverSocketAddr.sin_addr), ntohs(serverSocketAddr.sin_port));
	isOnline = 1;

	/* 创建接收来自服务端数据的线程 */
	_beginthread(RecevieThreadFunc, 0, NULL);

	/* 接收用户的输入并发送至服务端 */
	while (1)
	{
		EnterCriticalSection(&cs_isOnline);//进入关键段
		if (!isOnline)//判断客户端是否在线
		{
			LeaveCriticalSection(&cs_isOnline);
			goto END;
		}
		LeaveCriticalSection(&cs_isOnline);//离开关键段

		EnterCriticalSection(&cs_printf);//进入关键段
		printf("请输入需要发送至服务端的数据:\n");
		LeaveCriticalSection(&cs_printf);//离开关键段
		scanf("%s", sendBuff);
		send(clientSocket, sendBuff, strlen(sendBuff), 0);//发送至服务端
		Sleep(100);
	}

	DeleteCriticalSection(&cs_isOnline);//销毁关键段

END:
	/* 断开服务端套接字 */
	closesocket(clientSocket);

	/* 终止DLL的调用 */
	WSACleanup(wsadata);

	return 0;
}


void RecevieThreadFunc(void* arg)
{
	int result = { 0 };//recv函数返回值

	while (1)
	{
		/* 接收来自服务端的数据 */
		/*
		* recv函数 的实质就是从socket的缓冲区里拷贝出数据
		* 返回值就是拷贝出字节数的大小。
		* 当缓冲区内没有内容的时候,会处于阻塞
		* 状态,这个while函数会停在这里。直到新的数据进来或者出现异常。
		*/
		result = recv(clientSocket, receiveBuff, BUFF_SIZE, 0);
		if (result > 0)
		{
			//添加字符串结束标志,便于用字符串输出
			if (result >= BUFF_SIZE)
			{
				receiveBuff[BUFF_SIZE - 1] = '\0';
			}
			else
			{
				receiveBuff[result] = '\0';
			}
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("收到来自服务端的信息:%s\n", receiveBuff);
			LeaveCriticalSection(&cs_printf);//离开关键段
		}
		else if (result == 0)//result == 0 说明服务端断开连接
		{
			EnterCriticalSection(&cs_isOnline);//进入关键段
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("服务端断开连接\n");
			isOnline = 0;
			LeaveCriticalSection(&cs_printf);//离开关键段
			LeaveCriticalSection(&cs_isOnline);//离开关键段
			break;
		}
		else//result < 0 说明出现异常
		{
			EnterCriticalSection(&cs_printf);//进入关键段
			printf("与服务端通信时发生异常\n");
			LeaveCriticalSection(&cs_printf);//离开关键段
			break;
		}
	}

	closesocket(clientSocket);//断开连接
	_endthread();//终止当前线程
}

结果

服务端
在这里插入图片描述
客户端
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP/IP协议学习笔记(五)Windows下多线程多客户端的TCP服务端的实现 的相关文章

  • Linux防火墙添加白名单

    查看防火墙状态 systemctl status firewalld 开启防火墙 systemctl start firewalld service 开机启动防火墙 systemctl enable firewalld service 关闭
  • stream流对集合进行排序,取集合的topN

    package com tangbb test1 import java util ArrayList import java util Comparator import java util List import java util s
  • 1487 北极通讯网络

    1487 北极通讯网络 这个题就是用来维护一个最短路径吧 也不难 xff0c 就是用来求最小生成树的第k条边 并且 xff0c 这貌似和并查集还有一定的关系 我太傻了 xff0c 这里生成树的方法就是krusal xff0c 我居然想 xf
  • mybatis实现批量插入

    1 首先 创建一个简单的insert语句 lt insert id 61 insertname gt insert into names name values value lt insert gt 2 然后在java代码中像下面这样执行批
  • java实现简单的用户登录包含token

    要实现简单的用户登录包含token xff0c 可以按照以下步骤进行 xff1a 创建一个用户表 xff0c 在表中存储用户的用户名和密码等信息 当用户登录时 xff0c 首先检查用户提交的用户名和密码是否正确 xff0c 如果正确则生成一
  • tar.gz压缩,查看,解压

    压缩 span class token function tar span czf jpg tar gz jpg 将目录里所有jpg文件打包成jpg tar后 xff0c 并且将其用gzip压缩 xff0c 生成一个gzip压缩过的包 xf
  • C++调用shell脚本并传递参数

    C 43 43 调用脚本的两种方式 1 system 执行shell命令也就是向dos发送一条指令 xff0c system 会调用fork 产生子进程 xff0c 由子进程来调用 bin sh c string来执行参数string字符串
  • 学生成绩信息管理系统 C++(含源码)

    懂的都懂 经典大一大作业 我以前也是面向CSDN和GitHub搞定了 今天也来造福新的小朋友了 一 系统需求分析 学生成绩信息记录了学生的基本信息和成绩情况 包括 姓名 学号 整数 中间无分隔符 性别 院系 班级 各个学科及其成绩 总成绩
  • Vue项目导入ElementUI后,网页无显示,打不开Vue的Main页面

    错误如下 xff0c 这是因为我们的vue版本和node js版本太高了 xff0c 我们需要使用Element plus 执行 cnpm install save element plus 然后字main js中修改 import Ele
  • 递归求n的阶乘完整版

    递归函数 求n的阶乘 span class token keyword def span span class token function fun span span class token punctuation span Num sp
  • Ubuntu基础使用指南

    一 基础操作 1 终端重要热键 Tab xff1a 补全功能 Ctrl 43 c xff1a 中断目前程序 Ctrl 43 d xff1a 键盘输入结束 Ctrl 43 Shift 43 c xff1a 复制 Ctrl 43 Shift 4
  • Fixed Frame [base_link] does not exist

    出现这个问题请先检查有没有启动gazebo xff0c gazebo是否能正常加载模型 如果不能 xff0c 那么rviz很可能也加载不了相应的节点 启动了gazebo xff0c 在Fixed frame后可以选择节点 xff0c 没有启
  • 舵机控制(0°与90°之间反复)

  • 1488 新的开始

    1488 新的开始 新的开始 保证最少的花费 xff0c 那么很明显这就是一个最小生成树了 做题之前盲猜一下 xff0c 这个题应该要跑两遍最小生成树 xff0c 因为他说了使用两种情况 xff0c p和v 在井上修建一个和在各个井中间修建
  • Rancher2.7 + Jenkins CI/CD全流程保姆级最佳实践

    Rancher 43 Jenkins k8s集群 CI CD全流程最佳实践 CI方面 xff0c 官方推荐的视频教程等多是使用极狐Gitlab CI xff0c 但社区版极狐每月仅400分钟构造时间 xff0c 额外购买价格为1000分钟
  • linux驱动----内核模块

    目录 一 引言 二 模块的特点 三 模块程序的构成 必要内容 1 模块入口函数 2 模块出口函数 3 模块许可证声明 非必要内容 1 模块参数 2 模块导出符号 3 模块相关信息 四 模块操作命令 一 引言 我们往往需要对内核的大小进行控制
  • linux驱动----模块符号导出使用

    本文介绍一种 xff0c 一个模块调动另一个模块中定义的函数的方法 在内核中 xff0c 函数又叫做符号 xff0c 为了便于理解 xff0c 此处说成我们在C语言中熟悉的函数调用 xff0c 回想一下C语言中的函数调用 xff0c 如果我
  • gcc的编译流程

    xff08 1 xff09 预处理阶段 xff08 c gt i xff09 预处理器会处理所有的以 开头的命令 xff0c 这些都属于预处理命令 xff0c 也就是会将所有的头文件包含的东西插入到源文件中 xff0c 将所有定义的宏都在程
  • Linux驱动----mmap系统调用

    在介绍mmap之前 xff0c 我们首先要了解 xff0c 如果没有mmap xff0c 在我们对一个设备进行操作时 xff0c 内核中其实有映射一份该设备的物理内存地址 xff0c 这是一份虚拟地址 xff0c 但是这是属于内核的 xff
  • STM32的启动流程

    本文主要介绍 xff0c STM32从 CPU 上电复位执行第 1 条指令开始 xff08 汇编文件 xff09 到进入 C 程序 main 函数入口之间的那个部分 基本流程如下 1 确定启动方式 每个STM32的芯片上都有两个管脚BOOT

随机推荐

  • 推挽输出&&开漏输出

    在学习STM32的时候 xff0c 我发现了一个很值得研究学习的问题 xff0c 下面 xff0c 用我的理解来阐述一遍 xff0c 这其中的原理 首先请看电路图 在给GPIO配置输出的时候 xff0c 其有两种工作模式可选 xff0c 分
  • const

    const是一个限定符 xff0c 被const限定的变量其值不会被改变 目录 1 修饰变量 2 修饰指针 3 修饰引用 4 修饰成员函数 5 宏定义 define 和 const 常量 1 修饰变量 指向的变量的值不可改变 xff0c 次
  • 用联合体(union)判断大小端

    1 联合体的概念 联合 xff08 union xff09 是一种节省空间的特殊的类 xff0c 一个 union 可以有多个数据成员 xff0c 但是在任意时刻只有一个数据成员可以有值 当某个成员被赋值后其他成员变为未定义状态 这是因为和
  • 字节对齐(结构体)

    直接从问题入手 xff0c 下面这个结构体 xff0c 占多大内存 xff0c 也就是求sizeof xff08 A xff09 是多少 struct A int a char b double c 这就涉及到一个字节对齐的问题 xff0c
  • mysql 8.0.12安装注意事项

    安装 使用msi方式安装 xff0c win10系统 问题 安装之后想使用命令行的方式 xff0c 不是mysql自带的mysql shell所产生的问题 xff1a mysql 不是内部或外部命令 xff0c 也不是可运行的程序或批处理文
  • 树莓派创建WiFi热点

    0x00 将代码clone到本地 git span class hljs keyword clone span https span class hljs comment github com oblique create ap span
  • 虚函数表和虚函数指针

    虚函数表 vtable 虚函数指针 vptr 虚函数表会出现在一个带有虚函数的类中 xff0c 是属于类的 虚函数表相当于一个数组 xff0c 其中存放的只有虚函数 xff0c 可以是继承而来的 xff0c 也可以是自己本身的 虚函数指针是
  • 在构造函数中使用虚函数可以实现多态吗?

    在构造函数和析构函数中使用虚函数可以实现多态吗 xff1f 不能 因为不管是构造函数还是析构函数 xff0c 其父类和子类的执行都是有一定顺序的 xff0c 拿构造函数来说 xff0c 父类的构造函数会先生成 xff08 一个很简单的道理
  • C++类型转换操作符

    在C语言中其实已有类型转换 xff0c 比如强制类型转换 xff0c 形式上是 type expression 对标C中的强制类型转换 xff0c C 43 43 中按照类型转换的意图对他们进行了分类 xff0c 形式上是 static c
  • 直接插入法排序

    本文试例在VS编译器下可正常使用 算法思想 xff1a 每次从待排序的原始序列中取出一个元素 xff0c 将其排入一个新的有序数列中 xff0c 这实际上是一个有序子序列不断增长的过程 xff0c 当有序子序列与原序列的长度一致时 xff0
  • 编译器的差别gcc和VS

    问题的由来是我写了一个排序算法程序 xff0c 在gcc编译器下运行 xff0c 发现结果有问题 xff0c 然后开展的寻找错误解决问题 这是我写的一个简单的插入排序算法 include lt stdio h gt 直接插入法排序函数主体
  • 计算机程序内存分布

    计算机存储器 计算机中有两种存储器 xff0c RAM和ROM RAM xff1a 随机存取存储器 xff08 random access memory xff09 xff0c 也叫主存 内存 它可以随时读写 xff0c 而且速度很快 xf
  • linux下的线程thread

    何为线程 xff1f 线程是一种轻量级的进程 xff0c 一个进程至少包含 1 个线程 xff0c 也可以包含多个线程 xff0c 所有线程共享进程的资源 xff0c 各个线程也可以拥有属于自己的私有资源 其实 xff0c 进程仅负责为各个
  • 线程同步的实现

    互斥锁 互斥锁的使用就是当有线程访问进程空间中的公共资源时 xff0c 线程执行 加锁 操作 将资源锁起来 xff0c 阻止其它线程的访问 访问完成后 xff0c 该线程 xff08 谁锁上的必须由谁来解锁 xff09 负责完成 解锁 操作
  • TCP粘包问题

    TCP是一种可靠 顺序 耗资的传输协议 xff0c UDP则反之 关于TCP的粘包问题 首先 xff0c 关于传输层协议中的TCP和UDP两种协议 xff0c 其中只有TCP会出现这个沾包的问题 xff0c 因为 xff0c TCP是流式套
  • 如何在shell脚本中使用环境变量的值

    1 问题的由来 此处展示的是脚本的一部分 if Update mode eq 0 then TF update else UDS update fi 说明 xff1a Update mode是一个环境变量 xff0c TF update和U
  • clang-format格式文件。可以直接复制引用

    Language Cpp BasedOnStyle LLVM AccessModifierOffset 2 AlignAfterOpenBracket Align AlignConsecutiveMacros false AlignCons
  • 环形缓冲区(c语言)

    1 概念介绍 在我们需要处理大量数据的时候 xff0c 不能存储所有的数据 xff0c 只能先处理先来的 xff0c 然后将这个数据释放 xff0c 再去处理下一个数据 如果在一个线性的缓冲区中 xff0c 那些已经被处理的数据的内存就会被
  • JAVA基础06——运算符02

    1 位运算 处理数据类型的时候 xff0c 可以直接对组成整形数值的各个位完成操作 amp 34 and 34 34 or 34 xff08 34 not 34 xff09 34 xor 以下用例皆为byte类型 xff1a xff1a 按
  • TCP/IP协议学习笔记(五)Windows下多线程多客户端的TCP服务端的实现

    使用多线程来实现可与多个客户端通信的服务端 当客户端连接上服务端之后 xff0c 为该客户端创建一个新的线程 xff0c 在该线程中与客户端进行通信 服务端程序中的主线程负责监听并接受客户端的连接请求 xff0c 创建与客户端通信的线程 另