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.rhkb.cn/news/165324.html

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

相关文章

QML中常见模型使用

目录 引言基础知识简单模型重复模型常见视图ListViewRepeater ListModelQbjectModel容器模型数组QStringListQList<XX *> QAbstractItemModelQSortFilterProxyModel 总结 引言 Qt Quick的基础组件中大量使用到模型&#xff0c;如ListView、Repeater等。视图所支持模型类…

爬虫学习日记第八篇(爬取fofa某端口的协议排行及其机器数目,统计top200协议)

需求 找到最常用的200个协议 通过fofa搜索端口&#xff0c;得到协议排名前五名和对应机器的数目。 遍历端口&#xff0c;统计各个协议对应的机器数目&#xff08;不准&#xff0c;但能看出个大概&#xff09; 读写API API需要会员&#xff0c;一天只能访问1000次。 import…

SpringBoot常见异步编程,你会多少?

微信公众号访问地址&#xff1a;SpringBoot常见异步编程&#xff0c;你会多少&#xff1f; 近期热推文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis…

【C++面向对象】2.构造函数、析构函数

文章目录 【 1. 构造函数 】1.1 带参构造函数--传入数据1.2 无参构造函数--不传入数据1.3 实例1.4 拷贝构造函数 【 2. 析构函数 】 【 1. 构造函数 】 类的构造函数是类的一种特殊的成员函数&#xff0c;它会 在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同…

2023年中国自动排气阀产业链、市场规模及存在问题分析]图[

自动排气阀是一种用于排除管道、容器或设备中累积的空气或气体的装置。在液体流动系统中&#xff0c;气体或空气可能会积聚在管道或容器中&#xff0c;影响流体流动、导致气锁和能效降低。自动排气阀的作用是在系统中的气体达到一定压力时&#xff0c;自动地释放气体&#xff0…

非关系型数据库-Redis

一、缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬盘也有大小不一的缓存…

Java并发面试题:(五)volatile关键字

volatile 是什么 一旦一个共享变量&#xff08;类的成员变量、类的静态成员变量&#xff09;被volatile修饰之后&#xff0c;那么就具备了两层语义&#xff1a; 1&#xff09;保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c…

python控制负数以16进制整型格式输出

实际使用时候&#xff0c;发现 python输出负数进程是 十进制和16进制一样的&#xff0c;就是16进制多了一个负号&#xff0c;和预期结果不同&#xff1b;比如我想要 -1输出 0xFFFFFFFF&#xff0c;可以参考如下方式&#xff1b; def TestPrintf(): ret -3print("test1 r…

Linux | gdb的基本使用

目录 前言 一、调试文件的生成 二、调试指令 1、选择调试文件 2、查看代码 3、运行代码 4、断点 5、打印与常显示 6、其他 总结 前言 前面我们学习了如何使用gcc/g来进行对代码进行编译&#xff0c;本章我们将使用gdb来对代码进行调试&#xff0c;学习本章的前提是有…

【专题】测试人员为什么需要学会做业务总结?

背景 如何回答以下这个问题的知识支撑&#xff1a;系统的测试重点在哪&#xff0c;难点是什么&#xff0c;怎么攻克&#xff0c;为什么要这样设计&#xff1f;项目交接效率&#xff1f; 同样是做业务测试&#xff0c;为什么有的人是A有的人只能C 二、框架 2.1 测试场景 重点…

RK3568笔记四:基于TensorFlow花卉图像分类部署

若该文为原创文章&#xff0c;转载请注明原文出处。 基于正点原子的ATK-DLRK3568部署测试。 花卉图像分类任务&#xff0c;使用使用 tf.keras.Sequential 模型&#xff0c;简单构建模型&#xff0c;然后转换成 RKNN 模型部署到ATK-DLRK3568板子上。 在 PC 使用 Windows 系统…

centos 内核对应列表 内核升级 linux

近期服务器频繁出现问题&#xff0c;找运维同事排查&#xff0c;说是系统版本和内核版本和官方不一致&#xff0c;如下&#xff1a; Release 用的是7.8, kernal 用的是 5.9 我一查确实如此&#xff1a; 内核&#xff1a; Linux a1messrv1 5.9.8-1.el7.elrepo.x86_64 发行版 Cen…

中文编程工具开发语言开发的实际案例:触摸屏点餐软件应用场景实例

中文编程工具开发语言开发的实际案例&#xff1a;触摸屏点餐软件应用场景实例 软件特色&#xff1a; 1、功能实用&#xff0c;操作简单&#xff0c;不会电脑也会操作&#xff0c;软件免安装&#xff0c;已内置数据库。软件在关闭的时候&#xff0c;可以设置会员数据备份到U盘&…

小谈设计模式(29)—访问者模式

小谈设计模式&#xff08;29&#xff09;—访问者模式 专栏介绍专栏地址专栏介绍 访问者模式角色分析访问者被访问者 优缺点分析优点将数据结构与算法分离增加新的操作很容易增加新的数据结构很困难4 缺点增加新的数据结构比较困难增加新的操作会导致访问者类的数量增加34 总结…

RHCE---搭建博客网站

一.实验要求&#xff1a; Server-NFS-DNS主机配置NFS服务器&#xff0c;将博客网站资源文件共享给Server-web主机&#xff0c;Server-NFS-DNS主机配置DNS Server-web主机配置web服务&#xff0c;通过域名www.openlab.com可以访问到自建的博客网站 二.准备工作 创建两台虚拟机…

10G SFP+线缆选购指南

凭借低成本和易安装的优势&#xff0c;在10G速率短距离传输中SFP线缆比SFP光模块更受欢迎。本文将从类型、优势、应用和选购指导等方面为您介绍10G SFP线缆&#xff0c;旨在帮助您更快做出购买决策。 10G SFP线缆&#xff1a;定义和类型 SFP线缆是一种高速线缆&#xff0c;两…

【LeetCode】 387. 字符串中的第一个唯一字符

题目链接 文章目录 所有方法 复杂度 ( O ( n ) O(n) O(n)、 O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣)) Python3方法一&#xff1a;collections.Counter() 统计频次方法二&#xff1a;哈希映射 { key字符&#xff1a;value【首次出现的索引 or -1 出现多次】}方法三&#xff1a; c…

Leetcode 1 两数之和 (暴力循环 HashMap* ) 含set、数组、map作哈希表的特性分析*

Leetcode 1 两数之和 &#xff08;暴力循环 哈希表&#xff09; 解法1 &#xff1a; 暴力循环解法2 : 哈希表HashMap法:red_circle:为什么想到用哈希表呢&#xff1f;:red_circle:为什么想到用map呢&#xff1f;:red_circle:归纳使用数组、set、map做哈希法&#xff1a; 题目链…

【2023淘宝双十一活动什么时间开始?天猫双十一2023具体时间安排

2023双十一活动什么时间开始&#xff1f;让我们先来了解一下双十一的优惠活动以及玩法吧。请收藏这份2023年淘宝天猫双十一玩法优惠攻略&#xff0c;让你轻松购得心仪的商品&#xff01; 红包派送 活动期间&#xff0c;每天都可以领取超级红包&#xff01;请注意&#xff0c…