随着中国申奥成功,我国许多大中城市都在建设自己的体育中心(或体育场)。应当说这些场和馆的许多设计和设施,起点都是很高的。比如新落成的四万座“烟台中心体育场”,不仅有自己独立的局域网、大屏幕显示,而且有先进的“田、径”比赛记分的裁判系统。这些硬件设施,施工时往往是各大公司各自完成,所以整个工程完工后,如何使这些硬件用软件联系起来,使之协调工作,这就是体育中心自己的事了。本文的作者,自始至终参入了这项工作,略有所得,也走过一些弯路,现把成功的东西写下来,愿与同行交流。
烟台中心体育场的硬件系统框图如下:
记分裁判系统 局域网 大屏幕显示系统
(由深圳菲普公司提供) (由广东信息工程施工) (由北京利亚德施工)
图 一
图一中,记分裁判系统、大屏幕显示系统的软件部分都是自己本公司开发的。(记分裁判系统平台是WindowsNT;大屏幕显示系统平台是Windows98),两者连接的中心环节硬件则是局域网,软件需自己编写。
本文作者用Delphi5在Windows2K平台编写,已调试通过。具体做法如下:
1.按图2设计界面。
图 二
图二中使用了Delphi中两个重要组件,ServerSoket 和ClientSoket 。这两个组件位于VCL面板的 Internet 页上。它们是 Delphi 为实现 TCP/IP 协议,封装了 Windows 提供的一组名为 WinSock 的 API函数。所以设计者不必知道TCP/IP 的细节,只要简单的说明:
Host - 服务器的域名;
Address - 服务器的IP地址;(在运行时确定)
Port - 指定应用程序使用的端口;(在设计时确定)
Service - 服务器名称字符串,
就可以了。
图二界面全部程序如下:
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Menus, StdCtrls, Buttons, ScktComp, ExtCtrls, ComCtrls;
type
TChatForm = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Exit1: TMenuItem;
FileConnectItem: TMenuItem;
FileListenItem: TMenuItem;
StatusBar1: TStatusBar;
Bevel1: TBevel;
Panel1: TPanel;
Memo1: TMemo;
Memo2: TMemo;
N1: TMenuItem;
SpeedButton1: TSpeedButton;
Disconnect1: TMenuItem;
ServerSocket: TServerSocket;
ClientSocket: TClientSocket;
BitBtn1: TBitBtn;
procedure FileListenItemClick(Sender: TObject);
procedure FileConnectItemClick(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure FormCreate(Sender: TObject);
procedure ServerSocketError(Sender: TObject; Number: Smallint;
var Description: string; Scode: Integer; const Source,
HelpFile: string; HelpContext: Integer; var CancelDisplay: Wordbool);
procedure Disconnect1Click(Sender: TObject);
procedure ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketAccept(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure BitBtn1Click(Sender: TObject);
protected
IsServer: Boolean;
end;
var
ChatForm: TChatForm;
Server: String;
implementation
uses Unit1;
{$R *.DFM}
procedure TChatForm.FileListenItemClick(Sender: TObject);
begin
FileListenItem.Checked := not FileListenItem.Checked;
if FileListenItem.Checked then
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
Statusbar1.Panels[0].Text := 'Listening...';
end
else
begin
if ServerSocket.Active then
ServerSocket.Active := False;
Statusbar1.Panels[0].Text := '';
end;
end;
procedure TChatForm.FileConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Computer to connect to', 'Address Name:', Server) then
if Length(Server) > 0 then
with ClientSocket do
begin
Host := Server;
Active := True;
FileListenItem.Checked := False;
end;
end;
procedure TChatForm.Exit1Click(Sender: TObject);
begin
ServerSocket.Close;
ClientSocket.Close;
Close;
end;
procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
if IsServer then
ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1.Lines.Count - 1])
else
ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count - 1]);
end;
procedure TChatForm.FormCreate(Sender: TObject);
begin
FileListenItemClick(nil);
end;
procedure TChatForm.ServerSocketError(Sender: TObject; Number: Smallint;
var Description: string; Scode: Integer; const Source, HelpFile: string;
HelpContext: Integer; var CancelDisplay: Wordbool);
begin
ShowMessage(Description);
end;
procedure TChatForm.Disconnect1Click(Sender: TObject);
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
Statusbar1.Panels[0].Text := 'Listening...';
end;
procedure TChatForm.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Statusbar1.Panels[0].Text := 'Connected to: ' + Socket.RemoteHost;
end;
procedure TChatForm.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
procedure TChatForm.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
procedure TChatForm.ServerSocketAccept(Sender: TObject;
Socket: TCustomWinSocket);
begin
IsServer := True;
Statusbar1.Panels[0].Text := 'Connected to: ' + Socket.RemoteAddress;
end;
procedure TChatForm.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Clear;
end;
procedure TChatForm.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
FileListenItemClick(nil);
end;
procedure TChatForm.ClientSocketError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
Memo2.Lines.Add('Error connecting to : ' + Server);
ErrorCode := 0;
end;
procedure TChatForm.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Statusbar1.Panels[0].Text := 'Listening...';
end;
procedure TChatForm.BitBtn1Click(Sender: TObject);
begin
form1.show;
end;
end.
2.代码重点部分说明
ServerSocket 和 ClientSocket 有一个共同的属性 Active,它用来控制组件是否处于工作状态。当ServerSocket 设置为True 时,服务器端开始监听是否有客户要与之连接。当ClintSocket 设置为True时,客户端准备与服务器端连接。在上述代码中:
procedure TChatForm.FileListenItemClick(Sender: TObject);
begin
FileListenItem.Checked := not FileListenItem.Checked;
if FileListenItem.Checked then
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
Statusbar1.Panels[0].Text := 'Listening...';
end
else
begin
if ServerSocket.Active then
ServerSocket.Active := False;
Statusbar1.Panels[0].Text := '';
end;
end;
是这样的:
如果Listen菜单有复选标记,用户单击它后取消复选标记,关闭服务器,打开客户端,将这个窗口作为客户程序来运行。如果Listen菜单没有复选标记,用户单击它后标上复选标记,打开服务器,关闭客户端,将这个窗口作为服务器程序来运行。同时界面状态栏的文字,提醒用户处于的状态。
以下代码:
procedure TChatForm.FileConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Computer to connect to', 'Address Name:', Server) then
if Length(Server) > 0 then
with ClientSocket do
begin
Host := Server;
Active := True;
FileListenItem.Checked := False;
end;
end;
的作用是这样的:
这个菜单项被触发时,首先关闭 ClientSocket 。然后调用 InputQuery 函数,询问用户要连接服务器的IP地址,用户正确输入后,将新的IP值付给 ClientSocket 的 Address Name 属性,同时设置 Active 为 True ,让它用新的IP地址与指定的服务器连接。
3.图二界面的作用
记分裁判系统送出的比赛数据,是按标准 IP/TCP 格式-“ START,……,END ”送出的。下面是一组具体的例子:
START,男子组100米决赛1组,4,1,4,90029,刘子雨,,11.22,2,6,90028,刘英力,,11.43,3,3,90032,刘英男,,11.49,4,5,90019,周淑华,,11.57,END
上面各组数据是用“,”分开的,它们是这样约定的:
第一个分号前,“ START ” 格式头
第二个分号前,“ 男子组100米决赛1组 ” 组别
第三个分号前,“ 4 ” 参加人数
第四个分号前,“ 1 ” 第一名
第五个分号前,“ 4 ” 第一名所在跑道
第六个分号前,“ 90029 ” 第一名编号
第七个分号前,“ 刘子雨 ” 第一名姓名
第九个分号前,“ 11.22 ” 第一名成绩 (注:前面夹一分号)
第十个分号前,“ 6 ” 第二名所在跑道
.
.
.
(共四组这样的数据)
END 格式尾
按装在大屏幕主机上“ 界面二”,用菜单项 “ Listen ” 置为监听状态,做服务器端;按装在裁判系统主机上“ 界面二”,用菜单项 “Listen ”置为客户机状态,做客户端。并随时准备与“ 连接键”联用,给出 IP 地址,与服务器连接。
界面二中 Memo2 的是为接受裁判系统送来的数据而设计的。它的WordWrap 属性为 False 。这样它接受到数据,无论多长,它都是一行数据。这为以后从Memo2剥离数据打下基础。
至于界面二中的其他组件,读者看一下程序,不需赘述自明。
4.剥离数据的中间界面
前面我们说过,裁判系统送来的是一组“ START …… END ”数据,它里面除了我们有用的数据外,还有一些格式分界用的代码,象“START”;“END”;“,” 等等。我们必须从“ START …… END ”数据中,分离出我们所要显示的数据。而不必理会不需显示的数据。下面“图 三”即是为这个目的而设计的。
图 三
按一下“模拟”键(这是为调机设计的),会触发 OpenDaiglog1 对话框,询问输入“ START …… END ”数据(本文电子版提供两组这样的数据,调试者可事先复制到机器的一个目录中)。当完成OpenDaiglog1 对话框操作后,会在图二的 Memo2 中出现这组数据。
当再按“显示”键时,程序会要分两步走:
★:第一步,将图三显示成“图四”
图 四
从上图中可以看出,Memo2 的每一行是以“,”为“断点”,阶梯式显示“ START …… END ”内容。Memo3 的每一行,是对应 Memo2 最后一个“,”所在的位置(左第一个为“1”)。Memo4 是已剥离出带“,”的数据。
完成以上功能,只需以下代码:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
s,s1,s2:string;
i,j,m,n,k,g:integer;
begin
s:=chatform.memo2.Text ;
j:=length(s);
for i:=1 to j do
begin
if s[i]=',' then
begin
s1:=copy(s,-i,i);
j:=length(s1);
memo2.Lines .add(s1);
memo3.Lines .add(inttostr(j));
end ;
end ;
for g:=0 to memo3.Lines.Count-2 do
begin
m:= strtoint(trim(memo3.lines[g]));
n:= strtoint(trim(memo3.lines[g+1]));
k:=n-m;
memo4.Lines.add(copy(memo2.Lines [g+1],m,k)) ;
end;
为了最终将比赛成绩,在Memo2、Memo3、Memo4 上覆盖了一个 panel1 如图五 (运行是时,实际看不到Memo2、Memo3、Memo4的内容)。
图 五
★ :第二步,将图五显示成图六。
图 六
以上两步的完整程序如下:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Buttons;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
Edit1: TEdit;
Memo4: TMemo;
Memo2: TMemo;
Memo3: TMemo;
Panel1: TPanel;
Label9: TLabel;
Label2: TLabel;
Label30: TLabel;
Label3: TLabel;
Label14: TLabel;
Label10: TLabel;
Label11: TLabel;
Label12: TLabel;
Label13: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel;
Label19: TLabel;
Label15: TLabel;
Label16: TLabel;
Label17: TLabel;
Label18: TLabel;
Label24: TLabel;
Label23: TLabel;
Label22: TLabel;
Label27: TLabel;
Label26: TLabel;
Label25: TLabel;
Label20: TLabel;
Label21: TLabel;
Label28: TLabel;
Label29: TLabel;
OpenDialog1: TOpenDialog;
Button2: TButton;
Label1: TLabel;
Label31: TLabel;
Label32: TLabel;
procedure BitBtn1Click(Sender: TObject);
// procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Edit1DblClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses main;
{$R *.DFM}
procedure TForm1.BitBtn1Click(Sender: TObject);
var
s,s1,s2:string;
i,j,m,n,k,g:integer;
begin
s:=chatform.memo2.Text ;
j:=length(s);
for i:=1 to j do
begin
if s[i]=',' then
begin
s1:=copy(s,-i,i);
j:=length(s1);
memo2.Lines .add(s1);
memo3.Lines .add(inttostr(j));
end ;
end ;
for g:=0 to memo3.Lines.Count-2 do
begin
m:= strtoint(trim(memo3.lines[g]));
n:= strtoint(trim(memo3.lines[g+1]));
k:=n-m;
memo4.Lines.add(copy(memo2.Lines [g+1],m,k)) ;
end;
label2.Caption :=copy (memo4.Lines[2],2,60); //类别
label3.Caption :=copy (memo4.Lines[3],2,2); //人数
//label4.Caption :=copy (memo4.Lines[4],2,2); //名次
label5.Caption :=copy (memo4.Lines[5],2,2); //跑道 -1
label6.Caption :=copy (memo4.Lines[6],2,5); //编号 -1
label7.Caption :=copy (memo4.Lines[7],2,6); //姓名 -1
label8.Caption :=copy (memo4.Lines[9],2,6); //成绩 -1
label15.Caption :=copy (memo4.Lines[11],2,2); //跑道 -2
label16.Caption :=copy (memo4.Lines[12],2,5); //编号 -2
label17.Caption :=copy (memo4.Lines[13],2,6); //姓名 -2
label18.Caption :=copy (memo4.Lines[15],2,6); //成绩 -2
label21.Caption :=copy (memo4.Lines[17],2,2); //跑道 -3
label22.Caption :=copy (memo4.Lines[18],2,5); //编号 -3
label23.Caption :=copy (memo4.Lines[19],2,6); //姓名 -3
label24.Caption :=copy (memo4.Lines[21],2,6); //成绩 -3
label26.Caption :=copy (memo4.Lines[23],2,2); //跑道 -4
label27.Caption :=copy (memo4.Lines[24],2,5); //编号 -4
label28.Caption :=copy (memo4.Lines[25],2,6); //姓名 -4
label29.Caption :=copy (memo4.Lines[27],2,6); //成绩 -4
panel1.Visible :=true;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
F: Textfile;
S:string;
begin
memo2.Lines.Clear ;
memo2.Lines.Add('0');
memo3.Lines.Clear ;
memo3.Lines.Add('0');
memo4.Lines.Clear ;
memo4.Lines.Add('0');
OpenDialog1.Execute;
AssignFile(F, OpenDialog1.FileName);
Reset(F);
Readln(F, S); { Read the first line out of the file }
chatform.memo2.Text := S; { Put string in a TEdit control }
CloseFile(F);
end;
procedure TForm1.Edit1DblClick(Sender: TObject);
begin
label1.Caption :=edit1.Text ;
end;
end.
procedure TForm1.Button1Click(Sender: TObject);
var
F: Textfile;
S:string;
begin
memo2.Lines.Clear ;
memo2.Lines.Add('0');
memo3.Lines.Clear ;
memo3.Lines.Add('0');
memo4.Lines.Clear ;
memo4.Lines.Add('0');
OpenDialog1.Execute;
AssignFile(F, OpenDialog1.FileName);
Reset(F);
Readln(F, S); { Read the first line out of the file }
form1.memo2.Text := S; { Put string in a TEdit control }
CloseFile(F);
end;
end. 最后应当说明的是,我们所设计的界面二,不但可以显示体育比赛成绩,还可以在比赛中,随时与有联系的其他客户端通信,这些读者稍看一下程序,很容易看出。
|