网络编程--多线程服务器客户端

news2024/11/28 11:57:07

写在前面

此前的回声服务器/客户端都是在主线程中阻塞交互,本文将使用多线程方式实现服务器/客户端。

互斥量相关接口

使用多线程,自然避免不了线程同步问题。

因本文使用互斥量实现线程同步,因此仅介绍互斥量相关接口,其他实现线程同步的方式(如关键代码段、事件以及信号量等)可自行查阅MSDN帮助文档。

创建互斥量

使用CreateMutex创建互斥量,原型如下:

#include <windows.h>
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

成功时返回创建的互斥量对象句柄,失败返回NULL
lpMutexAttributes:传递安全相关的配置信息,使用默认安全设置时可以传递NULL
bInitialOwner:如果为TRUE,则创建出的互斥量对象属于调用该函数的线程,同时进入non-signaled状态;
如果为FALSE,则创建出的互斥量对象不属于任何线程,此时状态为signaled
lpName: 用于命名互斥量对象。传入NULL时创建无名的互斥量对象。

销毁互斥量

互斥量属于系统内核资源,使用完后需要手动释放。使用CloseHandle函数释放互斥量资源,原型如下:

BOOL CloseHandle(HANDLE hObject);

成功时返回TRUE,失败时返回FALSE
hObject 要销毁的内核对象的句柄

获取互斥量

通过WaitForSingleObject接口获取互斥量,原型如下:

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

hHandle:对象的句柄。 如果等待仍在等待时关闭此句柄,则函数的行为未定义。
dwMilliseconds:超时间隔(以毫秒为单位)。 如果指定了非零值,该函数将等待对象发出信号或间隔。 如果 dwMilliseconds 为零,则如果对象未发出信号,则函数不会输入等待状态;它始终会立即返回。 如果 dwMilliseconds 为 INFINITE,则仅当发出对象信号时,该函数才会返回。

释放互斥量

使用ReleaseMutex释放互斥量,使其转变为signaled状态。

BOOL ReleaseMutex(HANDLE hMutex);

成功时返回TRUE,失败时返回FALSE
hMutex: 需要释放(解除拥有)的互斥量对象句柄

多线程服务器

多线程服务器使用一个全局的socket数组维护连接的客户端socket,在主线程中等待客户端的连接,每有一个客户端连接时就单独开启一个线程提供回声服务,使用一个全局的互斥量对象实现各提供回声服务线程的线程同步。

代码如下:

// MultiThread_Server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <process.h>
#include <WinSock2.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")

using namespace std;

#define BUF_SIZE 100
#define MAX_CLNT 256

unsigned WINAPI HandleClnt(void* arg);
void SendMsg(char* arg, int len);

HANDLE hMutex;
int clntCnt = 0;
SOCKET clntSocks[MAX_CLNT];

void WriteRunLog(LPCSTR lpszLog, int len);

int _tmain(int argc, _TCHAR* argv[])
{
	if (argc != 2)
	{
		printf("argc error!\n");
		return -1;
	}

	WSADATA wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		printf("WSAStartup error!\n");
		return -1;
	}

	SOCKET srvSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == srvSock)
	{
		printf("socket error!\n");
		WSACleanup();
		return -1;
	}

	SOCKADDR_IN srvAddr;
	memset(&srvAddr, 0, sizeof(srvAddr));
	srvAddr.sin_family = PF_INET;
	srvAddr.sin_addr.s_addr = htonl(ADDR_ANY);
	srvAddr.sin_port = htons(_ttoi(argv[1]));

	if (SOCKET_ERROR == bind(srvSock, (sockaddr*)&srvAddr, sizeof(srvAddr)))
	{
		printf("bind error!\n");
		closesocket(srvSock);
		WSACleanup();
		return -1;
	}

	if (SOCKET_ERROR == listen(srvSock, 5))
	{
		printf("listen error!\n");
		closesocket(srvSock);
		WSACleanup();
		return -1;
	}

	SOCKADDR_IN cltAddr;
	memset(&cltAddr, 0, sizeof(cltAddr));
	int nCltAddrSize = sizeof(cltAddr);

	hMutex = CreateMutex(NULL, FALSE, NULL);

	
	while (true)
	{
		//接受连接线程
		nCltAddrSize = sizeof(cltAddr);
		puts("wait for client connect...");
		SOCKET cltSock = accept(srvSock, (sockaddr*)&cltAddr, &nCltAddrSize);
		if (cltSock == INVALID_SOCKET)
		{
			printf("accept error\n");
			continue;
		}

		//等待操作互斥量数组
		WaitForSingleObject(hMutex, INFINITE);
		clntSocks[clntCnt++] = cltSock;
		ReleaseMutex(hMutex);

		//最后开启该套接字的消息处理线程
		HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void*)&cltSock, 0, NULL);
		printf("Connected Client IP: %s \n", inet_ntoa(cltAddr.sin_addr));

	}

	CloseHandle(hMutex);
	closesocket(srvSock);
	WSACleanup();

	puts("main thread end.");
	puts("任意键继续...");
	getchar();

	return 0;
}

