当前位置: 首页 > news >正文

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

写在前面

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

互斥量相关接口

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

因本文使用互斥量实现线程同步,因此仅介绍互斥量相关接口,其他实现线程同步的方式(如关键代码段、事件以及信号量等)可自行查阅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完成端口实现。

相关文章:

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

写在前面 此前的回声服务器/客户端都是在主线程中阻塞交互&#xff0c;本文将使用多线程方式实现服务器/客户端。 互斥量相关接口 使用多线程&#xff0c;自然避免不了线程同步问题。 因本文使用互斥量实现线程同步&#xff0c;因此仅介绍互斥量相关接口&#xff0c;其他实…...

如何使用vue的计算属性来处理数据计算?

计算属性是Vue.js中非常强大的功能&#xff0c;它可以帮助我们轻松地处理数据计算和管理数据。 先说个段子&#xff1a;有一天&#xff0c;一个新手问一个Vue大师&#xff0c;“大师&#xff0c;我的数据计算和管理怎么那么麻烦&#xff1f;”&#xff0c;大师回答&#xff1a…...

游戏研发项目管理

基于阶段模式进行游戏新产品研发过程&#xff0c;以及基于这种研发过程使用Leangoo 领歌敏捷工具管理 二、游戏产品开发流程 通常开发一款新游戏大体上会按照如下流程来进行&#xff1a; 1&#xff09; 概念阶段 – Concept 主策根据产品创意&#xff0c;确定游戏策划草案&a…...

P1249 乘积最大

最大乘积 题目描述 一个正整数一般可以分为几个互不相同的自然数的和&#xff0c;如 3 1 2 312 312&#xff0c; 4 1 3 413 413&#xff0c; 5 &#xff1d; 1 4 2 3 5&#xff1d;1423 5&#xff1d;1423&#xff0c; 6 1 5 &#xff1d; 2 4 615&#xff1d;24 …...

【7 Vue3 – Composition API】

1 认识Composition API Options API的弊端 setup函数 2 setup函数的参数 3 setup简单使用 1 注意不再有响应式数据 要做到响应式数据需要在数据定义时使用ref包装数据,并且在使用时,使用value解包 2 注意template要使用的数据或者函数,必须要return 返回才能被使用 <templa…...

设计模式-模板方法模式

模板方法模式 问题背景解决方案&#xff1a;模板方法模式基本介绍解决问题代码示例运行结果 钩子方法注意事项和细节 问题背景 豆浆的制作&#xff1a; 1&#xff09;制作豆浆的流程&#xff1a;选材—>添加配料—>浸泡—>放到豆浆机打碎 2&#xff09;通过添加不同…...

9. python的if语句

文章目录 一、if结构1.1 比较符号1.1.1 使用比较两个数据是否相等&#xff1a;1.1.2 使用!号比较数据是否不相等1.1.3 使用<号比较数字大小关系1.1.4 使用<号比较数字大小关系1.1.5 使用>号比较数字大小关系1.1.6 使用>号比较数字大小关系 1.2 关键字1.2.1 and关键…...

并发编程的基础知识

并发编程的优缺点 充分利用多核CPU的计算能力&#xff1a;通过并发编程的形式可以将多核CPU的计算能力发挥到极致&#xff0c;性能得到提升方便进行业务拆分&#xff0c;提升系统并发能力和性能&#xff1a;在特殊的业务场景下&#xff0c;先天的就适合于并发编程。现在的系统动…...

C 语言风格的字符串,非 string 类如何初始化字符串,以及操作字符串的函数(C++复习向p12)

C 风格的字符串 以 C 风格初始化字符串&#xff0c;有这 2 种方法。其中的 ‘\0’ 是字符串结束符号&#xff0c;是 null 字符 char site[7] {R, U, N, O, O, B, \0}; char site[] "RUNOOB";C 中操作字符串的函数 (以null结尾的字符串) strcpy(s1, s2) 把 s2 复…...

