14.10 Socket 套接字选择通信

这篇具有很好参考价值的文章主要介绍了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;

    // 判断是否为需要发送的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;
}

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

14.10 Socket 套接字选择通信文章来源地址https://www.toymoban.com/news/detail-710823.html

到了这里,关于14.10 Socket 套接字选择通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【网络】socket——预备知识 | 套接字 | UDP网络通信

    🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言: 你只管努力,剩下的交给时间! 在前面本喵对网络的整体轮廓做了一个大概的介绍,比如分层,协议等等内容,现在我们直接进入socket(套接字)编程,先来感受到网络编程。 我们知道,在网络通信中,存在两套地址,一

    2024年02月13日
    浏览(42)
  • Socket套接字编程(实现TCP和UDP的通信)

      🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔 🦾🦾🦾 目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿 谢谢

    2024年02月03日
    浏览(74)
  • Qt下Tcp套接字(socket)通信的整个流程

    QT += network Qt中提供的所有的Socket类都是非阻赛的 Qt中常用的用于socket通信的套接字类         QTcpServer 用于TCP/IP通信,作为服务器端套接字使用         QTcpSocket 用于TCP/IP通信,作为客户端套接字使用。         QUdpSocket 用于UDP通信,服务器,客户端均使用此套接字

    2024年02月10日
    浏览(25)
  • 网络编程day1——进程间通信-socket套接字

            基本特征:socket是一种接口技术,被抽象了一种文件操作,可以让同一计算机中的不同进程之间通信,也可以让不同计算机中的进程之间通信(网络通信)         进程A                                                        进程B     创建socket对象

    2024年02月10日
    浏览(48)
  • 用java套接字socket实现两台电脑间的通信

    实现效果: 一方发送简单的文字消息 发送、接收复杂的图片、音频、文档等 相互之间实现自由通信 java对网络编程的支持 前提条件:两台电脑在一个局域网内,比如连接了同一个路由器 将一台电脑作为服务端,一台作为客户端 实现中遇到的问题: ip地址连接不上 发送的消

    2023年04月17日
    浏览(33)
  • socket套接字通信 TCP传输控制协议/IP网络协议 5.18

    B/S :浏览器和服务器 C/S :客户机和服务器 网络的层次结构和每层所使用协议的集合 网络采用分层管理的方法,将网络的功能划分为不同的模块 OSI模型: 共7种: 数据的封装与传递过程: 网络传输数据大小user data: 6~1460 网络传输中容易发生拆包和粘包,所以接收和发送的字节

    2024年02月05日
    浏览(65)
  • python3套接字编程之socket和socketserver(TCP和UDP通信)

    socket和socketserver是python3中socket通信模块,关于其使用做如下总结。 目录 1.socket 1.1模块引入 1.2套接字获取 1.3套接字接口 1.3.1 服务端 1.3.2 客户端套接字函数 1.3.3 公共套接字函数 1.3.4 面向锁的套接字方法 1.3.5 面向文件的套接字的函数 2.socketserver 3.TCP 3.1 socket类型TCP 3.2 sockets

    2024年02月15日
    浏览(63)
  • 「网络编程」第二讲:网络编程socket套接字(三)_ 简单TCP网络通信程序的实现

    「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(二),下面开始讲解!  「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句」 I do not know where to go,but I have been on the road. 我不知

    2024年02月11日
    浏览(37)
  • 【Java】网络编程与Socket套接字、UDP编程和TCP编程实现客户端和服务端通信

    为什么需要网络编程? 现在网络普及程序越来越高,网络上保存着我们日常生活中需要的各种资源,使用程序通过网络来获取这些资源的过程就需要网络编程来实现。 什么是网络编程? 网络编程,指网络上的主机,通过不同的进程以程序的方式实现网络通信(网络数据传输)

    2024年02月17日
    浏览(51)
  • 【Python】Python 网络编程 ( Socket 套接字简介 | Socket 套接字使用步骤 | Socket 套接字服务端与客户端开发 )

    Socket 套接字 是一种 进程之间的 通信机制 , 通过套接字可以在 不同的进程之间 进行数据交换 ; 在 网络编程 中 , Socket 套接字 主要用于 客户端 与 服务器 之间的 通信 , 大部分 网络相关的应用程序 , 都使用到了 Socket 套接字技术 ; 套接字有两种类型 : 流套接字 : 提供了一个可

    2024年02月15日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包