当前位置: 首页 > 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;…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践&#xff0c;很多人以为AI已经强大到不需要程序员了&#xff0c;其实不是&#xff0c;AI更加需要程序员&#xff0c;普通人…...