unit MainUnit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent, IdUDPBase,
IdUDPServer, IdSystatUDPServer, IdGlobal, IdSocketHandle, Vcl.StdCtrls,
IdUDPClient, Vcl.ComCtrls,StrUtils,System.Types, Vcl.ExtCtrls, Data.DB,
Data.Win.ADODB,MsListObject,DateUtils;
type
TfrmUDPServer = class(TForm)
udpserver: TIdUDPServer;
mmoData: TMemo;
btnClear: TButton;
lv1: TListView;
tmrheart: TTimer;
qry1: TADOQuery;
btnFree: TButton;
procedure FormCreate(Sender: TObject);
procedure udpserverUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
procedure FormDestroy(Sender: TObject);
procedure btnClearClick(Sender: TObject);
procedure tmrheartTimer(Sender: TObject);
procedure btnFreeClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure AddListView(AName,AIp,APort:string);
end;
type
TUDPList =Class(TObject) //保存客户端对象
fIp : String; //ip
fPort : Integer; //port
fTime : TDateTime; //时间
fName : String; //用户名
fPwd : String; //密码
private
public
procedure UpdateTime; //用于更新客户端的连接时间
{判断是否心跳超时}
function IsLinkTimeOut: Boolean;
End;
var
frmUDPServer : TfrmUDPServer;
UdpList : TMsObjectList; //这个类为个人重写的TStringlist类。
implementation
// 心跳包。登录。消息转发。
{$R *.dfm}
uses IniFiles,DMUnit;
//释放队列对象
procedure TfrmUDPServer.btnFreeClick(Sender: TObject);
begin
UDpList.Destroy;
lv1.Items.Clear;
mmoData.Clear;
UdpList := TMsObjectList.Create;
end;
//清空mmo中的数据 (mmo中保存登陆数据)
procedure TfrmUDPServer.btnClearClick(Sender: TObject);
begin
mmoData.Clear;
end;
procedure TfrmUDPServer.FormCreate(Sender: TObject);
begin
try
//绑定端口
udpServer.DefaultPort := 86;
//激活
udpServer.Active := true;
//创建队列
UdpList := TMsObjectList.Create;
except
on e:exception do
WriteLog(‘绑定端口出错’+e.Message);
end;
end;
//用户登录 此处为了线程安全而做的操作(当多个用户登录时不会出现问题)
function CheckLogin(const UserName,Password: string): Boolean;
begin
Result := dbFindTabelValue(
‘SELECT count(1) as c from tb_user where username =”’+UserName+”’ and password = ”’+Password+”’ ‘,
False,
‘c’
);
end;
//接收客户端发送的信息,这个方法是最重要的
//AThread 为线程对象主要是处理多个客户端发送数据 AData就是客户端所发送的数据
//ABindIng 为Socket对象,里面包含了客户端的登录Ip 与端口
procedure TfrmUDPServer.udpserverUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
Ip,Port : String; //IP ,Port
Udp,RUdp : TUDPList; //创建用户
SendUdp : TUDPList; //发送语音者
i : Integer;
index : Integer;
Data : TIdBytes;
UserData : String;
ArrayData : TArray;
Name : String;
Pwd : string;
begin
Ip := ABinding.PeerIP;
Port := IntToStr(ABinding.PeerPort);
OutputDebugString(PChar(‘数据长度’+Length(AData).ToString));
writeLog(‘数据长度接收开始’+ IntTOStr( Length(AData)));
//接收数据头,判断是什么操作,这个需要和客户端对好协议
**//此处的数据头为 0 表示登录,后面是用户名与用户密码
//1 表示发送语音消息 2 表示心跳 心跳用于检测客户端是否还存在**
case BytesToInt32(AData) of
0 : begin //登录
AppendBytes(Data,AData,4,Length(AData)-4);
//用户登录信息
UserData := BytesToString(Data,IndyTextEncoding_UTF8);
ArrayData := UserData.Split([‘|’]);
if Length(ArrayData)>1 then begin
Name := ArrayData[0];
Pwd := ArrayData[1];
{验证用户名和密码}
if CheckLogin(Name,Pwd) then begin
Udp := UdpList.IndexOfObject(Ip+Port);
if Assigned(Udp) and (Udp.fName = Name) then begin
//登录成功返回数据
Data := ToBytes(Integer(0));
udpServer.SendBuffer(Udp.fIp,Udp.fPort,TIdIPVersion.Id_IPv4,Data);
WriteLog('用户已经存在队列返回0表示登录');
Udp.UpdateTime;
Exit;
end;
mmoData.Lines.Add('登录用户数据'+UserData);
Udp := TUDpList.Create;
Udp.fIp := Ip;
Udp.fPort := Port.ToInteger;
Udp.UpdateTime;
UDP.fName := Name;
UDP.fPwd := Pwd;
UdpList.AddObject(Ip+Port,Udp);
AddListView(Name,Ip,Port);
try
//登录成功返回数据
Data := ToBytes(Integer(0));
udpServer.SendBuffer(Udp.fIp,Udp.fPort,TIdIPVersion.Id_IPv4,Data);
WriteLog('登录成功'+'IP:'+Udp.fIp +'端口:'+IntTOstr(Udp.fPort));
except
on e:exception do
WriteLog('系统错误'+e.Message);
end;
end else begin
Data := ToBytes(Integer(-1));
udpServer.SendBuffer(Ip,Port.ToInteger,TIdIPVersion.Id_IPv4,Data);
writeLog('用户名或者密码错误'+'用户名='+Name+'密码='+Pwd);
mmoData.Lines.Add('用户名或者密码错误='+'用户名='+Name +'密码=' + Pwd);
end;
end;
end;
1: begin //发送语音
SendUdp :=UdpList.IndexOfObject(IP+Port);
if Assigned(SendUdp) = False then begin
udpServer.SendBuffer(Ip,Port.ToInteger,TIdIPVersion.Id_IPv4,ToBytes(Integer(-1)));
writeLog('非法用户!'+IP+Port);
exit;
end;
SendUdp.UpdateTime;
AppendBytes(Data,AData,4,Length(AData)-4);
for I := 0 to UdpList.Count-1 do begin
RUdp := UdpList.Objects[i];
if Assigned(RUdp) then begin
if (RUdp.fName<>SendUDp.fName) then begin
udpServer.SendBuffer(RUdp.fIp,RUdp.fPort,TIdIPVersion.Id_IPv4,Data);
writeLog('发送数据长度'+ IntTOStr( Length(Data)));
WriteLog('接收语音--'+'用户IP:'+RUdp.fIp+'用户端口:'+IntToStr(RUdp.fPort));
end else
WriteLog('发送语音--'+'用户IP:'+RUdp.fIp+'用户端口:'+IntToStr(RUdp.fPort));
end;
end;
end;
2: begin //发送心跳
SendUdp :=UdpList.IndexOfObject(IP+Port);
if Assigned(SendUdp) = False then exit;
SendUdp.UpdateTime;
Data := ToBytes(Integer(2));
udpServer.SendBuffer(Ip,Port.ToInteger,TIdIPVersion.Id_IPv4,Data);
WriteLog('回传心跳数据'+'IP:'+Ip +'端口:'+Port);
end;
end;
end;
procedure TfrmUDPServer.FormDestroy(Sender: TObject);
begin
udpServer.Active := false;
UdpList.Destroy;
end;
//定时器 从队列中删除客户端对象
procedure TfrmUDPServer.tmrheartTimer(Sender: TObject);
var
I : integer;
begin
UdpList.Lock;
try
for I := UdpList.Count -1 downto 0 do
if UdpList.Objects[I].IsLinkTimeOut then begin
UdpList.FreeAndNilObject(I);
end;
finally
UdpList.UnLock;
end;
end;
//添加用户登录数据在TListView列表中
procedure TfrmUDPServer.AddListView(AName,AIp,APort:string);
var
item : TListItem;
begin
item := lv1.Items.Add;
item.Caption := AName;
item.SubItems.Add(AIP);
item.SubItems.Add(APort);
item.SubItems.Add(FormatDateTime(‘c’,Now))
end;
{ TUDPList }
//此方法用于判断心跳数据超过10分钟都没有接收到了,此时删除用户
function TUDPList.IsLinkTimeOut: Boolean;
begin
Result := MinutesBetween(Now,fTime) > 10;
end;
//每次用户发送数据都更新最新时间
procedure TUDPList.UpdateTime;
begin
fTime := Now;
end;
end.