Linux文件系统、磁盘I/O是怎么工作的?

同CPU、内存一样&#xff0c;文件系统和磁盘I/O&#xff0c;也是Linux操作系统最核心的功能。磁盘为系统提供了最基本的持久化存储。文件系统则在磁盘基础上&#xff0c;提供了一个用来管理文件的树状结构。 目录&#xff1a; 一. 文件系统 1. 索引节点和目录项 2. 虚拟文件系…...

设计原则之接口隔离原则

tip: 需要《设计模式之禅》的书籍&#xff0c;可以联系我 作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 相关规则&#xff1a; 1.6大设计规则-迪米特法则 …...

ubuntu20.04 ffmpeg mp4转AES加密的m3u8分片视频

样本视频(时长2分35秒): 大雄兔_百度百科 大雄兔_百度百科不知大家否看过世界上第一部开源电影&#xff1a;Elephants Dream&#xff08;大象之梦&#xff09;。这是一部由主要由开源软件Blender制作的电影短片&#xff0c;证明了用开源软件也能制作出效果媲美大公司的作品。…...

Java08——继承

1. 继承 父类&#xff1a; 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("姓名&#xff1a;" name " 年龄&#xff1…...

C++高级语法

文章目录 C高级语法面向对象 -- 类/结构体抽象-具体类型 标准I/O流I/O流I/O缓存区 文件操作头文件的重复包含问题深拷贝和浅拷贝&#xff0c;写时复制面向对象的三大特性面向对象是什么 C高级语法 面向对象 – 类/结构体 C使用class定义一个类&#xff0c;使用struct定义一个…...

React学习笔记九-高阶函数与函数柯里化

此文章是本人在学习React的时候&#xff0c;写下的学习笔记&#xff0c;在此纪录和分享。此为第九篇&#xff0c;主要介绍高阶函数与函数柯里化。 高阶函数&#xff0c;和函数的柯里化&#xff0c;是学习react的拓展&#xff0c;方便以后优化代码&#xff0c;更好的学习react。…...

2023年电工杯B题半成品论文使用讲解

注&#xff1a;蓝色字体为说明备注解释字体&#xff0c;不能出现在大家的论文里。黑色字体为论文部分&#xff0c;大家可以根据红色字体的注记进行摘抄。该文件为半成品论文&#xff0c;即引导大家每一步做什么&#xff0c;怎么做&#xff0c;展示按着本团队的解题思路进行建模…...

第1关:ODBC程序设计

第1关&#xff1a;ODBC程序设计 任务描述相关知识ODBC主要功能ODBC接口主要函数ODBC应用程序开发实例DM ODBC应用程序开发总体流程DM ODBC代码编写流程DM ODBC代码编写实例 编程要求测试说明代码参考&#xff1a; 任务描述 本关任务&#xff1a;使用 ODBC 查询表中数据。 相关…...

Kotlin笔记(零)简介

百度百科简介 2017年&#xff0c;google公司在官网上宣布Kotlin成为Android的开发语言&#xff0c;使编码效率大增。Kotlin 语言由 JetBrains 公司推出&#xff0c;这是一个面向JVM的新语言 参考资料 官网&#xff1a;https://kotlinlang.org/中文官网&#xff1a;https://w…...

android 12.0去掉usb授权提示框 默认给予权限

1.概述 在12.0的系统rom产品开发中,在进行iot开发过程中,在插入usb设备时会弹出usb授权提示框,也带来一些不便,这个需要默认授予USB权限,插拔usb都不弹出usb弹窗所以这要从usb授权相关管理页默认给与usb权限 2.去掉usb授权提示框 默认给予权限的相关代码 frameworks/bas…...

工作积极主动分享,善于业务沟通

工作积极主动分享&#xff0c;善于业务沟通 目录概述需求&#xff1a; 设计思路实现思路分析1.工作积极主动承担责任2.善于沟通3.一起常常lauch 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...