目录
1 引言 ..............................................................................................................................................1
1.1 课题背景............................................................................................................................1
1.2 主要工作内容....................................................................................................................1
1.3 论文结构............................................................................................................................1
2 设计思路.......................................................................................................................................1
3 系统组成框图...............................................................................................................................2
4 服务端...........................................................................................................................................3
4.1 无线传感器网络................................................................................................................3
4.2 数据格式说明....................................................................................................................4
4.3 服务端实现........................................................................................................................6
5 客户端.........................................................................................................................................14
5.1 客户端思路......................................................................................................................14
5.2 客户端实现......................................................................................................................14
6 服务端移植.................................................................................................................................24
6.1 嵌入式 Linux ...................................................................................................................24
6.2 服务端移植至 liod270 开发平台....................................................................................25
7 测试结果.....................................................................................................................................25
7.1 测试环境..........................................................................................................................26
7.2 测试结果..........................................................................................................................26
8 总结 ............................................................................................................................................29
I
模式标识,据此开启线程。开启线程后,在串口回调函数的触发下,每次触发就发送一次串
口接收来的数据,为了使客户端与服务器端进行一些基本的错误处理能力,本文采用如下的
格式进行 Socket 数据传输:
表 2-1 本文 Socket 数据格式约定
本次发送的 Socket 数据长度
本次发送的 Socket 数据
其中本次发送的真实长度为数据长度加 1。
这样就可以根据实际发送长度和要发送长度进行判断,以处理 Socket 通信异常的一些
基本情况,例如客户端异常断开、服务端异常断开等。
根据无线传感器网络 EmBee 模块的相关材料中了解到,网络内和串口所传输的数据包
为如下格式:
表 2-2 基于 tinyos 操作系统的无线传感器网络数据包格式
Header
Data
(2 Bytes)
结束字段
(1 Bytes)
0x7E
校验
CCS
MSG_HEADER
(10 Bytes)
MSG_DATA
(长度不固定)
开始字段
(2 Bytes)
0x7E
0x42
其中开始字段和结束字段是固定不变的,故可将有开始字段和结束字段所包含的数据长
度是否大于 0 用来判断数据完整性。
客户端运行在 PC 上,将完成数据解析提取、显示、存储等工作,另外,通过一些简单
的协定,将系统扩展为可由客户端生成数据,由服务端从 Socket 接收后转发到串口,本文
的具体实现是客户端根据用户需求生成控制命令发给服务端,由服务端转发至串口,思路不
失一般性。
在互联网上,服务器处理的客户端可能往往不只一个,故将服务端设计成多客户端连接
模式。
3 系统组成框图
系统分为两部分,一是服务端程序,这是主要部分,运行在嵌入式 Linux 平台下,本文
测试平台为 liod 平台;二是客户端,这是与服务端配套的部分,运行在 PC 上,本文测试程
序为 MFC 对话框程序,运行在 Windows 下。
2
的 RAM、48KB Flash 和 128B 的 information flash。除此之外还提供了 8 个数模转换的控制
器端口、以及一系列的外设(例如 SPI、UART、数字 I/O 端口、看门狗等)。
EmBee使用 Chipcon CC2420 的 RF 进行无线通讯。CC2420 可由 TI MSP430 微控制器
通过 SPI 口、一系列的数字 I/O 线路和中断来控制。
EmBee 有 2 种可选择的天线,一种是内置于 PCB 板的内部天线,一种是通过 SMA 连
接器连接的外部天线。默认情况下 EmBee 使用内部天线,如果应用程序需要使用外部天线,
那么 SMA 连接器必须安装,外部天线就可以直接通过 SMA 连接上 EmBee 了。
EmBee 采用 DS1820 温度传感器,可提供 9 位温度读数。
4.2 数据格式说明
EmBee模块上位机和下位机通信时,所有的命令、应答均以如下数据包格式进行:
表 4-1 数据包格式
MSG_HEADER
(10 Bytes)
开始字段
(2 Bytes)
0x7E
0x42
所 有 的 包 有 着 固 定 的 开 始 字 段 和 结 束 字 段 , 负 载 数 据 包 括 MSG_HEADER 和
MSG_DATA 两部分,其中 MSG_HEADER 是 10 个字节,主要包括以下 8 个字段:
MSG_DATA
(长度不固定)
校验
CCS
结束字段
(1 Bytes)
0x7E
Header
Data
(2 Bytes)
字段名
length
fcfhi
fcflo
dsn
destpan
addr
type
group
表 4-2 MSG_HEADER 各字段意义
字段类型
UINT8
UINT8
UINT8
UINT8
UINT16
UINT16
UINT8
UINT8
意义
MSG_DATA 的长度(字节数)
FCF 的高字节,始终为 0
FCF 的低字节,始终为 0
Data Sequence Number,始终为 0
目标 PAN,始终为 0xff
目标地址,这里为广播地址,始终为 0xff
数据包类型
当前节点所在的组,始终为 0
其中,type 字段包含如下 4 种类型:
表 4-3 type 字段的可能值及意义
类型名
DataMSG
OrderMSG
DataACK
OrderACK
值
0x11
0x12
0x13
0x14
意义
数据
命令
数据应答
命令应答
实际应用中,下位机将采集的数据,包括温度、湿度和灯的状态等信息封装在一个数据
包内,其数据部分格式为:
Node ID
Parent
各字段意义如下:
表 4-4 数据包 MSG_DATA 格式
MSG_DATA
Epoch
Voltage
Temp
Humi
Light
表 4-5 数据包 MSG_DATA 各字段意义
字段名
Node ID
字段类型
UINT16
意义
节点号
4
SetDataRate
0x02
n
SetRFPower
0x03
n=0~255
设置节点上传数据的速率,n 的单
位为毫秒
设置新的 Radio Frequency Power
4.3 服务端实现
4.3.1 服务端设计思路
程序的基本目的是为了实现串口/Socket 端口数据的互相转换,一方数据一到来,先存
在缓冲区,再立即发送到另一方,涉及到 Linux 下的串口编程、Socket 编程和 POSIX 多线
程编程。
Linux 系统中,设备作为一种特殊的文件管理,设备分为字符设备、块设备文件,串口
编程以往的经验是打开串口文件,如/dev/ttyS0,代表打开 COM1,进行相关数据结构各个
字段的设置,即可用基本的读写函数进行串口的读写。但这样有个缺点,往往人们在刚接触
Linux 串口编程时,经常会因为那些数据结构设置的问题而使效率变低。Windows 下的串口
编程模式,特别是 VC++,对串口编程方法提供了很多解决方法,值得借鉴的是封装。封装
简化了串口编程的工作过程,往往只需做些简单的调用,即可完成很多工作。鉴于此,在网
上找到一个 Linux 串口编程库 libcssl,该库提供了串口的基本接口,为使用者简化了设置的
问题,而且源码开放,利于移植,很适合本文应用。
Linux 下的 Socket 编程,有很多书籍都有介绍,流程也大同小异,但多是一个服务端对
一个客户端,而实际又是需要多客户端连接。本文提出一种多客户端的解决方案,利用 Linux
对多线程的支持,为每个客户端分配一个线程。将服务端程序阻塞在 accept 函数上,当函
数返回时,将连接的客户端标识符作为参数,新建一个处理客户端的线程,传入客户端标识。
使用 POSIX 线程库实现,具有良好的移植性。
程序多线程化了以后,就要充分考虑线程同步的问题,比如一个变量 data 是对所有线
程共享的,那么如果线程 1 有分别两次读取 data 的值,并进行比较,如果第一次读取后,
另外一个线程 2 将 data 的值改写,那么线程 1 再次读取 data 的值,比较的结果将是不等,
而线程 1 的本意是希望这两次读取到的值是相等的。这就需要慎重对共享数据的写入。本文
中串口回调函数写入缓冲区和 Socket 线程读取缓冲区所采用的是将缓冲区锁定后读写,既
在写的时候不能读,在读的时候不能写。为防止当客户端数目多的时候,单个缓冲区的读写
等待造成串口数据的丢失,本文采用多缓冲区的方式来避免这中情况发生。
由于在 Linux 下的 Socket 程序,服务端会因为客户端的异常退出而退出,这种情况若
出现在多客户端的情况下,一个客户端异常退出,导致服务端退出,进而使得其他客户端出
现异常。这是因为在客户端异常退出时,服务端会收到系统的 SIGPIPE 信号,而应用程序
默认对该信号的处理是程序退出。利用 Linux 下的信号屏蔽,在服务端屏蔽 SIGPIPE 信号,
即可避免上述情况发生。
4.3.2 服务端流程图
6
是接收模式,只需开启 sock_deal 线程;如果是控制模式,则多开启 sock_deal_ctrl 线程;如
果是其他模式,则客户端无效,不开启线程。处理完客户端连接后继续等待客户端连接。
其流程图如下:
图 4-2 wait_for_connect 线程流程图
wait_for_connect 实现多线程的关键代码如下:
while(1)
{
memset(mod,0,60);
if((clientfd = accept(servfd, (struct sockaddr *)&client_sockaddr, (socklen_t
*)&sin_size)) == -1) //阻塞地等待客户端连接
continue;
if(errno == EINTR) //多客户端后可能会出现这种错误,应屏蔽掉
perror("accept"); //其他错误则退出线程
pthread_exit(NULL);
{
}
else
{
个连接
recvlen = recv(clientfd, mod, 60, 0); //接收客户端模式的标识
if(-1 == recvlen)//如果接收出错,那么可能客户端退出,应继续等待下一
break;
continue;
{
}
else if(0 == recvlen)//如果收到的数据长度为 0,说明客户端此时异常
{
}
else
{
if(0 == strcmp(mod,"ctrlmod")) //控制模式,
{
pthread_create(&conn_th, NULL, sock_deal_ctrl, &clientfd);
8
serial 线程实现阻塞等待命令及串口事件的关键代码:
while(1)
{
pthread_mutex_lock(&mm);
pthread_cond_wait(&cc, &mm); //每次循环都阻塞在此,由 Socket 接收到命令
数据后解除阻塞
cssl_putdata(ser,recvdat+1,recvdat[0]); //Socket 收到命令数据,由串口转发
cssl_drain(ser);
pthread_mutex_unlock(&mm);
}
4.3.5 串口回调函数
串口回调函数负责将串口缓冲区的数据提取转存至数据缓冲区,供客户端线程读取。
客户端采取等待条件变量的方式阻塞地等待新数据,回调函数在更新完缓冲区后,解除
客户端线程对特定条件变量的阻塞,这样,Socket 每次发送的数据都是新的。
由于是 4 块缓冲区,回调函数每写一次,下次写的位置就后移一个,顺序循环地写入 4
块缓冲区,当当前写入的缓冲区正在读,说明其他缓冲区也已经写入过数据但还未被读取,
则稍做延时等待数据读取完。
其流程图如下:
串口回调函数关键代码如下:
图 4-4 串口回调函数流程图
int cur=b.cw; //取得上次改写的写入位置,为这次的写入位置
while(b.lock[cur]!=0) usleep(1000);//如果当前要写的位置有线程正在读,则等待 1
10
毫秒
return NULL;
int sock;
sock = *id;
int cread=(b.cw-1+BUFBUN)%BUFNUM; //初始化读取位置为写入位置的前一个
printf("sock_deal: dealing with new clienct which was connected, id = %d\n",sock);
if(pthread_detach(pthread_self())) //设置线程分离
for(;;)
{
pthread_mutex_lock(&m);
while(b.cw==cread) usleep(1000); //如果缓冲区正在写,则等待
pthread_cond_wait(&c, &m);
b.lock[cread]++; //读取标识加 1,非 0 说明有线程正在读取该缓冲区
if((b.data[cread][0]+1) != send(sock,b.data[cread],b.data[cread][0]+1,0))
{
printf("send error\n"); //如果发送的实际长度和要求的不一样,说明客
户端异常退出,则客户端数目减 1,退出线程
sock_count--;
break;
}
b.lock[cread]--; //读取结束,读取标识减 1
cread=(cread+1)%BUFNUM; //读取位置后移
pthread_mutex_unlock(&m);
}
b.lock[cread]--; 客户端异常退出时的处理,
if(b.lock[cread]==0)
pthread_mutex_unlock(&m);
return NULL;
cread=(cread+1)%BUFNUM;
}
4.3.7 sock_deal_ctrl 线程
sock_deal_ctrl 线程也由 wait_for_connect 线程创建,在客户端模式选择控制模式时,服
务端为其开启 sock_deal_ctrl 线程和 sock_deal 线程,
sock_deal_ctrl 线程将自己设置成分离后,阻塞在接收数据,接收到数据后,判断接收
是否成功。若不成功,则继续接收。若接收到的数据长度为 0,说明是客户端异常退出,则
退出线程。接收成功时判断接收的数据是否是客户端退出消息,是则退出线程,否则将接收
到的数据传到命令的缓冲区,然后解除 serial 的阻塞,继续接收数据。
其流程图如下:
12
据
}
memcpy(recvdat,buf,60);
pthread_cond_broadcast(&cc);
pthread_mutex_unlock(&mm);
}
}
return NULL;
5 客户端
5.1 客户端思路
客户端是同服务端配套的,服务端实现串口数据向 Socket 端口的转发,客户端要能接
收到转发的数据。服务端要实现 Socket 端口向串口转发数据控制节点行为,客户端要能发
送正确控制命令。
客户端分为两种模式:接收模式和控制模式。为方便以后的扩展,客户端还可以选择将
数据存储到数据库中。
利用 MFC 编程,很容易实现这些操作。设计对话框程序,连接时设置服务器地址,端
口号,选择客户端类型,是否写数据库,写入的数据库类型。连接成功后,Socket 接收到数
据后解析提取出节点号、父节点号和温度信息,用列表显示出来。
客户端通过可视化界面,实现对节点发送命令控制节点。程序根据用户的选择,自动生
成命令数据并计算校验,发送给服务端,由服务端转发。
客户端使用接收线程接收数据,接收到数据后发送更新消息,由更新消息处理函数负责
更新列表、存储数据等。
5.2 客户端实现
5.2.1 客户端主界面
客户端主界面包括一个菜单、一个节点设置按钮、一个列表。菜单包括退出、连接设置
和断开连接,节点设置按钮只在客户端为控制模式时才可用。界面如下所示:
14