终端和服务器之间的交互(一)
背景
这一部分接着我项目起步阶段做的工作,利用已经实现好的图像截取和音频截取功能取完成,我们这一次描述的工作就是如何利用这些基础设施实现一个终端完整的工作程序
基本设计
终端和服务器之间的关系
终端需要在服务器的指令下,完成图像和音频的捕获,并将产生的文件发送至服务器。
要完成这样的工作,终端首先需要长期在线,从某种程度上来讲,终端也是一个服务器,不断等待着为web应用服务器提供服务
其次,终端与服务器之间的通信内容,服务器能够对终端进行控制,终端能向服务器发送文件,这就要求我们至少使用两种以上的协议:
- 文件发送使用的协议:不管具体实现是什么,这首先必须是一个基于流的协议,以TCP为基础
- 发送控制信号使用的协议:说的简单一点就是数据报,以UDP协议为基础
在明确了上述需求的条件下,我们就可以进行决策了——是自己实现一个协议还是利用已有的协议。这点,我们将分别对两种需求进行讨论
-
文件发送协议:经过相关资料的收集,发现文件传输并不是一个容易的工作:
- 需要为接收端的新文件确定一个文件名:如果采用自动生成的方式,则必须选择一个有意义的方式自动生成,方便服务端的开发;如果选择与源文件同名的方式,则必须将文件名发送给接收端,无论如何,这都需要一套额外的机制
- 文件大小的问题:与文件名的问题类似,接收端有义务保证文件和发送端的文件大小完全相同,要么在文件结尾添加结束符,要么直接将文件大小发送给接收方。总而言之,又是一套额外的协议
- 可靠性保证:虽然TCP可以保证可靠的传输,但可靠只是针对一个段的内容,而一个文件包含的段很多,还需要基于TCP实现一个保证整个文件可靠传输的协议
总而言之,还用各种各样需要考虑的方面,我借鉴了一下网络上关于文件传输的协议实现(ftp等),代码量没有低于800行的。亲自实现实在是没有这个闲工夫,而且直接拿过来用也怕出问题。所以,我们决定在文件传输上就选择ftp协议的相关API来实现
-
控制信号协议
整理我们上面的需求(刨去文件传输相关协议),我们需要的控制信号分为一下几类
-
服务器对终端的控制:
- 开始信号
- 停止信号
- 状态查询信号
虽然上面没有提到状态查询信号,但是服务器既然能操作终端的状态,那么很自然的可以想到服务器也应该能够查询终端的状态
-
状态信号:
- 待机状态
- 运行状态
- 不在线
服务器查询终端状态后的返回结果,其中不在线状态不需要通过网络传播
-
反馈信号
- 确认接收信号
- 拒绝信号
由于无法保证双方发送的内容都是对对方有效的,这里设定这两中信号,是为了保证数据报通信的可靠性
数据报这个东西没有现成的高层协议可选,因为我们使用的功能比较少。进行过上述分析,手动实现一套控制协议也不是十分困难,这也就是我们接下来的方针
-
终端上的程序结构
分析这个问题,我们首先需要回顾一下终端需要做些什么:
- 收集图像
- 收集音频
- 发送文件
经过我们上面的分析,终端还有一个必要的功能
- 响应控制信号
这些任务之间是独立性强,又有同步关系的,那么我们的工作也就十分明确了,就是为这些功能实现对应的线程,并设计线程之间的同步。
我们分析几个明显的同步关系
- 必须在收到开始信号后,两个收集线程才能开始工作,收到停止信号后,两个线程必须立刻停止,并将内存中的结果存入文件
- 发送线程只要又新文件,就得发送,和当前终端处于待机状态还是运行状态关系不大
那么这里就有一个问题,如何实现文件传输线程和文件捕获线程之间的异步通信,答案是我们之前在实现流水的深度学习程序中用到的技术——全局链表:
一旦终端被启动,捕获线程们就直接开始捕获,并把它们保存的文件的文件名存入链表,而发送线程不断检查链表,如果不为空,就继续发送,如果为空,就检查一下现在终端的状态,如果仍在运行,说明发送速度太快,等待一段时间后再次检查链表,否则直接认为本次会话中产生的所有文件发送完毕,停止工作
根据上面的描述,我们可以明确的看出,这个链表需要线程安全
实现
文件传输协议的实现
我们这里选择了ftp,首先需要配置ftp服务器,这项工作需要在服务器端的一个本地文件中完成,然后服务器将收到的文件发送给识别模块进行识别。我们在API的选择上选择了libcurl,利用了其中的ftp相关功能。
首先,检查API的可用性,探究使用方法
//trans.cpp
/**
* @brief init_udp_socket
* 创建一个udp socket并绑定到系统的一个端口
* @param port _in_ 端口
* @return
*/
int init_udp_socket(const int port)
{
//const char* local_ip = "127.0.0.1";
int sock;
struct sockaddr_in addr_param;
//创建描述符
while ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("Cannot open socket ");
sleep(3);
}
memset(&addr_param, 0, sizeof(