?? svrsocket,cltsocket控件源碼.htm
字號:
<br>
else DoListen(QueueSize);<br>
<br>
end;<br>
<br>
end;<br>
<br>
if FLookupState <> lsIdle then<br>
<br>
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);//遞歸<br>
<br>
except<br>
<br>
Disconnect(FSocket);<br>
<br>
raise;<br>
<br>
end;<br>
<br>
end;<br>
<br>
它從判斷FLookupState的狀態來執行相應的操作,而最后每一步都會執行到,它是怎么做到的呢,答案就是最后的這兩句:<br>
<br>
if FLookupState <> lsIdle then<br>
<br>
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);<br>
<br>
我們先從頭來看吧:<br>
<br>
首先,FLookupState的狀態行為lsIdle,則執行isIdle這一大塊,判斷Client的值,這里是服務端,所以應該為False(從調用該方法的函數也可得知),所以只執行了這一塊:<br>
<br>
FLookupState := lsLookupAddress;<br>
<br>
FAddr.sin_addr.S_addr := INADDR_ANY;<br>
<br>
其余都是對于客戶端的來作的,所以以后再討論它(由此也可以知道該函數的重要性了)。<br>
<br>
還記得INADDR_ANY的意義嗎,它會使得WinSock自動加入正確的地址。<br>
<br>
好,方法執行到最后的這幾句:<br>
<br>
if FLookupState <> lsIdle then<br>
<br>
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);<br>
<br>
這時FLookupState已經為lsLookupAddress了,所以又會調用ASyncInitSocket方法,它就是這樣遞歸調用本身,直到所有操作都完畢,這樣的設計思維實在是太精巧了。<br>
<br>
第一遞歸之后,它又執行了那一塊呢,當然是lsLookupAddress:這一塊啦,看看代碼,知道它會先判斷函數的參數Service是否為空,我們前面沒有對該傳遞過來的這個參數并沒有同值,所以為空,便執行了這兩句:<br>
<br>
FLookupState := lsLookupService;<br>
<br>
FAddr.sin_port := htons(Port);<br>
<br>
可知,它對結構的端口值了,又進行第二次遞歸,這里應該是執行lsLookupService:這一塊,并調用了這個方法DoListen(QueueSize);開始監聽:<br>
<br>
FLookupState := lsIdle;<br>
<br>
if Client then<br>
<br>
DoOpen<br>
<br>
else DoListen(QueueSize);<br>
<br>
我們看到FLookupState已經又回到了IsIdle,所以就不再遞歸了。<br>
<br>
最好,我們得來看看DoListen這個方法,這個大概就是萬事具務,只欠東風的東風了,我們猜測它會在這里調用Bind和Listen等API,并觸發Oolisten事件:<br>
<br>
(2211121)<br>
<br>
procedure TCustomWinSocket.DoListen(QueueSize: Integer);<br>
<br>
begin<br>
<br>
CheckSocketResult(bind(FSocket, FAddr, SizeOf(FAddr)), 'bind');<br>
<br>
DoSetASyncStyles;<br>
<br>
if QueueSize > SOMAXCONN then QueueSize := SOMAXCONN;<br>
<br>
Event(Self, seListen);<br>
<br>
CheckSocketResult(Winsock.listen(FSocket, QueueSize), 'listen');<br>
<br>
FLookupState := lsIdle;<br>
<br>
FConnected := True;<br>
<br>
end;<br>
<br>
哈,一切都明朗了,所有API都在這里調用了,事件也觸發了。現在就等客戶來連接了<br>
<br>
只是還沒有完,還有一個DoSetASyncStyles方法,堅持走下去吧,會到挑花園的:<br>
<br>
(22111211)<br>
<br>
procedure TCustomWinSocket.DoSetAsyncStyles;<br>
<br>
var<br>
<br>
Msg: Integer;<br>
<br>
Wnd: HWnd;<br>
<br>
Blocking: Longint;<br>
<br>
begin<br>
<br>
Msg := 0;<br>
<br>
Wnd := 0;<br>
<br>
if FAsyncStyles <> [] then<br>
<br>
begin<br>
<br>
Msg := CM_SOCKETMESSAGE;<br>
<br>
Wnd := Handle;<br>
<br>
end;<br>
<br>
WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));<br>
<br>
if FASyncStyles = [] then<br>
<br>
begin<br>
<br>
Blocking := 0;<br>
<br>
ioctlsocket(FSocket, FIONBIO, Blocking);<br>
<br>
end;<br>
<br>
end;<br>
<br>
有兩個情況,當FAsyncStyles有元素時和沒有元素時,從上面的代碼中我們看到它是有元素的。則:<br>
<br>
Msg := CM_SOCKETMESSAGE;<br>
<br>
Wnd := Handle;<br>
<br>
第一句應該是指針Socket消息了,用于與客戶端的讀寫事件的觸發的吧。而第二句,則我仔細看了,Handle是TCustomWinSocket的一個屬性:<br>
<br>
property Handle: HWnd read GetHandle;<br>
<br>
再看看GetHandle方法<br>
<br>
function TCustomWinSocket.GetHandle: HWnd;<br>
<br>
begin<br>
<br>
if FHandle = 0 then<br>
<br>
FHandle := AllocateHwnd(WndProc);<br>
<br>
Result := FHandle;<br>
<br>
end;<br>
<br>
我們記得上面并沒有對FHandle進行賦值,所以它應該為0,則調用了AllocateHwnd,這個方法產生一個不可見的窗口用于接收消息,窗口過程就是WndProc,在CustomWinSocket有聲明:<br>
<br>
procedure TCustomWinSocket.WndProc(var Message: TMessage);<br>
<br>
begin<br>
<br>
try<br>
<br>
Dispatch(Message);<br>
<br>
except<br>
<br>
if Assigned(ApplicationHandleException) then<br>
<br>
ApplicationHandleException(Self);<br>
<br>
end;<br>
<br>
end;<br>
<br>
可以知道,他調用Object的Dispatch(Message);進行消息分配,而我們看到類中有消息函數的聲明:<br>
<br>
procedure CMSocketMessage(var Message: TCMSocketMessage); message CM_SOCKETMESSAGE;<br>
<br>
當事件發生時就會調用這些消息處理函數了。而這些消息是從那里發生的呢,得回過頭去看看DoSetAsyncStyles方法,其中有這一個SocketAPI:<br>
<br>
WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));<br>
<br>
就是通過它,當有網絡消息發生的時候,才會觸發上面事件的,而Longint(Byte(FAsyncStyles)<br>
<br>
則指定允許觸發哪些事件。(具體的還是看看MSDN的說明吧)<br>
<br>
<br>
<br>
呼還沒有完嗎,其實差不多了,我們現在知道了當有客戶端連接或讀寫,是通過什么方式來讓服務端知道并觸發相應的事件的,說到底還是用了WinSock的API,只不過Delphi用自己的事件處理方式將那些Socket異步消息轉化成自己的事件了,這個過程是非常精彩的,很值得我們學習。<br>
<br>
但現在只剩下最一步了,TCustomWinSocket消息處理方法中,如何轉化為事件,并傳遞到ServerSocket去的呢,答案只有在源代碼中找了:<br>
<br>
procedure TCustomWinSocket.CMSocketMessage(var Message: TCMSocketMessage);<br>
<br>
<br>
<br>
function CheckError: Boolean;<br>
<br>
var<br>
<br>
ErrorEvent: TErrorEvent;<br>
<br>
ErrorCode: Integer;<br>
<br>
begin<br>
<br>
if Message.SelectError <> 0 then<br>
<br>
begin<br>
<br>
Result := False;<br>
<br>
ErrorCode := Message.SelectError;<br>
<br>
case Message.SelectEvent of<br>
<br>
FD_CONNECT: ErrorEvent := eeConnect;<br>
<br>
FD_CLOSE: ErrorEvent := eeDisconnect;<br>
<br>
FD_READ: ErrorEvent := eeReceive;<br>
<br>
FD_WRITE: ErrorEvent := eeSend;<br>
<br>
FD_ACCEPT: ErrorEvent := eeAccept;<br>
<br>
else<br>
<br>
ErrorEvent := eeGeneral;<br>
<br>
end;<br>
<br>
Error(Self, ErrorEvent, ErrorCode);<br>
<br>
if ErrorCode <> 0 then<br>
<br>
raise ESocketError.CreateResFmt(@sASyncSocketError, [ErrorCode]);<br>
<br>
end else Result := True;<br>
<br>
end;<br>
<br>
<br>
<br>
begin<br>
<br>
with Message do<br>
<br>
if CheckError then<br>
<br>
case SelectEvent of<br>
<br>
FD_CONNECT: Connect(Socket);<br>
<br>
FD_CLOSE: Disconnect(Socket);<br>
<br>
FD_READ: Read(Socket);<br>
<br>
FD_WRITE: Write(Socket);<br>
<br>
FD_ACCEPT: Accept(Socket);//這個很特殊<br>
<br>
end;<br>
<br>
end;<br>
<br>
呵,又是一個大函數,不過理解起來倒是容易多了,先檢查有沒有錯誤,如果有就調用Error(Self, ErrorEvent, ErrorCode);如果沒有,就根據相應標識,調用相就的函數,其實我們已經可以確定,像Read這些內部一定會調用Event,再由Event調用<br>
<br>
FOnSocketEvent(Self, Socket, SocketEvent);,這樣才能使得ServerSocket這些外部的類獲得事件(上面已經說到它們是怎么把事件關聯起來的了)。<br>
<br>
而源代碼中確實也是這樣的,這里只列出一個read,其他的一樣:<br>
<br>
procedure TCustomWinSocket.Read(Socket: TSocket);<br>
<br>
begin<br>
<br>
if (FSocket = INVALID_SOCKET) or (Socket <> FSocket) then Exit;<br>
<br>
Event(Self, seRead);<br>
<br>
end;<br>
<br>
procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);<br>
<br>
begin<br>
<br>
if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket, SocketEvent);<br>
<br>
end;<br>
<br>
<br>
<br>
完了吧,可惜還沒有,回到上面的函數去,發現還有一個方法沒有分析:<br>
<br>
FServerSocket.Disconnect(FServerSocket.SocketHandle)可是按我們流程,還不會調用到它,所以這里暫不提它。<br>
<br>
<br>
<br>
好了,第二步就這樣結束了,再說下去,我自己也暈了。不過我們知道了這一步所完成的任務:連接一個監聽的套接字,并設定好了事件方法指針,在適當的時機會調用適當的事件處理函數。真的得謝謝Delphi為我們做了這么多的事,使我們在使用的時候感覺到是如此的簡單。<br>
<br>
<br>
<br>
<br>
<br>
3.程序執行到這里,得看看ClientSocket吧,有了上面的分析,下面的分析都會簡單得多:<br>
<br>
constructor TClientSocket.Create(AOwner: TComponent);<br>
<br>
begin<br>
<br>
inherited Create(AOwner);<br>
<br>
FClientSocket := TClientWinSocket.Create(INVALID_SOCKET);<br>
<br>
InitSocket(FClientSocket);<br>
<br>
end;<br>
<br>
沒有什么值得討論的FClientSocket也是繼承父類的構造方法,上面已經說過了。<br>
<br>
接設置Port,Address等,都是到祖先類設置,沒有什么好說的,最后一句是<br>
<br>
Active:=true;<br>
<br>
上面的討論已經知道,它最后會調用ClientSocket的覆蓋方法:<br>
<br>
procedure TClientSocket.DoActivate(Value: Boolean);<br>
<br>
begin<br>
<br>
if (Value <> FClientSocket.Connected) and not (csDesigning in ComponentState) then<br>
<br>
begin<br>
<br>
if FClientSocket.Connected then<br>
<br>
FClientSocket.Disconnect(FClientSocket.FSocket)<br>
<br>
else FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);<br>
<br>
end;<br>
<br>
end;<br>
<br>
這里的Value為True,則調用<br>
<br>
FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);<br>
<br>
去看看吧:<br>
<br>
procedure TCustomWinSocket.Open(const Name, Address, Service: string; Port: Word; Block: Boolean);<br>
<br>
begin<br>
<br>
if FConnected then raise ESocketError.CreateRes(@sSocketAlreadyOpen);<br>
<br>
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);<br>
<br>
if FSocket = INVALID_SOCKET then raise ESocketError.CreateRes(@sCannotCreateSocket);<br>
<br>
try<br>
<br>
Event(Self, seLookUp);<br>
<br>
if Block then<br>
<br>
begin<br>
<br>
FAddr := InitSocket(Name, Address, Service, Port, True);<br>
<br>
DoOpen;<br>
<br>
&n <br>
</td>
</tr>
</table>
</body>
</html>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -