1.中控和仪表通信并且传输升级包
主要就是传输升级包的功能,传输通路是socket, 传输协议是东风的双屏互动的升级协议。
用TCP的socket,写个socket线程替代原来的spi_ipc_lvds内部的线程。
除了修改协议,adptor和spi_ipc_cluster通过一个socket线程替换就可以。
主要是用tcp,socket创建读写函数接口,替换原来的adptor中的读写接口,这样可以直接用新的读写接口替换main函数中原来的接口。可以不用pthread_create创建线程函数,可以将socket连接的id值定义成类的成员变量,再单独写读写函数,因为socket的读写函数主要就是利用这个id值来传输的 ,其他的传递步骤可以保持不动,剩下的就是修改协议中的交互步骤。
int ret = read(sock, buf, TCP_PROTOLAL_SIZE);
int ret = write(sock, buf, len);
2.用pthread_create函数创建了一个线程,读写,以及处理函数的调用都在这个线程中,是可以实现传输,但是必须一步一步的执行。
服务器和客户端的线程id,服务器有两个,一个用来监听,一个用来连接。
3.数组不能整体赋值
4.将读写线程分开的一些问题,以及其他的问题
a.把读和写放在线程中,处理函数放在线程外面,结果读和写的线程自己去跑,压根不出来。
想到的办法是,在最开始加一个信号量,读完减1,写完加1,并加条件判断,当信号量的值不符合时,continue,这样就会从当前的线程中跳出来,不过会立马跳到另一个线程,还是不出来。最后只能把处理的函数重写写进线程里面。
b.pthread_join会一直阻塞等待线程退出,第二个线程就一直执行不了了。
想到的办法是,将pthread_join函数注释掉,在它之前加sleep(1000),在后面的测试中,发现一直在等待,过后就停掉了,因为sleep(1000)等待时间过长。
c.char* message_buf; 定义了指针,但是没分配空间,在后面运行的过程中,总是会死掉。
解决办法,给指针开辟空间 char* message_buf = new char[IVI_TO_CLUSTER_LENGTH];
d.传输速度很慢,原因是在读之前的等待时间过长
解决办法是,将sleep的等待时间减小,改成usleep函数。但是这个在进行不同设备之前的传输的时候,并不能起到很稳定的作用,因为本地传输的话,可以在等待一会后,读到自己想读到的数据长度,但是网络传输,接收的时候,收到的长度是不固定的。
e.cmake的文件中替换原来的文件,链接的库也要修改,socket线程就调用socket库
f.代码一运行,就直接挂了,以为没有进入main函数,当时学到了一个很简单实用的方法,就是将return 0加在main函数的刚开始,如果不报错,说明进入main函数了;接下来就可以将return 0 一步一步挪动,来检查停在哪一步了。表明单纯的加log也不一定起作用。
g.因为创建线程的同时,会有其线程的启动函数,但是是static模式的,不能调用类的内部函数和内部变量;并且因为调用的处理函数是属于其他类的,也不能直接调用。
解决办法是:
1.需要先定义其相关的对象,可以在.cpp文件的开头进行定义,在需要用到的线程中进行实现;
2.可以将pthread_create函数的第四个参数,改成this指针。本身第四个参数是thread_run函数的参数,这样可以在thread_run函数中用this指针指向类的另一个内部函数,相当于回调,将读写的过程都放在thread_read这个函数中去实现,这样就可以当做读写都在同一个类中,而不是在static函数中。
pthread_create(&threadTCP, &attrTCP, thread_run, this);
void* thread_run(void* arg)
{
((TCPClient*)arg)->thread_read();
return NULL;
}
例子:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
class Thread
{
private:
pthread_t pid;
private:
static void * start_thread(void *arg);//
//静态成员函数
public:
int start();
virtual void run() = 0; //基类中的虚函数要么实现,要么是纯虚函数(绝对不允许声明不实现,也不纯虚)
};
int Thread::start()
{
if(pthread_create(&pid,NULL,start_thread,(void *)this) != 0) //′创建一个线程(必须是全局函数)
{
return -1;
}
return 0;
}
void* Thread::start_thread(void *arg) //静态成员函数只能访问静态变量或静态函数,通过传递this指针进行调用
{
Thread *ptr = (Thread *)arg;
ptr->run(); //线程的实体是run
}
class MyThread:public Thread
{
public:
void run();
};
void MyThread::run()
{
printf("hello world\n");
}
int main(int argc,char *argv[])
{
MyThread myThread;
myThread.start();//
test.run();
sleep(1);
return 0;
}
h.对于协议中要求的升级的反馈,几种不同的状态不知道要怎么回复
解决办法是:在打开接收文件的路径的时候进行判断,每次都判断,打开成功表明接收成功,打开失败表明接收失败,并将状态写在data区域,至于中控读不读另说。其他状态也是存储在相同的位置。
i.读取文件总是不成功,用流传输的方式?
5.电脑代替中控同仪表联调的问题
a.传输过程中,电脑端可以一次性写入一帧数据,但是仪表板子上并不能一次性读出,需要拼接,采用的是在读取的时候就拼接,就是用recv函数的第四个参数和第三个参数,让它知道读够一定的长度才返回,或者是加while循环,一直读,直到读到固定长度;然后才去处理,后面的内容就可以不用更改。这种方式只适用于每帧的数据长度相等,不等的情况需要另外处理。
b.宏定义,结构体等的摆放位置,以及头文件的互相调用,都会导致编译失败
c.电脑端代表的中控发送过来的升级包字节错乱?
最后发现在服务器端的代码中,将return返回的回复消息赋给一个在当前类中定义的内部结构体,相当于全局的结构体之后,消息就已经发生错乱了,然后再调用返回消息的那个函数中直接定义了一个局部的结构体来保存返回结构体,就没有错误了。
调用的返回的消息的函数是属于另一个类的,然而所被赋值的结构体确实当前类的私有成员,是这个原因吗?
从中得到的其他的结论是,在用tcp,socket 传输的过程中,虽然每次并不是读取固定的长度,但是内核中会自动帮助打包服务器传送过来的代码,不会发生错乱,如果乱序,1是在服务器发送的时候就已经错乱,2是在读取之后拼接时发生错乱;是不会在内核,或者说是在socket的读写id中出错的。
6.中控同仪表的联调
a.中控端的数据存储方式,和仪表端正好相反,中控端分别采用了下面两种方式,太混乱
frame_header = (unsigned char)message_buf[0] << 24 | (unsigned char)message_buf[1] << 16 | (unsigned char)message_buf[2] << 8 | (unsigned char)message_buf[3];
sub_type = (unsigned char)message_buf[8] | (unsigned char)message_buf[9] << 8 | (unsigned char)message_buf[10] << 16 | (unsigned char)message_buf[11] << 24;
b.心跳的回复跟下一帧的数据粘到一起了
读取并处理一条数据的整体思路是:read() -> deserialize_buf(拆分) -> push() -> pop() -> socket_process(处理) -> serialize_buf(合并) -> write()
解决的办法是:首先是要先添加帧头帧尾
1).在读取的时候就拼接好
(1)同5.a的方法,用recv函数确保能够读取的长度才返回,但是只适用于固定长度
(2)消息头是固定的20个字节,里面包含需要读取的数据内容的datalen,可以先读取20个字节,然后解析,根据解析出来的长度来读取接下来要读的数据长度:
int ret = 0;
int ret1 = 0;
int ret2 = 0;
unsigned int m_sub_type = 0;
unsigned int m_len = 0;
ret = read(sock, buf, TCP_PROTOLAL_SIZE);
m_sub_type = (unsigned char)buf[8] | (unsigned char)buf[9] << 8 | (unsigned char)buf[10] << 16 | (unsigned char)buf[11] << 24;
m_len = (unsigned char)buf[16] | (unsigned char)buf[17] << 8 | (unsigned char)buf[18] << 16 | (unsigned char)buf[19] << 24;
buf += ret;
ret1 = m_len + TCP_MAGIC_SIZE;
while(ret1 > 0)
{
ret = read(sock, buf, ret1);
if(ret <= 0)
{
return -1;
}
buf += ret;
ret1 -= ret;
}
ret2 = m_len + TCP_MAGIC_SIZE + TCP_PROTOLAL_SIZE;
2).在pop的时候拼接,利用在结构体中定义的flag来拼接,但容易出问题,把心跳拼进去
3).不拼接,边读边写
(1)在deserialize_buf(拆分)中拆分消息头,根据不同的消息头进行不同的处理,并加上一个表状态的flag
/a/ 有头有尾 根据新拆出来的类型进行处理 flag = 0
/b/ 有头没尾 同上,同时保存这种类型 flag = 1
/c/ 没头有尾 沿用之前保存的类型 flag = 2
/d/ 没头没尾 同上 falg = 3
(2)根据拆分出来的不同的类型,以及不同的flag,在socket_process函数中进行不同的处理
(3)发现这样子可能会将心跳的回复连接到升级包的数据中,因此在deserialize_buf(拆分)函数之前,将sub_type子类型拆分出来进行了简单的判断,当总共读取到的长度大于本来需要的长度的时候,就先进行一次拆分;但是这样还有可能出现心跳拼接在下一帧升级包数据后面的情况,导致if条件判断太多,速度很慢,并且很容易卡死。而且传输速度并不受读取函数之前的sleep的等待时间的限制。而且这种情况的代码复用性不高。
(4)同时,在消息的结构体中加入了一个固定的flag,表示当有帧尾时为1,没有时为0,可以取代上面的表示状态的flag。
c.中控同仪表一直在心跳中循环
心跳是仪表端主动发送,返回中控回复,仪表端接收并将计数器置为0,本应该直接return -1,结束掉这个交换,但是我回复了,导致中控同仪表一直在心跳中循环;
d.connect ——升级——recv这个顺序可以接收到数据,connect——recv——升级不接收不到从中控传过来的数据,最后发现是两边对心跳的理解不一样;中控的理解是在connect连接上后就开始启动心跳,用心跳来保持连接,仪表这边却是在接收到第一帧数据后才启动心跳。结果就是需要把定时器提前。
7.实现当中控中断重新传输的时候,仪表端要删除之前已经接收一部分的升级包,并将累计的已接收的长度置为0,发现每次传输,在文件信息后面紧接着的第一帧数据不相同,发现是读取信息的问题
8.修改文件工程的名字
需要修改:代码中的cmakelist中生成的可执行文件名,
9.根据驱动提供的接口和命令,在system中补充代码,根据读取到的接口值的不同来执行不同的命令:
发现的问题并解决的方式:打开的路径记得要关闭;接口的值就两种,不是0就是1,不能把需要用的参数本身刚开始就置为0或者1,可能会影响后面的转换,可能会因为代码没运行好而自己改变了命令。
10.select函数
a.select函数是用来检测socket描述符是否可读可写的,可读的前提是有数据在里面。
b.connect函数和socket函数需要单独写成一个函数,便于调用,而不是写在构造函数里面。
c.read函数同write函数的返回值为0时,都表明同中控的连接断开了。
d.select函数在得到一次表示超时的返回值0后 ,FD_SET函数中的socket描述符就会自动删除掉,rd里面只会保留本次select有响应的fd,所以需要在每次select函数调用之前都先使用FD_SET函数将要检测的socket描述符加进去。
**11.**当仪表先于车机启动的时候,两者就connect不上了,因为之前socket的创建和connect是分开写成了两个函数,也就是仪表在启动的时候就调用了socket函数,然后一直在connect函数中循环等待,之后改成了将socket和connect连接在一起,connect不上就close,然后重新socket创建。
**12.**在systemMonitor中加的监控转换io-usb驱动的接口的值的代码,出现了一段奇怪的log,最终发现这段log是由于lvds投图有一个宏定义跟systemmonitor相同了,然后投图的以太网检测触发了systemmonitor的代码,然后就出现这个log,最后就是将这个相撞的宏定义的值修改了。
**13.**因为将心跳提前到读写的类中,导致为心跳计数清零的处理函数中的变量不能再用:尝试使用全局变量,失败;尝试直接在类中定义读写的类对象,编译可以通过,但是执行不了,失败;最后尝试多重调用:在处理函数中调用 调用了定义了读写以及该变量的类的对象的对象,然后对冲调用实现对该变量的使用。
**14.**一个类包含另一个类的对象,反过来另一个类无法使用第一个类,也就是dispatcher该类包含client类的对象,但是client这个类无法在它的类中定义dispatcher类对象,但是client类又需要使用dispatcher类的内部函数:尝试创建对象失败;然后在client中定义一个变量,用该变量来存取要发送出去的信号,然后供dispatcher类调用,在dispatcher类中自己调用client类本来需要使用的函数,总之既然自己使用不了,那就把信号发出去,让可以使用的类帮助处理。当然,前提是将变量设置为public类型。
15.学习到一个可以打印传输过程中两者交互的数据是否发送并ack反馈的工具:
tcpdump拷贝到/mmc/bin,librpc.so.2库拷贝到/mmc/lib
tcpdump ip host 192.168.2.100 and 192.168.2.99
tcpdump -x ip host 192.168.2.100 and 192.168.2.99 //加个X可以打印一部分数据,主要方便看发送的是心跳还是下一帧请求