以后的几篇我将介绍一下GPRSServer模块。
为什么叫GPRSServer?因为这个模块是和下层GPRS硬件模块通讯所用。
在这个模块中我们将和GPRS通讯的所有细节全部封装到wcomm_dll.dll动态连接库中。
这个动态链接库负责和下层具体通讯,包括UDP包的封装,下层通讯队列的维护和一些扩展功能等等。
具体代码:
unit Unit_dll;

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消息的:

 procedure ProcessMessage(var Msg:TMessage);message GPRSRECVMESS;

这个是消息的定义,再来看看这个消息处理函数具体做了什么工作:

procedure TFormGPRSServer.ProcessMessage(var Msg:TMessage);
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)>=0then
      begin
        
if dr.m_data_len=0 then
            PollUserTable
        
else
            ProcessData(dr);
      
end;
  
finally
    FreeMem(p);
     
end;
end;

再次提醒一边,我在设计辅导的时候是不讲程序的语法的,你们要有什么不懂的地方,去查找资料!
在以上的这个函数中我们比较关心的是ProcessData()这个函数,其他函数都是一些修饰和保护等等。来看看ProcessData()这个函数的实现:

procedure TFormGPRSServer.ProcessData(dr:data_record);
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(strdo
      
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是如何实现的:

procedure TFormGPRSServer.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])=$16then
                  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后台。如果你们需要这部分的代码,发邮件给我。告诉我你们的姓名和班级。

posted on 2006-04-24 21:08  coffeeliu  阅读(1418)  评论(6编辑  收藏  举报