unsigned WINAPI HandleClnt(void* arg)
{
	SOCKET cltSock = *((SOCKET*)arg);

	int nRecvLen = 0;
	char Msg[BUF_SIZE] = {};
	char Log[2*BUF_SIZE] = {};
	while ( (nRecvLen = recv(cltSock, Msg, BUF_SIZE, 0)) != 0 )
	{
		Msg[nRecvLen] = 0;

		//sprintf(Log, "recv msg from client《%d》: %s\n", cltSock, Msg);
		//WriteRunLog(Log, strlen(Log));
		SendMsg(Msg, nRecvLen);
	}

	//若客户端断开连接,则在套接字数组中清除对应socket
	WaitForSingleObject(hMutex, INFINITE);
	//找到要清除的套接字,从该位置开始,后续元素前移覆盖删除
	//双指针实现覆盖删除
	int slow = 0;
	int fast = 0;

	for (; fast < clntCnt; fast++)
	{
		if (clntSocks[fast] == cltSock)
		{
			continue;
		}

		clntSocks[slow++] = clntSocks[fast];
	}
	clntSocks[slow] = INVALID_SOCKET;
	
	clntCnt--;
	ReleaseMutex(hMutex);
	closesocket(cltSock);

	//sprintf(Log, "Client %d Disconnected...\n", cltSock);
	//WriteRunLog(Log, strlen(Log));

	return 0;

}

void SendMsg(char* arg, int len)
{
	//回复所有客户端
	WaitForSingleObject(hMutex, INFINITE);
	char Log[2*BUF_SIZE] = {};
	for (int i = 0; i < clntCnt; i++)
	{
		//sprintf(Log, "Send to client 《%d》 msg: %s\n", clntSocks[i], len);
		//WriteRunLog(Log, strlen(Log));
		send(clntSocks[i], arg, len, 0);
	}

	ReleaseMutex(hMutex);
}

多线程客户端

此前的回声客户端均在主线程中进行读写操作,在多线程客户端中使用两个线程分别处理读写操作。

代码如下:

// MultiThread_Client.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <process.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 100
#define NAME_SIZE 20

unsigned WINAPI SendMsg(void* arg);
unsigned WINAPI RecvMsg(void* arg);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE] = {};


int _tmain(int argc, _TCHAR* argv[])
{
	if (argc != 4)
	{
		printf("argc error!\n");
		return -1;
	}

	WSADATA wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		printf("WSAStartup error!\n");
		return -1;
	}

	printf("server ip: %s, port: %s, client name: %s\n", argv[1], argv[2], argv[3]);
	sprintf(name, "[%s]", argv[3]);

	SOCKET cltSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == cltSock)
	{
		puts("socket error!");
		WSACleanup();
		return -1;
	}

	SOCKADDR_IN srvAddr;
	memset(&srvAddr, 0, sizeof(srvAddr));
	srvAddr.sin_family = PF_INET;
	srvAddr.sin_addr.s_addr = inet_addr(argv[1]);
	srvAddr.sin_port = htons(_ttoi(argv[2]));

	if (connect(cltSock, (sockaddr*)&srvAddr, sizeof(srvAddr)) == SOCKET_ERROR)
	{
		puts("connect error!");
		closesocket(cltSock);
		WSACleanup();
		return -1;
	}

	//开启客户端的接收和发送线程
	HANDLE hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&cltSock, 0, NULL);
	HANDLE hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&cltSock, 0, NULL);

	WaitForSingleObject(hSendThread, INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	closesocket(cltSock);
	WSACleanup();

	puts("任意键继续...");
	getchar();

	return 0;
}

unsigned WINAPI SendMsg(void* arg)
{
	SOCKET cltSock = *((SOCKET*)arg);
	char nameMsg[NAME_SIZE + BUF_SIZE] = {};
	while (true)
	{
		//printf("Input Msg: ");
		fgets(msg, BUF_SIZE, stdin);

		if ( !strcmp(msg, "q\n") || !strcmp(msg, "Q\n") )
		{
			puts("Disconnect...");
			break;
		}

		sprintf(nameMsg, "%s %s", name, msg);
		send(cltSock, nameMsg, strlen(nameMsg), 0);
	}
	//exit(0);
	closesocket(cltSock);

	printf("client %d thread end.\n", cltSock);
	return 0;
}

