网络编程--多线程服务器客户端
写在前面
此前的回声服务器/客户端都是在主线程中阻塞交互,本文将使用多线程方式实现服务器/客户端。
互斥量相关接口
使用多线程,自然避免不了线程同步问题。
因本文使用互斥量实现线程同步,因此仅介绍互斥量相关接口,其他实现线程同步的方式(如关键代码段、事件以及信号量等)可自行查阅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 256unsigned 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);}//若客户端断开连接,则在套接字数组中清除对应socketWaitForSingleObject(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 20unsigned 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完成端口实现。
相关文章:
网络编程--多线程服务器客户端
写在前面 此前的回声服务器/客户端都是在主线程中阻塞交互,本文将使用多线程方式实现服务器/客户端。 互斥量相关接口 使用多线程,自然避免不了线程同步问题。 因本文使用互斥量实现线程同步,因此仅介绍互斥量相关接口,其他实…...
如何使用vue的计算属性来处理数据计算?
计算属性是Vue.js中非常强大的功能,它可以帮助我们轻松地处理数据计算和管理数据。 先说个段子:有一天,一个新手问一个Vue大师,“大师,我的数据计算和管理怎么那么麻烦?”,大师回答:…...
游戏研发项目管理
基于阶段模式进行游戏新产品研发过程,以及基于这种研发过程使用Leangoo 领歌敏捷工具管理 二、游戏产品开发流程 通常开发一款新游戏大体上会按照如下流程来进行: 1) 概念阶段 – Concept 主策根据产品创意,确定游戏策划草案&a…...
P1249 乘积最大
最大乘积 题目描述 一个正整数一般可以分为几个互不相同的自然数的和,如 3 1 2 312 312, 4 1 3 413 413, 5 = 1 4 2 3 5=1423 5=1423, 6 1 5 = 2 4 615=24 …...
【7 Vue3 – Composition API】
1 认识Composition API Options API的弊端 setup函数 2 setup函数的参数 3 setup简单使用 1 注意不再有响应式数据 要做到响应式数据需要在数据定义时使用ref包装数据,并且在使用时,使用value解包 2 注意template要使用的数据或者函数,必须要return 返回才能被使用 <templa…...
设计模式-模板方法模式
模板方法模式 问题背景解决方案:模板方法模式基本介绍解决问题代码示例运行结果 钩子方法注意事项和细节 问题背景 豆浆的制作: 1)制作豆浆的流程:选材—>添加配料—>浸泡—>放到豆浆机打碎 2)通过添加不同…...
9. python的if语句
文章目录 一、if结构1.1 比较符号1.1.1 使用比较两个数据是否相等:1.1.2 使用!号比较数据是否不相等1.1.3 使用<号比较数字大小关系1.1.4 使用<号比较数字大小关系1.1.5 使用>号比较数字大小关系1.1.6 使用>号比较数字大小关系 1.2 关键字1.2.1 and关键…...
并发编程的基础知识
并发编程的优缺点 充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升方便进行业务拆分,提升系统并发能力和性能:在特殊的业务场景下,先天的就适合于并发编程。现在的系统动…...
C 语言风格的字符串,非 string 类如何初始化字符串,以及操作字符串的函数(C++复习向p12)
C 风格的字符串 以 C 风格初始化字符串,有这 2 种方法。其中的 ‘\0’ 是字符串结束符号,是 null 字符 char site[7] {R, U, N, O, O, B, \0}; char site[] "RUNOOB";C 中操作字符串的函数 (以null结尾的字符串) strcpy(s1, s2) 把 s2 复…...
Linux文件系统、磁盘I/O是怎么工作的?
同CPU、内存一样,文件系统和磁盘I/O,也是Linux操作系统最核心的功能。磁盘为系统提供了最基本的持久化存储。文件系统则在磁盘基础上,提供了一个用来管理文件的树状结构。 目录: 一. 文件系统 1. 索引节点和目录项 2. 虚拟文件系…...
设计原则之接口隔离原则
tip: 需要《设计模式之禅》的书籍,可以联系我 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 相关规则: 1.6大设计规则-迪米特法则 …...
ubuntu20.04 ffmpeg mp4转AES加密的m3u8分片视频
样本视频(时长2分35秒): 大雄兔_百度百科 大雄兔_百度百科不知大家否看过世界上第一部开源电影:Elephants Dream(大象之梦)。这是一部由主要由开源软件Blender制作的电影短片,证明了用开源软件也能制作出效果媲美大公司的作品。…...
Java08——继承
1. 继承 父类: package com.zsq.extend.improve_; //是pupil和graduate的父类 public class Student {public String name;public int age;private double score;public void info(){System.out.println("姓名:" name " 年龄࿱…...
C++高级语法
文章目录 C高级语法面向对象 -- 类/结构体抽象-具体类型 标准I/O流I/O流I/O缓存区 文件操作头文件的重复包含问题深拷贝和浅拷贝,写时复制面向对象的三大特性面向对象是什么 C高级语法 面向对象 – 类/结构体 C使用class定义一个类,使用struct定义一个…...
React学习笔记九-高阶函数与函数柯里化
此文章是本人在学习React的时候,写下的学习笔记,在此纪录和分享。此为第九篇,主要介绍高阶函数与函数柯里化。 高阶函数,和函数的柯里化,是学习react的拓展,方便以后优化代码,更好的学习react。…...
2023年电工杯B题半成品论文使用讲解
注:蓝色字体为说明备注解释字体,不能出现在大家的论文里。黑色字体为论文部分,大家可以根据红色字体的注记进行摘抄。该文件为半成品论文,即引导大家每一步做什么,怎么做,展示按着本团队的解题思路进行建模…...
第1关:ODBC程序设计
第1关:ODBC程序设计 任务描述相关知识ODBC主要功能ODBC接口主要函数ODBC应用程序开发实例DM ODBC应用程序开发总体流程DM ODBC代码编写流程DM ODBC代码编写实例 编程要求测试说明代码参考: 任务描述 本关任务:使用 ODBC 查询表中数据。 相关…...
Kotlin笔记(零)简介
百度百科简介 2017年,google公司在官网上宣布Kotlin成为Android的开发语言,使编码效率大增。Kotlin 语言由 JetBrains 公司推出,这是一个面向JVM的新语言 参考资料 官网:https://kotlinlang.org/中文官网:https://w…...
android 12.0去掉usb授权提示框 默认给予权限
1.概述 在12.0的系统rom产品开发中,在进行iot开发过程中,在插入usb设备时会弹出usb授权提示框,也带来一些不便,这个需要默认授予USB权限,插拔usb都不弹出usb弹窗所以这要从usb授权相关管理页默认给与usb权限 2.去掉usb授权提示框 默认给予权限的相关代码 frameworks/bas…...
工作积极主动分享,善于业务沟通
工作积极主动分享,善于业务沟通 目录概述需求: 设计思路实现思路分析1.工作积极主动承担责任2.善于沟通3.一起常常lauch 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy,…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
