14.4 Socket 双向数据通信

举报
王瑞专家 发表于 2023/10/14 08:56:51 2023/10/14
【摘要】 所谓双向数据传输指的是客户端与服务端之间可以无差异的实现数据交互,此类功能实现的核心原理是通过创建CreateThread()函数多线程分别接收和发送数据包,这样一旦套接字被建立则两者都可以异步发送消息,本章将实现简单的双向交互功能。首先我们需要封装两个函数,这里RecvFunction函数用于接收数据,SendFunction函数则用于发送数据,这两段代码在服务端与客户端之间是一致的两者可...

所谓双向数据传输指的是客户端与服务端之间可以无差异的实现数据交互,此类功能实现的核心原理是通过创建CreateThread()函数多线程分别接收和发送数据包,这样一旦套接字被建立则两者都可以异步发送消息,本章将实现简单的双向交互功能。

首先我们需要封装两个函数,这里RecvFunction函数用于接收数据,SendFunction函数则用于发送数据,这两段代码在服务端与客户端之间是一致的两者可被共用。

#include <iostream>
#include <Winsock2.h> 
#include <windows.h>
#pragma comment (lib, "ws2_32")
#define BUF_SIZE 6400

// 接收数据线程
DWORD WINAPI RecvFunction(LPVOID lpParam)
{
  SOCKET sClient = *(SOCKET*)lpParam;
  int retVal;
  char bufRecv[BUF_SIZE];
  memset(bufRecv, 0, sizeof(bufRecv));
  while (1)
  {
    retVal = recv(sClient, bufRecv, BUF_SIZE, 0);
    if (retVal == SOCKET_ERROR)
    {
      printf("返回错误 \n");
      break;
    }
    else
    {
      printf("收到服务器消息: %s \n", bufRecv);
    }
  }
  return 0;
}

// 发送数据线程
DWORD WINAPI SendFunction(LPVOID lpParam)
{
  SOCKET sClient = *(SOCKET*)lpParam;
  int retVal;
  char bufSend[BUF_SIZE];
  memset(bufSend, 0, sizeof(bufSend));
  while (1)
  {
    gets_s(bufSend);
    retVal = send(sClient, bufSend, strlen(bufSend) + sizeof(char), 0);
    if (retVal == SOCKET_ERROR)
    {
      printf("发送错误 \n");
      break;
    }
  }
  return 0;
}

14.4.1 服务端实现

对于服务端代码而言,一旦accept函数接收到有客户端连接后则自动将该sClient指针传输到子线程内执行,这样即可实现两者功能互不干扰。程序中通过使用CreateThread函数创建了两个线程来处理与客户端之间的发送和接收数据。将SendFunctionRecvFunction作为参数传递给线程,并与新的客户端套接字一起传递。线程存储在变量hThread1hThread2中。

int main(int argc, char* argv[])
{
  // 初始化套接字动态库
  WSADATA wsaData;
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
  {
    return 1;
  }

  //  创建服务段套接字
  SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sServer == INVALID_SOCKET)
  {
    WSACleanup();
    return -1;
  }

  //  服务端地址
  sockaddr_in addrServ;
  addrServ.sin_family = AF_INET;
  addrServ.sin_port = htons(9999);
  addrServ.sin_addr.s_addr = htonl(INADDR_ANY);

  //  绑定套接字
  if (bind(sServer, (const struct sockaddr*)&addrServ, sizeof(addrServ)) == SOCKET_ERROR)
  {
    closesocket(sServer);
    WSACleanup();
    return -1;
  }

  //  监听套接字
  if (listen(sServer, 5) == SOCKET_ERROR)
  {
    closesocket(sServer);
    WSACleanup();
    return -1;
  }

  SOCKET sClient;
  sockaddr_in addrClient;
  int addrClientLen = sizeof(addrClient);

  // 接收数据
  sClient = accept(sServer, (sockaddr FAR*) & addrClient, &addrClientLen);
  if (sClient == INVALID_SOCKET)
  {
    closesocket(sServer);
    WSACleanup();
    return -1;
  }

  printf("接收客户端 IP:[%s] --> port:[%d] \n", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port));

  // 分配线程
  HANDLE hThread1, hThread2;

  hThread1 = CreateThread(NULL, NULL, SendFunction, (LPVOID*)&sClient, 0, 0);
  hThread2 = CreateThread(NULL, NULL, RecvFunction, (LPVOID*)&sClient, 0, 0);

  WaitForSingleObject(hThread1, INFINITE);
  WaitForSingleObject(hThread2, INFINITE);
  CloseHandle(hThread1);
  CloseHandle(hThread2);

  closesocket(sClient);
  WSACleanup();
  return 0;
}

14.4.2 客户端实现

客户端的实现与服务端保持一致,唯一的区别在于客户端通过connect()主动向服务端发送连接请求,只要有新的连接被建立则将通过CreateThread函数创建线程,SendFunctionRecvFunction函数分别用于发送与接收功能。

int main(int argc, char* argv[])
{
  WSADATA wsaData;

  // 初始化库
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
  {
    return 1;
  }

  //  服务器套接字
  SOCKET sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sHost == INVALID_SOCKET)
  {
    WSACleanup();
    return -1;
  }

  SOCKADDR_IN servAddr;
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  servAddr.sin_port = htons(9999);

  //  连接服务器
  if (connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
  {
    closesocket(sHost);
    WSACleanup();
    return -1;
  }

  printf("连接到服务器 IP:[%s] --> port:[%d] \n", inet_ntoa(servAddr.sin_addr), ntohs(servAddr.sin_port));


  // 分别创建两个线程
  HANDLE hThread1, hThread2;

  hThread1 = CreateThread(NULL, NULL, SendFunction, (LPVOID)&sHost, 0, 0);
  hThread2 = CreateThread(NULL, NULL, RecvFunction, (LPVOID)&sHost, 0, 0);

  WaitForSingleObject(hThread1, INFINITE);
  WaitForSingleObject(hThread2, INFINITE);

  CloseHandle(hThread1);
  CloseHandle(hThread2);

  closesocket(sHost);
  WSACleanup();
  return 0;
}

编译并运行这两个程序,读者可自行测试,不论是在服务端还是客户端均可以实现双向数据通信功能,输出效果如下图所示;

image.png

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。