unsigned WINAPI RecvMsg(void* arg)
{
	SOCKET cltSock = *((SOCKET*)arg);
	char nameMsg[NAME_SIZE + BUF_SIZE] = {};

	int nRecvLen = 0;
	while (true)
	{
		nRecvLen = recv(cltSock, nameMsg, NAME_SIZE + BUF_SIZE - 1, 0);
		if (nRecvLen == -1)
		{
			puts("server disconnected!");
			return -1;
		}
		nameMsg[nRecvLen] = 0;
		printf("nameMsg from server: %s\n", nameMsg);
	}

	return 0;
}

运行结果如下:
运行结果

总结

虽然使用互斥量实现了简单的多线程服务器/客户端,但也只是借此熟悉下线程及线程同步相关的接口,可以明显的看到效率还是比较低下的。

要想使用高效的Windows服务器客户端,可以使用IOCP完成端口实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/590401.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL高级篇笔记-索引的数据结构 (中) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、索引及其优缺点 1、索引概述 2、优点 3、缺点 二、InnoDB中索引的推演 1、设计索引 1.一个简单的索引设计方案 2.InnoDB中的索引方案 2、常见索引概念 1. 聚簇索引 2. 二级索引&#xff08;辅助索引、非聚簇索引&#…

Java阶段三Day06

Java阶段三Day06 文章目录 Java阶段三Day06同步请求和异步请求案例演示创建SpringBoot工程application.propertiesUserControllerUserMapper静态页面 JSONSpring Security引入SpringSecurity框架对项目的影响关于SpringSecurity的配置默认登录表单设置白名单模拟登录使用自己的…

用pycharm来下载super-grandients 出现的错误

主要用来记录我用pycharm来下载super-grandients的一些错误 文章目录 1、UserWarning: Distutils was imported before Setuptools.1、distutils 2、pycharm的虚拟环境安装的包在c盘3、虚拟内存不够 1、UserWarning: Distutils was imported before Setuptools. UserWarning: …

系统集成项目管理工程师(系统集成基础知识)

第一章信息化知识 一、信息的概述 1、信息(information)是客观事物状态和运动特征的一种普遍形式&#xff1b;事物的本体论信息&#xff0c;就是事物的运动状态和状态变化方式的自我表述。 二、信息的传递 2、信息技术主要为解决信息的采集、加工、存储、传输、处理、计算、…

设计模式之~适配器模式

描述&#xff1a; adapter将一个类的接口转换成客户希望的另外一个接口。adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。 在软件开发中&#xff0c;当系统的数据和行为都正确&#xff0c;但接口不符时&#xff0c;我们应该考虑使用适配器&#xff0c;目的…

Node.js详解(二):Node.js与JS的关系

一、简介 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;是一个让 JavaScript 运行在服务端的开发平台&#xff0c;它让 JavaScript 成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。 JavaScript一种直译式脚本语言&#xff0c;是一种…

NMS非极大值抑制

文章目录 一、NMS详解二、NMS具体步骤与实现1.步骤2、代码(pytorch版本) 一、NMS详解 NMS即非极大值抑制&#xff0c;常被用于目标检测等&#xff0c;即只保留检测同一物体置信度最大的框。 具体作用可以看图&#xff1a; 可以看出&#xff0c;未经过nms的图片&#xff0c;有…

基于Freertos的ESP-IDF开发——8.使用wifi访问HTTP服务器

目录 0. 前言其他ESP-IDF文章 1. 前期准备1.1头文件准备1.2 http 服务器搭建 2. 连接 wifi3.http访问任务4. 完整代码 0. 前言 使用ESP32使用 wifi 访问 http 服务器 开发环境&#xff1a;ESP-IDF 4.2 操作系统&#xff1a;Ubuntu22.04 开发板&#xff1a;自制的ESP32-WROOM-…

流行框架(二)网络请求库 OKhttp

文章目录 概述HttpURLConnectionGET和POST获取文本数据GETPOST OKHttp基本使用依赖与权限发起一个get请求重要概念OkHttpClientRequestCallRealCallAsyncCall 请求调度器Dispatcher同步请求execute的执行异步请求enqueue的执行两种请求方式的总结 OkHttp拦截器链拦截器种类addI…

字节狂问1小时,小伙offer到手,太狠了!(字节面试真题)

