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

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

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

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

首先为了能实现套接字的存储功能,此处我们需要定义一个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; // 判断是否为需要发送的IP
if (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 ,激活 socket
WSADATA 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; // IPV4
sai_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 许可协议。转载请注明出处!

14.10 Socket 套接字选择通信的更多相关文章

  1. 网络编程基础之Socket套接字简单应用

    一.Socket套接字实现通信循环 所谓通信循环,简单理解就是客户端可以给服务端循环发送信息并获得反馈的过程. 1.基础版 通信循环的程序分为两部分,即两个python模块,分别为客户端.py和服务端 ...

  2. 8.7 day28 网络编程 socket套接字 半连接池 通信循环 粘包问题 struct模块

    前置知识:不同计算机程序之间的数据传输 应用程序中的数据都是从程序所在计算机内存中读取的. 内存中的数据是从硬盘读取或者网络传输过来的 不同计算机程序数据传输需要经过七层协议物理连接介质才能到达目标程 ...

  3. 网络编程 TCP协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法

    TCP协议:传输协议,基于端口工作 三次握手,四次挥手 TCP协议建立双向通道. 三次握手, 建连接: 1:客户端向服务端发送建立连接的请求 2:服务端返回收到请求的信息给客户端,并且发送往客户端建立 ...

  4. Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令.

    Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令. 一丶socket套接字 什么是socket套接字: ​ ​  ​ 专业理解: socket是应用层与TCP/IP ...

  5. Python socket套接字通信

    一.什么是socket? socket是一个模块, 又称套接字,用来封装 互联网协议(应用层以下的层). 二.为什么要有socket? socket可以实现互联网协议 应用层以下的层 的工作,提高开发 ...

  6. 网络编程初识和socket套接字

    网络的产生 不同机器上的程序要通信,才产生了网络:凡是涉及到倆个程序之间通讯的都需要用到网络 软件开发架构 软件开发架构的类型:应用类.web类 应用类:qq.微信.网盘.优酷这一类是属于需要安装的桌 ...

  7. socket套接字编程 HTTP协议

    socket套接字编程  套接字介绍  1. 套接字 : 实现网络编程进行数据传输的一种技术手段  2. Python实现套接字编程:import  socket  3. 套接字分类 >流式套接 ...

  8. 网络编程--Socket(套接字)

    网络编程 网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯.网络编程中 有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后 如何可靠高效的进行数据传输.在 ...

  9. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  10. python基础之try异常处理、socket套接字基础part1

    异常处理 错误 程序里的错误一般分为两种: 1.语法错误,这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正 2.逻辑错误,人为造成的错误,如数据类型错误.调用方法错误等,这些解 ...

随机推荐

  1. 一行命令使用 Docker 编译 Latex 文件,简单优雅

    使用 Docker 编译 LaTeX 文章 LaTeX 是一种常用的排版系统,它可以帮助用户创建漂亮.专业的文档.但是,安装和配置 LaTeX 比较麻烦,特别是对于初学者而言. Docker 是一个开 ...

  2. 投个 3D 冰壶,上班玩一玩

    ​本篇文章将介绍如何使用物理引擎和图扑 3D 可视化技术来呈现冰壶运动的模拟. Oimo.js 物理引擎 Oimo.js 是一个轻量级的物理引擎,它使用 JavaScript 语言编写,并且基于 Oi ...

  3. sql中当关联查询主表很大影响查询速度时怎么办?

    sql中当关联查询主表很大时,直接关联,查询速度会较慢,这时可以先利用子查询经筛选条件筛除一部数据,这样主连接表体量减少,这样能一定程度加快速度. (1)常规join -- 最慢7.558s sele ...

  4. Oracle分区表设置详解

    Oracle分区表详解 Oracle建议单表超过2G就需要进行分表,一万数据大概3MB,单表最多分区为1024*1024-1个分区,我感觉够我们使用了哈 废话不多说,上示例,Oracle分表具体sql ...

  5. 在 Arch 配置 i3-wm 终端模拟器 xterm

    在 Arch 配置 i3-wm 终端模拟器 xterm 关于怎么在 Arch 安装 i3-wm 可以查看上一篇文章 https://www.cnblogs.com/shadow-/p/17572589 ...

  6. 四 APPIUM GUI讲解(Windows版)(转)

    Windows版本的APPIUM GUI有以下图标或者按钮: ·Android Settings  - Android设置按钮,所有和安卓设置的参数都在这个里面 ·General Settings – ...

  7. [ansible]常用内置模块

    前言 ansible内置了很多模块,常用的并不多,可以通过ansible -l命令列出所有模块,使用 ansible-doc module-name 查看指定模块的帮助文档,例如:ansible-do ...

  8. Tibos.Devops项目介绍

    诞生背景 随着微服务的普及,更多的企业选择迁移到云,传统的部署方式已经无法满足需求,市面上devops产品也应运而生,结合自己使用的经验,也制作了一款同类产品,并开源出来,与大家一起探讨学习 前置条件 ...

  9. Java源代码是如何编译,加载到内存中的?

    1.前言 相信许多开发同学看过<深入理解java虚拟机>,也阅读过java虚拟机规范,书籍和文档给人的感觉不够直观,本文从一个简单的例子来看看jvm是如何工作的吧. 本文所有操作均在mac ...

  10. Python colorama 设置控制台、命令行输出彩色文字

    为了方便调试代码,经常会向stdout中输出一些日志,但是大量日志,有时不好定位问题. 使用终端打印特定颜色字符串,可以突出显示关键性的信息,帮助用户更好地识别和理解输出内容. https://pyp ...