为什么叫GPRSServer?因为这个模块是和下层GPRS硬件模块通讯所用。
在这个模块中我们将和GPRS通讯的所有细节全部封装到wcomm_dll.dll动态连接库中。
这个动态链接库负责和下层具体通讯,包括UDP包的封装,下层通讯队列的维护和一些扩展功能等等。
具体代码:
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Menus, ComCtrls, ToolWin, StdCtrls, ImgList, ExtCtrls, NMUDP,winsock;
const MAX_RECEIVE_BUF=1024; //最大接收缓冲区
const MAX_SEND_BUF=1024; //最大发送缓冲区
const gprs_dll='wcomm_dll.dll';
const gprs_smm='gprs_smm.dll';
const misc='misc.dll';
type
// 用户注册信息结构
gprs_user_info=record
m_userid:array[1..12] of char; //终端模块号码
m_sin_addr:Cardinal; //终端模块进入Internet的代理主机IP地址
m_sin_port:word; //终端模块进入Internet的代理主机IP端口
m_local_addr:Cardinal; //终端模块在移动网内IP地址
m_local_port:word; //终端模块在移动网内IP端口
m_logon_date:array[1..20] of char; //终端模块登录时间
m_update_time:array[1..20] of char; //终端模块更新信息时间
m_status:byte; //终端模块状态, 1 在线 0 不在线
//m_pid:array[1..12] of char;
end;
type
//用户数据结构
data_record=record
m_userid:array[1..12] of char;
m_recv_date:array[1..20] of char;
m_data_buf:array[1..MAX_RECEIVE_BUF] of char;
m_data_len:word;
m_data_type:byte;
end;
type
Arr12 = array[1..12] of Char;
Arr16 = array[1..16] of Char;
Arr256 = array[1..256] of Char;
Arr512 = array[1..512] of Char;
Arr1024 = array[1..1024] of Char;
//下面定义的时函数类型,用于指向动态库中的函数
function start_gprs_server(a:HWND;b:Cardinal;c:Integer;d:PChar):Integer;stdcall;
function start_net_service(a:HWND;b:Cardinal;c:Integer;d:PChar):Integer;stdcall;
function do_read_proc(var a:data_record;b:PChar;c:boolean):Integer;stdcall;
procedure cancel_read_block();stdcall;
function stop_gprs_server(a:PChar):Integer;stdcall;
function stop_net_service(a:PChar):Integer;stdcall;
function do_close_all_user(a:PChar):Integer;stdcall;
function do_send_user_data(a:PChar;b:PChar;c:Cardinal;d:PChar):Integer;stdcall;
function get_user_at(a:Cardinal;var b:gprs_user_info):Integer;stdcall;
function get_max_user_amount:Cardinal;stdcall;
function do_close_one_user(a:PChar;b:PChar):Integer;stdcall;
function SetWorkMode(nWorkMode: integer): integer; stdcall;
function KillProcess(a:PChar):Integer;stdcall;
function DisConnectRas(a:PChar):Integer;stdcall;
function GetConnEntryName(a:PChar):Integer;stdcall;
//The Following function is SMM
//int SMMInit(char *,int,char *,DCB *,char *,char *,int);
function SMMInit(var a:Arr16;b:Integer;var c:Arr16;var d:DCB;var e:Arr12;var f:Arr12;g:Integer):Integer;stdcall;
function SMMFree:Integer;stdcall;
function SMMSetting(var a:Arr12;b:Integer):Integer;stdcall;
function MakeDTUOnLine(a:Integer;var b:Arr12):Integer;stdcall;
function SendDataToDTUBySM(var a:Arr12;var b:Arr256;c:Integer;d:Integer):Integer;stdcall;
implementation
//gprs_dll
function start_gprs_server; external gprs_dll name 'start_gprs_server';
function start_net_service; external gprs_dll name 'start_net_service';
function do_read_proc; external gprs_dll name 'do_read_proc';
procedure cancel_read_block; external gprs_dll name 'cancel_read_block';
function stop_gprs_server; external gprs_dll name 'stop_gprs_server';
function stop_net_service; external gprs_dll name 'stop_net_service';
function do_close_all_user;external gprs_dll name 'do_close_all_user';
function do_send_user_data;external gprs_dll name 'do_send_user_data';
function get_user_at;external gprs_dll name 'get_user_at';
function get_max_user_amount;external gprs_dll name 'get_max_user_amount';
function do_close_one_user;external gprs_dll name 'do_close_one_user';
function SetWorkMode;external gprs_dll name 'SetWorkMode';
//gprs_smm
function SMMInit;external gprs_smm name 'SMMInit';
function SMMFree;external gprs_smm name 'SMMFree';
function SMMSetting;external gprs_smm name 'SMMSetting';
function MakeDTUOnLine;external gprs_smm name 'MakeDTUOnLine';
function SendDataToDTUBySM;external gprs_smm name 'SendDataToDTUBySM';
function KillProcess;external misc name 'KillProcess';
function DisConnectRas;external misc name 'DisConnectRas';
function GetConnEntryName;external misc name 'GetConnEntryName';
end.
其他所用的一些动态链接库我们在这个项目中用不到。在这里我需要你们自己熟悉一下静态调用动态链接库的方法。调用动态链接库有两种方法,静态调用和动态调用,以上就是静态调用的方法。通过封装调用我们在以后的开发中就可以直接用这些函数了。
当下层硬件发送数据给我们的模块的时候我们该如何处理呢?
答案是:我们会用消息机制来处理。
处理过程为:下层数据通过UDP数据包发送到wcomm_dll.dll,我的wcomm_dll.dll就会去解析UDP数据包,并且要维护一下数据列表。最后向外广播约定好的windows消息,告诉外层程序数据已经接受到了,来取吧。
当然我的外层程序(就是我们马上要讲到的GPRSServer模块)收到了这个特定的windows消息之后,就会通过
do_read_proc函数来读取数据。do_read_proc是在wcomm_dll.dll中定义的,看看上面的代码,是不是已经定义了。
我们来看看GPRSServer是如何处理Windows消息的:
这个是消息的定义,再来看看这个消息处理函数具体做了什么工作:
var dr:data_record;
p:PChar;
bAnswer:Boolean;
begin
try
GetMem(p,1024);
if ckAnswer.Checked then
bAnswer:=true
else
bAnswer:=false;
if (do_read_proc(dr,p,bAnswer)>=0) then
begin
if dr.m_data_len=0 then
PollUserTable
else
ProcessData(dr);
end;
finally
FreeMem(p);
end;
end;
再次提醒一边,我在设计辅导的时候是不讲程序的语法的,你们要有什么不懂的地方,去查找资料!
在以上的这个函数中我们比较关心的是ProcessData()这个函数,其他函数都是一些修饰和保护等等。来看看ProcessData()这个函数的实现:
var i,j:Integer;
str,S:String;
re:Pchar;
reLong,ReLongTemp:integer;
label H1;
begin
str:=dr.m_userid+'---'+dr.m_recv_date+'---'+IntToStr(dr.m_data_len);
for i:=1 to Length(str) do
if str[i]=#0 then
str[i]:=' ';
mmDataWnd.Lines.Add(str);
mmDataWnd.Lines.Add('下位机-->通讯服务器:'+dr.m_data_buf);
/////////////////////////////////
try
re:=nil;
ReLongTemp:=0;
if dr.m_data_len>G_cachLeng then //对缓冲区进行保护
begin
fillmemory(@G_cach,G_cachLeng,$FF);
mmDataWnd.Lines.Add('缓冲区溢出!');
goto H1;
end;
re:=@dr.m_data_buf;
reLong:=dr.m_data_len;
s:='';
for j:=0 to reLong-1 do
begin
S:=S+inttohex(ord((re+j)^),2)+' ';
end;
mmDataWnd.Lines.Add('下位机-->通讯服务器:'+S+#13+#10);
if G_cachPose>=G_cachleng-1 then
G_cachPose:=0;
if reLong>(G_cachleng-G_cachPose)then
G_cachPose:=0;
move(re^,G_cach[G_cachPose],reLong);
G_cachPose:=G_cachPose+reLong;
G_reLong:=reLong;
Doit;
H1:
except
on EAccessViolation do
exit;
end;
end;
这个函数是干什么的?这个函数其实主要功能就像一个漏斗。将不断接受到的数据流存入一个叫G_cach的缓存中以供后续操作。这步很必要,因为作为UDP数据包来说你不能保证一连串的数据的完整性,而且在无限传输过程中这种不稳定性尤其突出,同学们可以用无线电传输字节流来试试,看看整个数据的真确率能不能达到70%。当缓存好了之后我们要作的事就是Doit这个函数。
看看Doit是如何实现的:
var
i,DDL:integer;
label H1;
begin
try
i:=0;
while i<G_cachLeng-1 do
begin
if (ord(G_cach[i])=$10)and(ord(G_cach[i+4])=$16) then
begin
CutReceiveData(@G_cach,G_cachPose);
fillmemory(@G_cach[i],G_cachPose,$FF);
G_cachPose:=0;
i:=i+4;
goto H1;
end else
if (ord(G_cach[i])=$68)and (ord(G_cach[i+3])=$68)and((ord(G_cach[i+ord(G_cach[i+1])+5])=$16)or(ord(G_cach[i+ord(G_cach[i+1])+5])=0) )then
begin
CutReceiveData(@G_cach,G_cachPose);
fillmemory(@G_cach[i],G_cachPose,$FF);
DDL:= ord(G_cach[i+1]);
i:=i+DDL+5;
G_cachPose:=0;
goto H1;
end;
inc(i);
end;
H1:
except
on EAccessViolation do begin
abort;
end;
end;
application.ProcessMessages;
end;
原来Doit 主要的功能是在那个G_cache里寻找完整的数据包,如果找到了就执行CutReceiveData这个函数进行真正的拆包分析。当然这个Doit是用线程来实现的。你们可能要熟悉一下线程和进程的概念。
那么CutReceiveData是作什么的呢?它是一个大的函数,主要是将101通信规约,645通信规约等等规约解析成我们需要的原始数据,比如说电量,电压,电流等等。这里涉及到商业机密在此不表述了。
当我们把这些原始数据都得到之后,就将这些数据按照与Java后台的约定以一定形式打包,发送给ClientIn模块。由ClientIn模块处理之后发送给Java后台。如果你们需要这部分的代码,发邮件给我。告诉我你们的姓名和班级。