前言&#xff1a; 在尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常有小伙伴&#xff0c;需要面试 头条、美团、阿里、京东等大厂。 下面是一个小伙伴成功拿到字节飞书offer&#xff0c;通过一小时拷问的面试经历&#xff0c;就两个字&#xff1a; 深&#xf…

基于STM32的SYN6288语音播报模块驱动实验(代码开源)

前言&#xff1a;本文为手把手教学 SYN6288 语音播报模块的驱动实验&#xff0c;本教程的 MCU 采用STM32F103ZET6。通过 CubeMX 软件配置 UART 串口协议驱 SYN6288 模块进行规定的语音播报。考虑到 SYN6288 模块的集成化与智能化很高&#xff0c;所以该模块的使用是极其便利的。…

【HarmonyOS】初识低代码平台开发元服务

【关键字】 HarmonyOS、低代码平台、元服务开发、拖拽式开发 【写在前面】 今天要分享的是HarmonyOS中的低代码开发相关的内容&#xff0c;低代码开发是DevEco Studio提供的一种UI界面可视化的构建方式&#xff0c;通过图形化的自由拖拽数据的参数化配置&#xff0c;可以快速…

【Java项目】基于SpringBoot+Vue的校园二手商品交易平台

文章目录 功能简述功能展示用户模块购物车模块管理员模块物物对价功能实现 代码 视频演示 代码下载 项目内含有 功能简述 系统登录界面的实现 系统首页界面的实现 用户信息管理界面的实现 商品购物功能的实现 购物车管理功能及支付功能的实现 物物对价功能的实现 用户安全设置…

【面试需了解】jvm垃圾回收机制-GC基础知识、jvm基本组成、查看、排查

前言 jvm垃圾回收机制-GC基础知识、jvm基本组成、查看、排查 文章目录 前言GC基础知识概述 JVM基本组成1. 虚拟机的组成2. jvm的内存区域 查看jvm排查jvm问题1. 正常运行的系统2. 对于已经发生了OOM的系统 GC基础知识 概述 什么是垃圾 一个对象没有被引用&#xff0c;没有任何…

Spring MVC详解(学习总结)

一、Sprig MVC简介1.1介绍1.2MVC是什么 二、Spring MVC实现原理2.1核心组件2.2工作流程 三、第一个Spring MVC四、常用注解五、参数绑定5.1URL风格参数绑定5.2RESTful风格的URL参数获取5.3映射Cookie5.4使用POJO绑定参数5.5JSP页面的转发和重定向 六、Spring MVC数据绑定6.1基本…

vulnstack(红日)内网渗透靶场二: 免杀360拿下域控

前言 在我之前的文章vulnstack(一)打靶&#xff0c;我主要依赖Cobalt Strike进行后期渗透测试&#xff0c;这次我计划使用Metasploit框架(MSF)来进行这个阶段的工作。这个靶场与之前的不同之处在于它的WEB服务器安装了360安全卫士。虽然这增加了挑战的难度&#xff0c;但只要我…

Shell脚本攻略:循环语句while、until

目录 一、理论 1.while 2.until 3.break 4.continue 二、实验 1.实验一 2.实验二 3.实验三 4.实验四 5.实验五 一、理论 1.while (1)while用法 while循环满足条件执行&#xff0c;不满足不执行。 用于不知道循环次数&#xff0c;需要主动结束循环或达到条件结束…

二开项目权限应用全流程-按钮级控制

二开项目权限应用全流程-按钮级控制 员工A和员工B都可以访问同一个页面&#xff08;以员工管理为例&#xff09;&#xff0c;但是员工A可以导出excel&#xff0c;员工B就不可以导出excel(看不到按钮) 思路 用户登陆成功后&#xff0c;用户可以访问的按钮级别权限保存在point…

阿里巴巴淘天集团后端暑期实习面经

目录 1.面向对象三大特性2.重写和重载3.protected 关键字和 default 关键字的作用范围4.栈帧中有哪些东西&#xff1f;5.堆中有哪些区域&#xff1f;6.new 一个对象存放在哪里&#xff1f;7.CMS 收集器回收阶段8.CMS 收集器回收过程哪些需要暂停线程&#xff1f;9.HashMap JDK …

手机行业再多一条“鲶鱼”,小度青禾要打一场漂亮突围战?

文 | 智能相对论 作者 | 佘凯文 智能手机到底还是不是一门好生意&#xff1f; 在换机周期被无限拉长、市场竞争越发激烈、高端市场迟迟无法突破等共同背景下&#xff0c;智能手机到底还是不是一门好生意&#xff0c;成为行业内这两年被热议的话题之一。 由TechInsights发布…