今天总结一下之前node.js的C++Addon的项目,去交流后,否决了之前把MacDataTransfer.lib和dll再次封装成为node可用的.node文件的方案,因为这个转换的MacDataTransfer对象和存储解析结果的ST_SEND_RTCM32_MSG结构体都要求声明为全局变量并一直保持运行,而这在之前的方案是做不到的。所以换成了整个“接收——输入——解析”的流程都在VC++平台上完成的方案,之后再把解析结果封装成JSON再发送出去。
为此我算是重新拾起两年没写的C++,小到数据类型转换和读取,大到多线程和TCP SOCKET,都重新温习了一遍。虽然最后只写了一个200+行的小程序,但也用到了不少东西,在这里记录下来。
数据类型
C++的数据类型都比较原始,大致说来就是往下一走就是内存,因而不同类型的数据转换须小心谨慎。比如说unsigned char转换成char,一个unsigned字符需要占用两个char的位置,因为unsigned char是-127—127的,而char是0—255,为保证数据不丢失,需要相应的内存去储存它们。
数据转换和读取
有了对数据类型在内存中的性质的了解,进行数据转换就有的放矢了。
说白了数据的转换就是新建一个目标数据类型的变量a,把源数据类型的变量b所指的内存中的编码?字符?反正是这些东西拷贝到a中,如果数据在拷贝过程中没有丢失,那么应该就算是转换成功。
常见的拷贝方法
- 直接赋值
- 内存拷贝(memcpy)这是C的方法,很多C的方法其实都很好用
- 最强的,sprintf(),这也是C的方法,它相当于把printf的打印目的从终端改为目标地址(即我们的变量、数组什么的)。
基本用法是第一个参数是目标地址(字符串类型),第二个参数是格式化控制:如“%2d”,“%6.2X”等等,能用于控制打印格式,这个也是它的精髓,甚至我们可以“%*X”,随后在后面用变量来规定格式,比如:
sprintf(dest,"%*X",len,origin); // dest:目标字符串,len:对应"*"位置的参数,origin:源数据
这也是为什么说sprintf强大的原因,比如在一些存储Unicode字符,16进制字符这些特殊类型的数组,我们调试时它们会被VS显示为无效字符,因为它们如果被理解为ASCII码,将是一大堆非打印字符。这时候我们在VS中可以使用
添加监视————在监视窗口中,在名字后面加上".x"来显示为16进制形式,unicode也有相应的参数
我一开始在尝试解析的时候,看到一堆非打印字符,一脸懵逼(这也是源于无知,我并不了解它们是16进制的,也不知道如何查看16进制的值)。
sprintf就能很好的执行数据转换的功能,我们可以使用它,很方便的把unsigned 类型的原始数据转换为我们想要的。它还可以从结构体数组中很方便地获取其中的数组等等类型数据而不需要预设get()方法,而这个memcpy是做不到的。
sprintf甚至可以用于拼接字符串,代替strcat(),只需把后面的源数据实际参数,改成多个就可以了。
TCP SOCKET
说来惭愧,我的C++从来都没有写过除了算法之外的东西,所以这些算是首次接触。
我们的TCP SOCKET也不是特别复杂,同步类型的比较简单,因为我写的这个程序目前只需要从单源接收,转发给单目标,所以我只写了同步的。
TCP SOCKET同步下也没什么坑,但是它的recv函数说实话比较鶸,而相反UDP协议下的recvfrom函数就能获取我们本次收到的字节数。这个相当赞。因为我们的转换函数的入口是需要数据长度的(在这里再次吐槽C++的数组,实在是太原始了)。幸好我们TCP SOCKET也能使用recvfrom函数(是在connect函数后使用,意味着是client才能用?),这个函数的返回值就是接收长度。
多线程
上面提到过了,我写的这个程序目前只需要从单源接收,转发给单目标,所以多线程方面也不太需要很多线程,我只需要写成一个收线程(输入),一个发线程(解析——封装——发送),然后按最简单的”生产-消费者“模型,设置一个互斥量就可以完成了。
C++多线程在windows下依赖windowsAPI,所以需要包含<windows.h>头文件。
DWORD WINAPI RecT(LPVOID lpParameter) {
while (true) {
WaitForSingleObject(hMutex, INFINITE);
cout << "写入缓冲区" << endl;
len = recvfrom(socketclient, szRecv, 2048, 0, (struct sockaddr*)&sourceAddr, fromlen);
cout << "输入数据,长度为:" << len << endl;
ReleaseMutex(hMutex);
}
return 0L;
}
DWORD WINAPI sendT(LPVOID lpParameter) {
int errMsg = 0;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET servSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/*int result_set = setsockopt(servSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&f, sizeof(f));
if (result_set==0)
{
cout << "设置socket,关闭Nagle成功" << endl;
}*/
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(SOCKADDR));
//memset(&)
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(2019);
servAddr.sin_addr.s_addr = inet_addr("192.168.3.339");
::bind(servSocket, (SOCKADDR *)&servAddr, sizeof(SOCKADDR));
listen(servSocket, 20);
destSocket = accept(servSocket, (SOCKADDR *)&servAddr,&nSize);
while (true) {
WaitForSingleObject(hMutex, INFINITE);
cout << "解析数据" << endl;
errMsg = send(destSocket, msg_send.c_str(), msg_send.length()*sizeof(const char),0);
ReleaseMutex(hMutex);
if (SOCKET_ERROR == errMsg)
{
closesocket(destSocket);
listen(servSocket, 20);
destSocket = accept(servSocket, (SOCKADDR *)&servAddr, &nSize);
}
}
return 0L;
}
基本上简单的带互斥量的一个轮换的模型就是这样子。如果要真正实现生产者——消费者模型,还得设置两个全局变量full和empty。
我在发送进程中添加了防止客户端掉线之后仍然往原来的socket发送数据的代码,就是利用send()函数发送失败会返回-1的特点。
JSON for modern C++
这是一个德国大神写的C++下的JSON库,优点在于简便(只需包含json.hpp一个头),单元测试全覆盖(所有异常都能覆盖,捕捉到),可谓相当的强大。
但是我是上学期末才开始接触node.js,而我理解JSON是从JavaScript衍生出来的,JSON的方法我算是一窍不通。
我封装JSON的过程需要解决以下几个问题:
-
我们的解析结果有相当多的冗余数据,我需要一一剔除,这也是我为什么需要从结构体数组里提取数据的原因。这个ST_SEND_RTCM32_MSG结构体虽然关于存储数据的数组的长度有标志元素,但是它很(和谐)地设置了一个默认值,默认长度是满的,如果这个数组是空的,也就是没有这个数据,那么相应的标志元素就不会得到修改,所以整个数组就是满满的"0"(所有未赋值的位置都被初始化为"0")。
-
我需要判断数据是否存在,动态的加入到JSON中,这意味着我不能用直观的静态赋值的方式。
----------------------------------- 我是分界线 --------------------------------------
我的解决方法如下:
-
剔除冗余数据我用循环sprintf的形式,顺便转换了数据形式。对于它们有可能为空但是标志元素却是满长度的情况下,我们大胆推测它不可能满,满就是空。
-
动态加入JSON我是用JSON_PATCH + 修改对应的值的方式来实现的,先用JSON PATCH的方式添加一个键值对,值初始化为随便一个字符(因为JSON PATCH补丁不支持变量的值好像),随后用json.at()=???的方式,用赋值形式修改相应键的值就好了。
json msg;
msg = R"({ "name":"grid"})"_json;
msg.at("name") = i;
if (stRoverRtCM32Msg[i]->nByte != 1024) {
for (int j = 0; j < stRoverRtCM32Msg[i]->nByte; j++)
sprintf(my_pbuf + j * 2, "%02X", stRoverRtCM32Msg[i]->pbuf[j]);
json j_Patch = R"([{"op": "add", "path": "/MT1074", "value": "B"}])"_json;
msg = msg.patch(j_Patch);
msg.at("MT1074") = my_pbuf;
}