14.10 Socket 套接字选择通信

对于网络通信中的服务端来说,显然不可能是一对一的,我们所希望的是服务端启用一份则可以选择性的与特定一个客户端通信,而当不需要与客户端通信时,则只需要将该套接字挂到链表中存储并等待后续操作,套接字服务端通过多线程实现存储套接字和选择通信,可以提高服务端的并发性能,使其能够同时处理多个客户端的请求。在实际应用场景中,这种技术被广泛应用于网络编程、互联网应用等领域。

该功能的具体实现思路可以总结为如下流程;

在服务端启动时,创建套接字并进行绑定,然后开启一个线程(称为主线程)用于监听客户端的连接请求。主线程在接收到新的连接请求后,会将对应的套接字加入一个数据结构(例如链表、队列、哈希表等)中进行存储。同时,主线程会将存储套接字的数据结构传递给每个子线程,并开启多个子线程进行服务,每个子线程从存储套接字的数据结构中取出套接字,然后通过套接字与客户端进行通信。

在选择通信方面,用户可以指定要与哪个客户端进行通信。服务端会在存储套接字的数据结构中寻找符合条件的套接字,然后将通信数据发送给对应的客户端。

首先为了能实现套接字的存储功能,此处我们需要定义一个ClientInfo该结构被定义的作用只有一个那就是存储套接字的FD句柄,以及该套接字的IP地址与端口信息,这个结构体应该定义为如下样子;

typedef struct
{SOCKET client;sockaddr_in saddr;char address[128];unsigned short port;
}ClientInfo;

接着我们来看主函数中的实现,首先主函数中listen正常侦听套接字连接情况,当有新的套接字接入后则直接通过CreateThread函数开辟一个子线程,该子线程通过EstablishConnect函数挂在后台,在挂入后台之前通过std::vector<ClientInfo *> info全局变量用来保存套接字。

当读者需要发送数据时,只需要调用SendMessageConnect函数,函数接收一个套接字链表,并接收需要操作的IP地址信息,以及需要发送的数据包,当有了这些信息后,函数内部会首先依次根据IP地址判断是否是我们所需要通信的IP,如果是则从全局链表内取出套接字并发送数据包给特定的客户端。

弹出一个套接字调用PopConnect该函数接收一个全局链表,以及一个字符串IP地址,其内部通过枚举链表的方式寻找IP地址,如果找到了则直接使用ptr.erase(it)方法将找到的套接字弹出链表,并以此实现关闭通信的目的。

输出套接字元素时,通过调用ShowList函数实现,该函数内部首先通过循环枚举所有的套接字并依次Ping测试,如果发现存在掉线的套接字则直接剔除链表,如果没有掉线则客户端会反馈一个pong以表示自己还在,此时即可直接输出该套接字信息。

14.10.1 服务端实现

