大家好,我是C++新人qing,今天继续给大家分享我的编程想法。
此项目的套接字编程环境是Linux,适合新人学习,有任何问题可以在下方评论区回应。
-----------------------------------------
众所周知,TCP协议中的接收方本身,它是不能够确定要收消息的长度。
这样就带来了一些问题,为了解决这个问题,很自然地想到让发送方在发送消息之前先发送此次要发的消息主体长度。接收方收到长度之后,根据这个消息的长度来创建第二次接收需要的缓冲区。
这样一来,我们的通信约定就包括但不限于TCP协议了,有了额外的要求。
我给此约定起了一个名字,叫SIMPLE,表示简单通信。
要实现简单通信有几个问题:
1.消息长度如何发送?这里有两个解决方法,分别是先发送一次消息的长度,收到回应后再发送一次消息主体;以及把消息长度编码为固定长度的报头,接收方先接收报头,解析出剩下的长度再接收第二次。
2.是连续发送还是交替发送?TCP发送时把数据放到缓冲区就算发送成功了,那么我们要知道对方是否接收到消息就需要让接收方发送回应。为了防止两个消息不按顺序地拼接在一起,在连续发送的状态下,我们发送消息及接收回应的次数应当是偶数次,在交替发送状态下,因为镜像的原因应该是奇数次。
-----------------------------------------
在这里因为本人水平尚且有限,我选择了更慢的分次发送,然后交替发送。
我的实现中,每次发送SIMPLE消息需要3次TCP发送,分别是:
1.发送方发送长度
2.接收方发送回应
3.发送方发送主体
包装长度消息、回应消息都使用了cJSON库,大家可以从这里下载:
DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C (github.com)
编译时只需要将cJSON.c、cJSON.h纳入项目中就可以了。
//SIMPLE.h
/* LCI简单数据交换协议:
* 1.发送方发送此次数据的长度,格式为JSON
* 2.接收方收到长度后发送反馈信息,格式为JSON。
* 3.发送方接收反馈后发送数据体
* 4.接收方根据数据长度创建缓冲区并接收数据。
*
* extra:
* 1.在发送数据长度时,可以携带数据的加密方式信息。*/
#pragma once
#include <iostream>
/* 这个是用来创建缓冲区,以接收此次数据的长度信息的 */
#define BUFFER_SIZE 1024
namespace qing {
//异常类
class Error: public std::exception {
public:
Error(): m_message("LCI代码部分出了问题。"){}
Error(const std::string& message): m_message(message) {}
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class Simple {
public:
//=================================================================
/* 使用简单LCI数据交换协议接收信息 */
static std::string recv_msg(int sock);
//=================================================================
//Check if there is data available to read on the socket
//检查套接字缓冲区中是否还有可以读的数据
//
//The parameter is a created socket descripor
//参数是一个已经创建好的套接字描述符
//
//@qing,20240525: 第二次接收时可能会发生问题
static int checkSocket(int sock_fd);
//=================================================================
/* 通过LCI协议发送数据 */
static long send_msg(int sock, std::string s);
//=================================================================
//=================================================================
//-------------------------------------------------------------
class CommuniError: public Error{
public:
CommuniError(const std::string& msg): Error(msg){}
};
//-------------------------------------------------------------
class RcvError: public CommuniError{
public:
RcvError(const std::string& msg): CommuniError(std::string("接收数据异常:") + msg){}
};
class RcValueError: public CommuniError{
public:
RcValueError(const std::string& msg): CommuniError(std::string("接收到的值异常:") + msg){}
};
class SndError: public CommuniError{
public:
SndError(const std::string& msg): CommuniError(std::string("发送数据异常:") + msg){}
};
class ChkRcvBufError: public CommuniError{
public:
ChkRcvBufError(): CommuniError(std::string("检查接收缓冲区出错。")){}
};
//-------------------------------------------------------------
};//交流
}//qing
//SIMPLE.cpp
/* LCI简单数据交换协议:
* 1.发送方发送此次数据的长度,格式为JSON
* 2.接收方收到长度后发送反馈信息,格式为JSON。
* 3.发送方接收反馈后发送数据体
* 4.接收方根据数据长度创建缓冲区并接收数据。
*
* extra:
* 1.在发送数据长度时,可以携带数据的加密方式信息。
* 2.当双方互相交流时候,每次发送信息调用3次,
* 3.当双方持续发送时候,每次发送信息调用4次。*/
#include <string.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "cJSON.h"
#include "SIMPLE.h"
namespace qing {
//=================================================================
std::string Simple::recv_msg(int sock) {
/* 使用简单LCI数据交换协议接收信息 */
std::string s = "";
double lengthValue = 0;
//---------------------------------------------------------
//---------------------------------------------------------
{
/* 接收并解析长度信息,格式为JSON字符串 */
/* 创建第一缓冲区 */
char buffer[BUFFER_SIZE+1];
buffer[0] = '\0';
/*从第一缓冲区接收长度信息*/
ssize_t size = recv(sock, buffer, BUFFER_SIZE, 0);
if (size == -1)
throw RcvError(std::string("接收LCI长度信息。"));
buffer[size] = '\0';
/*解析位于第一缓冲区的JSON格式的长度信息*/
cJSON *root = cJSON_Parse(buffer);
cJSON *lengthObj = cJSON_GetObjectItem(root, "length");
if (lengthObj && cJSON_IsNumber(lengthObj)){ /*找到了*/
lengthValue = cJSON_GetNumberValue(lengthObj);
if (lengthValue < 0) /* 非法的长度 */
throw RcValueError(std::string("非法的LCI长度") + std::to_string(lengthValue) + "。");
} else /*没找到*/
throw JsonInterface::JsonParseError(std::string("LCI长度信息。") + strerror(errno));
/*释放掉解析的JSON对象*/
cJSON_Delete(root);
}
//-------------------------------------------------------------
//-------------------------------------------------------------
//Check if there is data to read
//检查缓冲区中是否还有数据(测试用)
int len2 = checkSocket(sock);
if (len2 > 0)
throw RcvError(std::string("缓冲区剩余数据") + std::to_string(len2) + "。");
//-------------------------------------------------------------
//-------------------------------------------------------------
{
/* 准备回应的JSON字符串 */
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "response", "1ok");
char *jsonString = cJSON_Print(root);
/* 发送回应的JSON字符串 */
size_t n1 = strlen(jsonString);
ssize_t n2 = send(sock, jsonString, n1, 0);
if (n1 != n2)
throw SndError(std::string("发送第一次LCI回应。"));
/* 释放打印的JSON字符串,以及创建的JSON对象 */
free(jsonString);
cJSON_Delete(root);
}
//-------------------------------------------------------------
{
/* 准备用于接收数据体的第二缓冲区 */
char *recvBuf = (char*)malloc((lengthValue + 1) * sizeof(char));
*recvBuf = '\0';
/* 接收数据体 */
ssize_t size = recv(sock, recvBuf, lengthValue, 0);
if (size == -1 || size != lengthValue)
throw RcvError(std::string("接收LCI数据体。"));
recvBuf[size] = '\0';
/*获取数据体,释放创建的第二缓冲区*/
s = recvBuf;
free(recvBuf);
}
//-------------------------------------------------------------
//-------------------------------------------------------------
//Check if there is data to read
//检查缓冲区中是否还有数据(测试用)
len2 = checkSocket(sock);
if (len2 > 0)
throw RcvError(std::string("缓冲区剩余数据") + std::to_string(len2) + "。");
//-------------------------------------------------------------
//-------------------------------------------------------------
{
/*
// 准备回应的JSON字符串
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "response", "2ok");
char *jsonString = cJSON_Print(root);
// 发送回应的JSON字符串
size_t n1 = strlen(jsonString);
ssize_t n2 = send(sock, jsonString, n1, 0);
if (n1 != n2)
throw SndError(std::string("发送第二次LCI回应。"));
// 释放打印的JSON字符串,以及创建的JSON对象
free(jsonString);
cJSON_Delete(root);
*/
}
//返回接收到的LCI数据体
return s;
}/*接收信息*/
//=================================================================
long Simple::send_msg(int sock, std::string s) {
/* 通过LCI协议发送数据 */
/* 获取该消息的长度 */
size_t l1 = s.size(), l2 = 0;
//-------------------------------------------------------------
/* 创建JSON格式的消息长度信息 */
{
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "length", l1);
char *jsonString = cJSON_Print(root);
/* 发送长度信息 */
size_t n1 = strlen(jsonString);
ssize_t n2 = send(sock, jsonString, n1, 0);
if (n1 != n2)
throw SndError(std::string("发送长度信息。"));
/* 释放打印出的JSON字符串,以及JSON对象 */
free(jsonString);
cJSON_Delete(root);
}
//-------------------------------------------------------------
//-------------------------------------------------------------
{
/* 创建缓冲区,以接收回应 */
char buffer[BUFFER_SIZE+1];
buffer[0] = '\0';
/* 接收回应信息 */
ssize_t size = recv(sock, buffer, BUFFER_SIZE, 0);
if (size == -1)
throw RcvError(std::string("接收第一次回应。"));
buffer[size] = '\0';
/* 解析回应信息 */
cJSON *root = cJSON_Parse(buffer);
cJSON *responseObj = cJSON_GetObjectItem(root, "response");
if (responseObj && cJSON_IsString(responseObj)){
const char *responseValue = cJSON_GetStringValue(responseObj);
if (strcmp(responseValue, "1ok") != 0)
throw RcValueError(std::string("第一次回应消息") + responseValue + "非法");
} else
throw JsonInterface::JsonParseError(std::string("第一次回应信息") + strerror(errno));
/* 删除解析出的JSON对象 */
cJSON_Delete(root);
}
//-------------------------------------------------------------
//-------------------------------------------------------------
//Check if there is data to read
//检查缓冲区中是否还有数据(测试用)
int len2 = checkSocket(sock);
if (len2 > 0)
throw RcvError(std::string("缓冲区剩余数据") + std::to_string(len2) + "。");
//-------------------------------------------------------------
{
/* 发送数据体 */
l2 = send(sock, s.c_str(), l1, 0);
if (l1 != l2)
throw SndError(std::string("发送消息。"));
}
//-------------------------------------------------------------
{//
/*
//创建缓冲区,以接收第二次回应
char buffer[BUFFER_SIZE+1];
buffer[0] = '\0';
// 接收回应信息
ssize_t size = recv(sock, buffer, BUFFER_SIZE, 0);
if (size == -1)
throw RcvError(std::string("接收第二次回应。 "));
buffer[size] = '\0';
//解析回应信息
cJSON *root = cJSON_Parse(buffer);
cJSON *responseObj = cJSON_GetObjectItem(root, "response");
if (responseObj && cJSON_IsString(responseObj)){
const char *responseValue = cJSON_GetStringValue(responseObj);
if (strcmp(responseValue, "2ok") != 0)
throw RcValueError(std::string("第二次回应") + responseValue + "非法");
} else
throw JsonInterface::JsonParseError(std::string("第二次回应信息") + strerror(errno));
// 删除解析出的JSON对象
cJSON_Delete(root);
*/
}
//-------------------------------------------------------------
//Check if there is data to read
//检查缓冲区中是否还有数据(测试用)
len2 = checkSocket(sock);
if (len2 > 0)
throw RcvError(std::string("缓冲区剩余数据") + std::to_string(len2) + "。");
//-------------------------------------------------------------
//-------------------------------------------------------------
return l1;
}//发送数据
//=================================================================
int Simple::checkSocket(int sock_fd){
//Check if there is data available to read on the socket
//检查套接字缓冲区中是否还有可以读的数据
//
//The parameter is a created socket descripor
//参数是一个已经创建好的套接字描述符
//
//@qing,20240525: 第二次接收时可能会发生问题
fd_set readfds; //定义一个文件描述符集合变量readfds
FD_ZERO(&readfds); //初始化readfds集合,将所有位设置为0
FD_SET(sock_fd, &readfds); //将套接字文件描述符sock_fd添加到readfds集合中
//设置堵塞时间
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 1000;
//使用select函数来检查套接字sock_fd上是否有数据可读
//因为文件描述符的范围是从0开始的,所以要加1以包括套接字本身
int retval = select(sock_fd + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
// select调用失败
throw ChkRcvBufError();
} else if (retval > 1) {
//数据可以被读取
return retval;
} else {//结果等于1,可能是有数据,也可能是
//select调用成功了, 但是没有可读数据
//堵塞时间到了或是别的文件描述符就绪了
return retval - 1;
}
}
}//qing