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

news/2025/2/15 23:13:35/

写在前面

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

互斥量相关接口

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

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


http://www.ppmy.cn/news/108850.html

相关文章

5月底了,现在不想着跳槽可就晚了

前两天跟朋友感慨&#xff0c;今年的铜三铁四、裁员、疫情导致好多人都没拿到offer!现在已经5月底了&#xff0c;具体金九银十只剩下三个月。 对于想跳槽的职场人来说&#xff0c;绝对要从现在开始做准备了。这时候&#xff0c;很多高薪技术岗、管理岗的缺口和市场需求也出来了…

为什么有些情况下需要重写equals()和hashCode()方法?

目录 方法作用实战案例 方法作用 equals()&#xff1a;判断对象是否相等&#xff0c;比如判断是否能放入Set集合中 情况1&#xff1a;没有重写equals()方法&#xff1a;由于所有类的默认基类都是Object类&#xff0c;所以默认使用Object类的equals()方法&#xff0c;那就是对象…

HNU-电子测试平台与工具2-Veriog答案

这个题目很阴间,做了两次,还是只有90分,我也不知道错在哪里,怎么改。 我把两次的答案发一下,各位自己仁者见仁,智者见智。 有时间的话我再做吧。 第一次: 第二次:

Map、Set和哈希表(数据结构系列14)

目录 前言&#xff1a; 1.搜索树 1.1概念 1.2插入 1.3查找 1.4删除 1.5二叉搜索树整体代码展示 2. Map和Set的讲解 2.1 Map的说明 2.1.1Map的方法 2.2 Set 的说明 2.2.1Set的方法 3.哈希表 3.1哈希表的概念 3.2哈希冲突 3.3冲突的避免 3.4哈希冲突的解决 3.4…

有哪些代码编辑器可以推荐? - 易智编译EaseEditing

以下是一些常用的代码编辑器&#xff0c;并对它们进行简单介绍&#xff1a; Visual Studio Code&#xff1a; Visual Studio Code&#xff08;简称VS Code&#xff09;是由微软开发的免费、跨平台的代码编辑器。 它具有丰富的功能和插件生态系统&#xff0c;支持多种编程语言…

内网渗透(八十六)之Exchange ProxyLogon攻击链利用

Exchange ProxyLogon攻击链利用 漏洞背景 2021年3月2日,微软发布了Exchange服务器的紧急安全更新,修复了如下7个相关的漏洞。 Exchange服务端请求伪造漏洞(CVE-2021-26855):未经身份验证的攻击者能够构造HTTP请求扫描内网并通过Exchange服务器进行身份验证。Exchange反序列…

数列DP进阶

目录 一&#xff0c;斜率优化 1&#xff0c;斜率优化原理 2&#xff0c;凸包和斜率计算 3&#xff0c;实战 黑暗爆炸 - 4709 柠檬 二&#xff0c;else 力扣 644. 子数组最大平均数 II&#xff08;最大子段和二分&#xff09; ​力扣 646. 最长数对链 力扣 1235. 规划兼…

寻求帮助:尝试在深度学习领域找创新点

尝试在深度学习领域找创新点 写在最前面思路第一步&#xff1a;找到现有问题&#xff08;改进方向&#xff09;第二步&#xff1a;确认解决方案第三步&#xff1a;学习方法步骤第四步&#xff1a;改进创新归纳第五步&#xff1a;可视化说明 其他A算法部分嵌入B交叉applicationc…