服务端的实现方式在上述概述中已经简单介绍过了,服务端实现的原理概括起来就是,通过多线程技术等待客户端上线,当有客户端上线后就直接将其加入到全局链表内等待操作,主函数执行死循环,等待用户输入数据,用于选择与某个套接字通信。

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <string>
#include <vector>#pragma comment(lib,"ws2_32.lib")using namespace std;typedef struct
{SOCKET client;sockaddr_in saddr;char address[128];unsigned short port;
}ClientInfo;std::vector<ClientInfo *> info;       // 全局主机列表
SOCKET server;                        // 本地套接字
sockaddr_in sai_server;               // 存放服务器IP、端口// 弹出下线的主机
void PopConnect(std::vector<ClientInfo *> &ptr, char *address)
{// 循环迭代器,查找需要弹出的元素for (std::vector<ClientInfo *>::iterator it = ptr.begin(); it != ptr.end(); it++){ClientInfo *client = *it;// 如果找到了,则将其从链表中移除if (strcmp(client->address, address) == 0){ptr.erase(it);// std::cout << "地址: " << client->address << " 已下线" << std::endl;return;}}
}// 输出当前主机列表
void ShowList(std::vector<ClientInfo *> &ptr)
{for (int x = 0; x < ptr.size(); x++){// 发送Ping信号,探测bool ref = send(ptr[x]->client, "Ping", 4, 0);if (ref != true){PopConnect(info, ptr[x]->address);continue;}// 接收探测信号,看是否存活char ref_buf[32] = { 0 };recv(ptr[x]->client, ref_buf, 32, 0);if (strcmp(ref_buf, "Pong") != 0){PopConnect(info, ptr[x]->address);continue;}std::cout << "主机: " << ptr[x]->address << " 端口: " << ptr[x]->port << std::endl;}
}// 发送消息
void SendMessageConnect(std::vector<ClientInfo *> &ptr, char *address, char *send_data)
{for (int x = 0; x < ptr.size(); x++){// std::cout << ptr[x]->address << std::endl;// 判断是否为需要发送的IPif (strcmp(ptr[x]->address, address) == 0){// 对选中主机发送数据send(ptr[x]->client, send_data, strlen(send_data), 0);int error_send = GetLastError();if (error_send != 0){// std::cout << ptr[x]->address << " 已离线" << endl;// 弹出元素PopConnect(info, address);return;}// 获取执行结果char recv_message[4096] = { 0 };recv(ptr[x]->client, recv_message, 4096, 0);std::cout << recv_message << std::endl;}}
}// 建立套接字
void EstablishConnect()
{while (1){ClientInfo* cInfo = new ClientInfo();int len_client = sizeof(sockaddr);cInfo->client = accept(server, (sockaddr*)&cInfo->saddr, &len_client);// 填充主机地址和端口char array_ip[20] = { 0 };inet_ntop(AF_INET, &cInfo->saddr.sin_addr, array_ip, 16);strcpy(cInfo->address, array_ip);cInfo->port = ntohs(cInfo->saddr.sin_port);info.push_back(cInfo);}
}int main(int argc, char* argv[])
{// 初始化 WSA ,激活 socketWSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);// 初始化 socket、服务器信息server = socket(AF_INET, SOCK_STREAM, 0);sai_server.sin_addr.S_un.S_addr = 0;    // IP地址sai_server.sin_family = AF_INET;        // IPV4sai_server.sin_port = htons(8090);        // 传输协议端口// 本地地址关联套接字if (bind(server, (sockaddr*)&sai_server, sizeof(sai_server))){WSACleanup();}// 套接字进入监听状态listen(server, SOMAXCONN);// 建立子线程实现侦听连接CreateThread(0, 0, (LPTHREAD_START_ROUTINE)EstablishConnect, 0, 0, 0);while (1){char command[4096] = { 0 };input:memset(command, 0, 4096);std::cout << "[ LyShell ] # ";// 发送命令int inputLine = 0;while ((command[inputLine++] = getchar()) != '\n');if (strlen(command) == 1)goto input;// 输出主机列表if (strcmp(command, "list\n") == 0){ShowList(info);}// 发送消息else if (strcmp(command, "send\n") == 0){SendMessageConnect(info, "127.0.0.1", "Send");}// 发送CPU数据else if (strcmp(command, "GetCPU\n") == 0){SendMessageConnect(info, "127.0.0.1", "GetCPU");}// 发送退出消息else if (strcmp(command, "Exit\n") == 0){SendMessageConnect(info, "127.0.0.1", "Exit");}}return 0;
}

14.10.2 客户端实现

客户端的实现与之前文章中的实现方式是一样的,由于客户端无需使用多线程技术所以在如下代码中我们只需要通过一个死循环每隔5000毫秒调用connect对服务端进行连接,如果没有连接成功则继续等待,如果连接成功了则直接进入内部死循环,在循环体内根据不同的命令执行不同的返回信息,如下是客户端实现完整代码片段。

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <string>#pragma comment(lib,"ws2_32.lib")using namespace std;int main(int argc, char* argv[])
{while (1){WSADATA WSAData;SOCKET sock;struct sockaddr_in ClientAddr;if (WSAStartup(MAKEWORD(2, 0), &WSAData) != SOCKET_ERROR){ClientAddr.sin_family = AF_INET;ClientAddr.sin_port = htons(8090);ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");sock = socket(AF_INET, SOCK_STREAM, 0);int Ret = connect(sock, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr));if (Ret == 0){while (1){char buf[4096] = { 0 };memset(buf, 0, sizeof(buf));recv(sock, buf, 4096, 0);// 获取CPU数据if (strcmp(buf, "GetCPU") == 0){char* cpu_idea = "10%";int ServerRet = send(sock, cpu_idea, sizeof("10%"), 0);if (ServerRet != 0){std::cout << "发送CPU数据包" << std::endl;}}// 发送消息else if (strcmp(buf, "Send") == 0){char* message = "hello lyshark";int ServerRet = send(sock, message, sizeof("hello lyshark"), 0);if (ServerRet != 0){std::cout << "发送消息数据包" << std::endl;}}// 终止客户端else if (strcmp(buf, "Exit") == 0){closesocket(sock);WSACleanup();exit(0);}// 存活探测信号else if (strcmp(buf, "Ping") == 0){int ServerRet = send(sock, "Pong", 4, 0);if (ServerRet != 0){std::cout << "Ping 存活探测..." << std::endl;}}}}}closesocket(sock);WSACleanup();Sleep(5000);}return 0;
}

读者可自行编译并运行上述代码,当服务端启动后客户端上线,此时读者可根据输入不同的命令来操作不同的套接字,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/4acbc25e.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 3.0 已经就绪,您准备好了么?

Java 微服务开发框架王者 Spring 2014 年的 4 月&#xff0c;Spring Boot 1.0.0 正式发布。距离 1.0 版本的发布已经过去了 9 年多的时间&#xff0c;如今 Spring Boot 已经被 Java 开发者广泛使用&#xff0c;正如 JRebel 的 2022 年开发者生产力报告中提到的那样&#xff0c…

pdf压缩文件怎么压缩最小?

pdf压缩文件怎么压缩最小&#xff1f;我们很多项目介绍或是学术的报告都是采用的这个pdf格式&#xff0c;那么我们在存储或是需要进行分享的时候&#xff0c;可能就会因为文件过大而导致无法打开或是发送了。那么就需要将其进行压缩。PDF文件压缩方法很多&#xff0c;pdf压缩文…

基于安卓Android的掌上酒店预订APP

项目介绍 网络的广泛应用给生活带来了十分的便利。所以把掌上酒店预订与现在网络相结合&#xff0c;利用java技术建设掌上酒店预订APP&#xff0c;实现掌上酒店预订的信息化。则对于进一步提高掌上酒店预订发展&#xff0c;丰富掌上酒店预订经验能起到不少的促进作用。 掌上酒…

Netty 入门 — 要想掌握 Netty,你必须知道它的这些核心组件

在上篇文章&#xff08;Netty 入门 — 亘古不变的Hello World&#xff09;中&#xff0c;我们简单认识了开发一个 Netty 服务端和客户端代码的主要步骤了&#xff0c;在这几大步骤中我们基本上可以看出 Netty 的几个核心组件。 在真正进入 Netty 的学习之前&#xff0c;我们非…

docker-compose安装

如果提示docker-compose未找到命令&#xff0c;需要安装一下 curl -L https://github.com/docker/compose/releases/download/1.23.0-rc2/run.sh > /usr/local/bin/docker-compose 修改权限 chmod x /usr/local/bin/docker-compose 建立软连接 ln -s /usr/local/bin/do…

如何利用BIGEMAP软件查看历史影像

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 很多人都在寻找历史影像图&#xff0c;这块的需求是非常大&#xff0c;历史影像一般可以用于历史地貌的变迁分析&#xff0c;还原以前的生态场景&#xff0c;对范围面积…

微信小程序获取手机号和openid

小程序通过wx.login组件会返回一个code&#xff0c;这个code用来获得用户的openid。 小程序写法为&#xff1a; wx.login({success (res) {if (res.code) {//发起网络请求wx.request({url: https://example.com/onLogin,// 后台给的请求地址data: {code: res.code}})} else {…

2023年中国熔盐储能装机量、新增装机量及行业投资规模分析[图]

熔盐储能是一种可以传递能量、长时间&#xff08;6-8h&#xff09;、大容量储能的技术路径&#xff0c;作为传热介质可以实现太阳能到热能的转换&#xff0c;作为储能介质可以实现将热能和电能的双向转换&#xff0c;可以很好的适应和解决以上两大矛盾。因此&#xff0c;熔盐储…

Linux上Docker的安装以及作为非运维人员应当掌握哪些Docker命令

目录 前言 1、安装步骤 2、理解镜像和容器究竟是什么意思 2.1、为什么我们要知道什么是镜像&#xff0c;什么是容器&#xff1f; 2.2、什么是镜像&#xff1f; 2.3、什么是容器&#xff1f; 2.4、Docker在做什么&#xff1f; 2.5、什么是镜像仓库&#xff1f; 2、Dock…

Mobpush智能化精准推送,助力求职者快人一步

近日&#xff0c;“BOSS”直聘崩了的消息又又又上了热搜&#xff0c;2023年9月15日上午&#xff0c;BOSS直聘在线统计超过4700万人。由此可见&#xff0c;随着金九银十招聘旺季的到来&#xff0c;求职软件成为人们的青睐。但是对于大多数使用招聘软件的用户而言&#xff0c;往往…

2023_Spark_实验十六:编写LoggerLevel方法及getLocalSparkSession方法

一、搭建Spark项目结构 在SparkProject模块的pom.xml文件中增加一下依赖&#xff0c;并等待依赖包下载完毕&#xff0c;如上图。 ​<!-- Spark及Scala的版本号 --><properties><scala.version>2.11</scala.version><spark.version>2.1.1</sp…

GPT绘制流程图咒语

【咒语】下面是我的一篇论文选取部分&#xff0c;为了让读者更好理解&#xff0c;我准备画一张图&#xff0c;请你阅读后为我设计一下这个图应该怎么画&#xff0c;更有说服力&#xff0c;更容易理解 论文片段&#xff1a; 多模态数据融合研究的基础在于有效的数据采集。首先&a…