Delphi编写事件模型客户端(2)
生活随笔
收集整理的這篇文章主要介紹了
Delphi编写事件模型客户端(2)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
上次寫了事件模型類的定義,今天我來寫一寫如何實現(xiàn)這個類。 首先的兩個函數(shù)我想稍微了解網(wǎng)絡編程的人都會清楚。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
?
procedure WSAStatupSocket; var ? WSData:TWSAData; begin ? if WSAStartup($0202, WSData) <> 0 then ? begin ??? raise Exception.Create('WSAStartup Error.'); ? end; end;?
procedure WSACleanupSocket; begin ? if WSACleanup <> 0 then ? begin ??? raise Exception.Create('WSACleanup Error.'); ? end; end;?
下來是真正類函數(shù)的實現(xiàn)。 procedure TIOEvents.ClearBuffer; var ? FNext:PSendBuffer; begin ? while Assigned(FFirstNode) do ? begin ??? FNext:=FFirstNode.Next; ??? FreeMem(FFirstNode.Buf); ??? FFirstNode.BufLen:=0; ??? Dispose(FFirstNode); ??? Dec(FTotalCount); ??? FFirstNode:=FNext; ? end; ? if not Assigned(FFirstNode) then ? begin ??? FLastNode:=nil; ? end; ? FTotalCount:=0; end; 此函數(shù)用來對發(fā)送隊列中的的數(shù)據(jù)清空,此函數(shù)在釋放網(wǎng)絡的時候使用。 創(chuàng)建和銷毀函數(shù)的定義和實現(xiàn)如下: constructor TIOEvents.Create; begin ? InitializeCriticalSection(FEventCS);?? //定義臨界區(qū) ? FMainIP?????????? :=? '127.0.0.1';????? //定義服務端默認IP和默認端口 ? FMainPort???????? :=? 5500; ? FActive?????????? :=? false; ? FKeepAlive??????? :=? false;???????? //初始化心跳不開啟 ? FKeepTime???????? :=? 1000; ? FSendLen????????? :=? DEFAULT_BUFSIZE;???? //初始化發(fā)送數(shù)據(jù)長度 ? FEventNums??????? :=? 0; end; 在創(chuàng)建函數(shù)中我對一些使用的變量進行了初始化。 在銷毀函數(shù)中我將臨界區(qū)進行釋放。 destructor TIOEvents.Destroy; begin ? DeleteCriticalSection(FEventCS); ? inherited; end;?
以下的兩個函數(shù)是發(fā)送數(shù)據(jù)和接收數(shù)據(jù)中使用的主要函數(shù)。 首先是PostRecv函數(shù),此函數(shù)用于投遞接收。 function TIOEvents.PostRecv: Boolean; var ? Flags,RecvBytes: DWORD; begin ? Result:=true; ? Flags := 0; ? FRecvIOData.DataBuf.len?? := DATA_BUFSIZE; ? ZeroMemory(@FRecvIOData.Buffer, sizeof(@FRecvIOData.Buffer)); ? FRecvIOData.DataBuf.buf?? := @FRecvIOData.Buffer; ? if (WSARecv(FSocket, @(FRecvIOData.DataBuf), 1, @RecvBytes, @Flags, @(FRecvIOData.Overlapped), nil) = SOCKET_ERROR) then ? begin ??? if (WSAGetLastError() <> ERROR_IO_PENDING) then ??? begin ????? Result:=false; ??? end; ? end; end;?
其次是PostSend函數(shù),此函數(shù)是投遞發(fā)送,大家會注意到投遞發(fā)送的時候的代碼和投遞接收數(shù)據(jù)的代碼有些不同,這里的不同主要在于,我們每次投遞發(fā)送的時候,數(shù)據(jù)的大小最大為4K的數(shù)據(jù)。對于比較長的數(shù)據(jù),我會將它放入到發(fā)送隊列中,所以投遞的時候是從發(fā)送隊列中的First數(shù)據(jù),進行投遞,并將First的Next數(shù)據(jù)設置為First。 function TIOEvents.PostSend: Boolean; var ? SendBytes: DWORD; ? FNext:PSendBuffer; begin ? EnterCriticalSection(FEventCS); ? try ??? Result:=true; ??? FillChar(FSendIOData.Buffer,SizeOf(FSendIOData.Buffer),#0); ??? ZeroMemory(@FSendIOData.Overlapped, sizeof(OVERLAPPED)); ??? FNext:=FFirstNode.Next; ??? Move(FFirstNode.Buf^,FSendIOData.Buffer,FFirstNode.BufLen); ??? FSendIOData.BufferLen???? :=? FFirstNode.BufLen; ??? FSendIOData.DataBuf.len?? :=? FFirstNode.BufLen; ??? FSendIOData.DataBuf.buf?? :=? @FSendIOData.Buffer; ??? FSendIOData.Socket??????? :=? FSocket; ??? if (WSASend(FSocket, @(FSendIOData.DataBuf), 1, @SendBytes, 0, @(FSendIOData.Overlapped), nil) = SOCKET_ERROR) then ??? begin ????? if (WSAGetLastError() <> ERROR_IO_PENDING) then ????? begin ??????? Result:=false; ????? end; ??? end; ??? FreeMem(FFirstNode.Buf); ??? Dispose(FFirstNode); ??? FFirstNode:=FNext; ? finally ??? LeaveCriticalSection(FEventCS); ? end; end;?
下來是設置心跳函數(shù),此函數(shù)如果看過我以前BLOG的朋友應該知道如何使用的。見《網(wǎng)絡通信中的心跳機制的實現(xiàn)!》 function TIOEvents.SetKPAlive: Boolean; var ? inKeepAlive,OutKeepAlive:TTCP_KEEPALIVE; ? opt:Integer; ? insize,outsize,outByte:DWORD; begin ? Result? :=true; ? opt???? :=1; ? if setsockopt(FSocket,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then ? begin ??? Exit; ? end; ? inKeepAlive.onoff???????????? :=? 1; ? inKeepAlive.keepalivetime???? :=? FKeepTime; ? inKeepAlive.keepaliveinterval :=? 1; ? insize??????????????????????? :=? sizeof(TTCP_KEEPALIVE); ? outsize?????????????????????? :=? sizeof(TTCP_KEEPALIVE); ? if WSAIoctl(FSocket,SIO_KEEPALIVE_VALS,@inKeepAlive,insize,@outKeepAlive,outsize,@outByte,nil,nil)=SOCKET_ERROR then ? begin ??? Exit; ? end; end;?
上面的PostSend函數(shù)只是一個投遞發(fā)送數(shù)據(jù)的函數(shù),可是如何將發(fā)送的時候放入發(fā)送隊列呢?這個工作主要依靠一下的函數(shù)來實現(xiàn)。?
function TIOEvents.SocketWrite(Data: Pchar; DataLen: Integer):Boolean; var ? iPos:Integer; ? PNode:PSendBuffer; begin ? Result:=true; ? if Assigned(Self) then ? begin ??? if (DataLen<=0) or (FSendLen<DataLen) then ??? begin ????? Result:=false; ????? Exit; ??? end; ??? iPos:=0; ??? while DataLen - iPos > 0 do ??? begin ????? New(PNode); ????? if (DataLen - iPos)>=DATA_BUFSIZE then ????? begin ??????? PNode.BufLen := DATA_BUFSIZE; ??????? GetMem(PNode.Buf, DATA_BUFSIZE); ??????? Move((Data+iPos)^, PNode.Buf^, DATA_BUFSIZE); ??????? PNode.Next := nil; ??????? Inc(iPos,DATA_BUFSIZE); ????? end ????? else ????? begin ??????? PNode^.BufLen := DataLen - iPos; ??????? GetMem(PNode.Buf, DataLen - iPos); ??????? Move((Data+iPos)^, PNode.Buf^, DataLen - iPos); ??????? PNode.Next := nil; ??????? Inc(iPos,DataLen - iPos); ????? end; ????? //加入發(fā)送隊列 ????? EnterCriticalSection(FEventCS); ????? try ??????? if not Assigned(FFirstNode) then ??????? begin ???????? ?FFirstNode:=PNode; ??????? end ??????? else ??????? begin ????????? FLastNode.Next:=PNode; ??????? end; ??????? FLastNode:=PNode; ??????? Inc(FTotalCount); ????? finally ??????? LeaveCriticalSection(FEventCS); ????? end; ????? if not Sending then ????? begin ??????? Sending:=true; ??????? if not PostSend then ??????? begin ????????? closesocket(FSocket); ????????? break; ??????? end; ????? end; ??? end; ? end; end;?
由于要注意粘包緩存,所以發(fā)送的時候需要小于粘包函數(shù)大小,所以此函數(shù)的開始就是對發(fā)送數(shù)據(jù)的長度進行了判斷。if (DataLen<=0) or (FSendLen<DataLen) then 當發(fā)送數(shù)據(jù)長度合適的時候,就需要將發(fā)送的數(shù)據(jù)分割成4k大小的數(shù)據(jù)塊,并將此數(shù)據(jù)塊放入到發(fā)送隊列中。 當放置完畢以后,需要調(diào)用一次PostSend來發(fā)送數(shù)據(jù)。大家會看到在我調(diào)用PostSend函數(shù)之前對一個變量進行了判斷。if not Sending then 這個變量Sending是用來判斷當前是否正在發(fā)送數(shù)據(jù),如果正在發(fā)送數(shù)據(jù)的話,那么就無需投遞PostSend. TIOEvents類的最后一個函數(shù)就是Start函數(shù)了,此函數(shù)用于創(chuàng)建、連接服務端、設置套接字的事件和創(chuàng)建一個工作者線程。 procedure TIOEvents.Start; var ? Addr:TSockAddr; ? Event:Cardinal; begin ? FSocket := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); ? if FSocket = SOCKET_ERROR then ? begin ??? closesocket(FSocket); ??? FSocket:=INVALID_SOCKET; ??? raise Exception.Create(Format('WSASocket Error ErrorID=%d',[GetLastError])); ??? Exit; ? end; ? Addr.sin_addr.s_addr:=inet_addr(Pchar(FMainIP)); ? Addr.sin_family:=AF_INET; ? Addr.sin_port:=htons(FMainPort); ? if (connect(FSocket,@Addr,sizeof(Addr))=SOCKET_ERROR) then ? begin ??? closesocket(FSocket); ??? FSocket:=INVALID_SOCKET; ??? raise Exception.Create(Format('connect Error ErrorID=%d',[GetLastError])); ??? Exit; ? end; ? if FKeepAlive then ? begin ??? SetKPAlive; ? end; ? Event:=WSACreateEvent; ? FillChar(FRecvIOData,SizeOf(FRecvIOData),0); ? FillChar(FSendIOData,SizeOf(FSendIOData),0); ? FEventArray[FEventNums] :=Event; ? FSocketArray[FEventNums]:=FSocket; ? FRecvIOData.Overlapped.hEvent :=? Event; ? FSendIOData.Overlapped.hEvent :=? Event; ? Inc(FEventNums); ? //注冊此套接字上的事件 ? if WSAEventSelect(FSocket,Event, FD_READ or FD_WRITE or FD_CLOSE) = SOCKET_ERROR then ? begin ??? raise Exception.Create(Format('WSAEventSelect Error ErrorID=%d',[GetLastError])); ??? Exit; ? end; ? //創(chuàng)建工作者線程 ? FWorkThread:=TWorkThread.Create(Self); end;?
工作者線程是發(fā)送和接收數(shù)據(jù)的主要部分。沒有這部分代碼,將無法實現(xiàn)網(wǎng)絡通信。下面一篇我會寫出我是如何編寫工作者線程的。轉載于:https://blog.51cto.com/fxh7622/164247
總結
以上是生活随笔為你收集整理的Delphi编写事件模型客户端(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python的日志简单使用
- 下一篇: 老公,等儿子长大,我去天堂找你(推荐)