14.8 Socket 一收一发通信
通常情况下我们在编写套接字通信程序时都会实现一收一发的通信模式,当客户端发送数据到服务端后,我们希望服务端处理请求后同样返回给我们一个状态值,并以此判断我们的请求是否被执行成功了,另外增加收发同步有助于避免数据包粘包问题的产生,在多数开发场景中我们都会实现该功能。
Socket粘包是指在使用TCP协议传输数据时,发送方连续向接收方发送多个数据包时,接收方可能会将它们合并成一个或多个大的数据包,而不是按照发送方发送的原始数据包拆分成多个小的数据包进行接收。
造成粘包的原因主要有以下几个方面:
- TCP协议的特性:TCP是一种面向连接的可靠传输协议,保证了数据的正确性和可靠性。在TCP协议中,发送方和接收方之间建立了一条虚拟的连接,通过三次握手来建立连接。当数据在传输过程中出现丢失、损坏或延迟等问题时,TCP会自动进行重传、校验等处理,这些处理会导致接收方在接收数据时可能会一次性接收多个数据包。
- 缓冲区的大小限制:在接收方的缓冲区大小有限的情况下,如果发送方发送的多个小数据包的总大小超过了接收方缓冲区的大小,接收方可能会将它们合并成一个大的数据包来接收。
- 数据的处理方式:接收方在处理数据时,可能会使用不同的方式来处理数据,比如按照字节流方式读取数据,或者按照固定长度读取数据等方式。不同的处理方式可能会导致接收方将多个数据包合并成一个大的数据包。
如果读者是一名Windows平台开发人员并从事过网络套接字开发,那么一定很清楚此缺陷的产生,当我们连续调用send()时就会产生粘包现象,而解决此类方法的最好办法是在每次send()后调用一次recv()函数接收一个返回值,至此由于数据包不连续则也就不会产生粘包的现象。
14.8.1 服务端实现
服务端我们实现的功能只有一个接收,其中RecvFunction函数主要用于接收数据包,通过使用recv函数接收来自socket连接通道的数据,并根据接收到的数据判断条件,决定是否发送数据回应。如果接收到的数据中命令参数满足command_int_a=10和command_int_b=20,那么该函数会构建一个新的数据包,将其发送回客户端,其中包括一个表示成功执行的标志、一个包含欢迎信息的字符串以及其他数据信息。如果接收到的数据命令参数不满足上述条件,则函数会构建一个新的数据包,将其发送回客户端,其中只包括一个表示执行失败的标志。最后,函数返回一个BOOL类型的布尔值,表示接收函数是否成功执行。
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
typedef struct
{
int command_int_a;
int command_int_b;
int command_int_c;
int command_int_d;
unsigned int command_uint_a;
unsigned int command_uint_b;
char command_string_a[256];
char command_string_b[256];
char command_string_c[256];
char command_string_d[256];
int flag;
int count;
}send_recv_struct;
// 调用接收函数
BOOL RecvFunction(SOCKET &sock)
{
// 接收数据
char recv_buffer[8192] = { 0 };
int recv_flag = recv(sock, (char *)&recv_buffer, sizeof(send_recv_struct), 0);
if (recv_flag <= 0)
{
return FALSE;
}
send_recv_struct *buffer = (send_recv_struct *)recv_buffer;
std::cout << "接收参数A: " << buffer->command_int_a << std::endl;
// 接收后判断,判断后发送标志或携带参数
if (buffer->command_int_a == 10 && buffer->command_int_b == 20)
{
send_recv_struct send_buffer = { 0 };
send_buffer.flag = 1;
strcpy(send_buffer.command_string_a, "hello lyshark");
// 发送数据
int send_flag = send(sock, (char *)&send_buffer, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
}
else
{
send_recv_struct send_buffer = { 0 };
send_buffer.flag = 0;
// 发送数据
int send_flag = send(sock, (char *)&send_buffer, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
return FALSE;
}
return TRUE;
}
int main(int argc, char *argv[])
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
std::cout << "WSA动态库初始化失败" << std::endl;
return 0;
}
SOCKET server_socket;
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == ERROR)
{
std::cout << "Socket 创建失败" << std::endl;
WSACleanup();
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(9999);
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
std::cout << "绑定套接字失败" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
if (listen(server_socket, 10) == SOCKET_ERROR)
{
std::cout << "侦听套接字失败" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
SOCKET message_socket;
char buf[8192] = { 0 };
if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) == INVALID_SOCKET)
{
return 0;
}
send_recv_struct recv_buffer = { 0 };
// 接收对端数据到recv_buffer
BOOL flag = RecvFunction(message_socket);
std::cout << "接收状态: " << flag << std::endl;
closesocket(message_socket);
closesocket(server_socket);
WSACleanup();
return 0;
}
14.8.2 客户端实现
对于客户端而言,其与服务端保持一致,只需要封装一个对等的SendFunction函数,该函数使用send函数将一个send_recv_struct类型的指针send_ptr发送到指定的socket连接通道。在发送完成后,函数使用recv函数从socket连接通道接收数据,并将其存储到一个char型数组recv_buffer中。接下来,该函数使用send_recv_struct类型的指针buffer将该char型数组中的数据复制到一个新的send_recv_struct类型的结构体变量recv_ptr中,最后返回一个BOOL类型的布尔值,表示发送接收函数是否成功执行。
#include <iostream>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
typedef struct
{
int command_int_a;
int command_int_b;
int command_int_c;
int command_int_d;
unsigned int command_uint_a;
unsigned int command_uint_b;
char command_string_a[256];
char command_string_b[256];
char command_string_c[256];
char command_string_d[256];
int flag;
int count;
}send_recv_struct;
// 调用发送接收函数
BOOL SendFunction(SOCKET &sock, send_recv_struct &send_ptr, send_recv_struct &recv_ptr)
{
// 发送数据
int send_flag = send(sock, (char *)&send_ptr, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
// 接收数据
char recv_buffer[8192] = { 0 };
int recv_flag = recv(sock, (char *)&recv_buffer, sizeof(send_recv_struct), 0);
if (recv_flag <= 0)
{
return FALSE;
}
send_recv_struct *buffer = (send_recv_struct *)recv_buffer;
memcpy((void *)&recv_ptr, buffer, sizeof(send_recv_struct));
return TRUE;
}
int main(int argc, char* argv[])
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
return 0;
}
SOCKET client_socket;
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
WSACleanup();
return 0;
}
struct sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = htons(9999);
ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(client_socket, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
{
closesocket(client_socket);
WSACleanup();
return 0;
}
send_recv_struct send_buffer = {0};
send_recv_struct response_buffer = { 0 };
// 填充发送数据包
send_buffer.command_int_a = 10;
send_buffer.command_int_b = 20;
send_buffer.flag = 0;
// 发送数据包,并接收返回结果
BOOL flag = SendFunction(client_socket, send_buffer, response_buffer);
if (flag == FALSE)
{
return 0;
}
std::cout << "响应状态: " << response_buffer.flag << std::endl;
if (response_buffer.flag == 1)
{
std::cout << "响应数据: " << response_buffer.command_string_a << std::endl;
}
closesocket(client_socket);
WSACleanup();
return 0;
}
运行上述代码片段,读者可看到如下图所示的输出信息;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/4796bde3.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
14.8 Socket 一收一发通信的更多相关文章
- C#的Socket实现UDP协议通信
今天稍花化了一点时间,利用C#的Socket验证了UDP的通信,为接下来特地利用UDP做个分布式的通信仿真系统打下基础.众所周知,UDP 就是用户数据报协议,在互联网参考模型的第四层——传输层.与TC ...
- UE4 Socket多线程非阻塞通信
转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...
- Python的网络编程[0] -> socket[2] -> 利用 socket 建立 TCP/UDP 通信
Socket 目录 socket 的 TCP/IP 通信基本建立过程 socket 的 UDP 通信基本建立过程 socket 的 UDP 广播式通信基本建立过程 socket 的多线程通信建立过程 ...
- C# Socket Server 收不到数据
#/usr/bin/env python # -*- coding: utf- -*- # C# Socket Server 收不到数据 # 说明: # 最近在调Python通过Socket Clie ...
- 网络编程 TCP协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法
TCP协议:传输协议,基于端口工作 三次握手,四次挥手 TCP协议建立双向通道. 三次握手, 建连接: 1:客户端向服务端发送建立连接的请求 2:服务端返回收到请求的信息给客户端,并且发送往客户端建立 ...
- Python基于socket模块实现UDP通信功能示例
Python基于socket模块实现UDP通信功能示例 本文实例讲述了Python基于socket模块实现UDP通信功能.分享给大家供大家参考,具体如下: 一 代码 1.接收端 import ...
- java多线程实现TCP网络Socket编程(C/S通信)
目录 开篇必知必会 一.多线程技术 二.实现多线程接收 1.单线程版本 2.多线程版本 三.多线程与进程的关系 四.客户端界面完整代码 五.多线程通信对比 最后 开篇必知必会 在前一篇<Java ...
- python网络编程-socket套接字通信循环-粘包问题-struct模块-02
前置知识 不同计算机程序之间数据的传输 应用程序中的数据都是从程序所在计算机内存中读取的. 内存中的数据是从硬盘读取或者网络传输过来的 不同计算机程序数据传输需要经过七层协议物理连接介质才能到达目标程 ...
- 合宙Luat | Cat.1 Socket数据收不到?学会两招不掉线
1 服务器收不到Socket数据的原因 Socket是大家使用Cat.1模块常用的功能之一,但Cat.1模块不是直接跟服务器连接,而是通过NAT(即网络地址转换)与服务器连接. 一个会话建立后会在NA ...
- [C++] socket - 1 [简单TCP通信C\S代码]
服务端: #include<iostream> #include<winsock2.h> #include<stdio.h> #pragma comment(lib ...
随机推荐
- Uniapp下GoEasy通知栏推送不工作问题排查记录
我们是uniapp开发的app,项目中的系统消息推送使用的是GoEasy Websocket 实时推送,上线一段时间后,客户反馈说,当app没有在前台运行时也需要想办法通知用户一些重要的系统通知.那么 ...
- "Process finished with exit code 1" 进程结束
问题描述 : springboot 程序运行出现以下情况 没有错误日志 返回运行结束 状态码 1 状态码为 1 的时候表示程序不是异常终止 连接到目标VM, 地址: ''127. ...
- CDI的概念理解
1.CDI是什么?目的和作用是什么? 概念(是什么):是JavaEE 6标准中一个规范, 作用(干什么): 它提供了Java EE平台上服务注入的组件管理核心,简化应该是CDI的目标,让一切都可以被注 ...
- Java版人脸跟踪三部曲之三:编码实战
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 作为<Java版人脸跟踪三部曲> ...
- AI技术在软件测试中的应用和实践
随着人工智能(AI)技术的快速发展,它在各个领域都展现出了巨大的潜力和影响力.在软件测试领域,AI技术也越来越得到重视和应用.本文将探讨AI技术在软件测试中的应用和实践,重点关注chatGPT如何根据 ...
- 基于Taro开发京东小程序小记
一.小程序基础模型 这里要从微信小程序的历史说起,从前身到现在大概分为3个阶段: 阶段1: 微信网页需要用到app的原生能力,微信官方推出了js-sdk 阶段2: 解决移动端白屏问题,采用微信web资 ...
- 进程相关API
ID与句柄 如果我们成功创建一个进程,CreateProcess函数会给我们返回一个结构体,包括四个数 据:进程编号(ID).进程句柄.线程编号(ID).线程句柄. 进程ID其实我们早见过了,通常我们 ...
- MyBatis 常用工具类
SQL 类 MyBatis 提供了一个 SQL 工具类,使用这个工具类,我们可以很方便在 Java 代码动态构建 SQL 语句 String newSql = new SQL() ({ SELECT( ...
- VuePress@next 使用数学公式插件
VuePress@next 使用数学公式插件 搞了一个VuePress1.0的 现在升级了一下,但是使用数学公式的插件老报错啊!经过不懈努力,终于搞定了.现在记录一下. VuePress 介绍 Vue ...
- 深度系统安装mysql
# 安装 Mysql 8.0.19下载 MySQL Community Server 8.0.19 [Compressed TAR Archive](mysql-8.0.19-linux-glibc2 ...