?? svrsocket,cltsocket控件源碼.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<title>New Page 1</title>
</head>
<body>
<p align="center"><font size="5">ServerSocket,ClientSocket控件源碼</font></p>
<table border="1" width="100%" bgcolor="#C0C0C0">
<tr>
<td width="100%">ServerSocket,ClientSocket控件源碼 <br>
<br>
本篇將通過一次Socket通信操作來對ServerSocket和ClientSocket這兩個控件的源碼進行一次閱讀,希望能理出一個脈絡來,以供大家參考。其實說得確切一點,應該是對Scktcomp這個單元進行解讀,但由于這個單元的代碼太多了,所以不可能面面俱到,我試圖以非阻塞式的通信來一步步說明它們是怎么樣封裝WinSock的API的,至于阻塞式的,在ServerSocket和ClientSocket并不常用,所以這一篇就不打算說了,可能源碼中會有一些忽略掉,以后有時間來補一篇阻塞式的閱讀吧。雖然現在Delphi已經用Indy控件替換了SS和CS等網絡控件,但畢竟這兩個控件是對Socket函數進行封裝,學習它怎么樣封裝也好吧。<br>
<br>
在這之前,須對WinSock有一個大概的了解,但也僅止大概,對Socket編程有一個總體的掌握行了,畢竟那不是我的能力所及。想要系統一點的學習Socket,可以去網上下WinSocket的中文文檔以及參考MSDN。<br>
<br>
以下是說明這個過程中各個函數的聲明<br>
<br>
在開始使用WinSock的Api的時候,必須加載WinSock Dll的相應版本,這時用到的函數是:<br>
<br>
int WSAStartup (<br>
WORD wVersionRequested, <br>
LPWSADATA lpWSAData <br>
);<br>
wVersionRequested指定用到的WinSock的最低版本,高字節指定副版本,低字節指定主版本,關于WinSock的版本,詳見MSDN。<br>
<br>
lpWSAData結構類型,系統把加載進去的版本信息添加到這個結構中。<br>
<br>
該函數成功返回0,不成功則返回幾個錯誤代碼之一<br>
<br>
這個函數和WSACleanup對應,查看MSDN<br>
<br>
從MSDN列出一個例子:<br>
<br>
WORD wVersionRequested;<br>
<br>
WSADATA wsaData;<br>
<br>
int err;<br>
<br>
<br>
<br>
wVersionRequested = MAKEWORD( 2, 2 );<br>
<br>
<br>
<br>
err = WSAStartup( wVersionRequested, &wsaData );<br>
<br>
if ( err != 0 ) {<br>
<br>
/* Tell the user that we could not find a usable */<br>
<br>
/* WinSock DLL. */<br>
<br>
return;<br>
<br>
}<br>
<br>
調用上面的函數之后,就可以用下面的函數來創建一個Socket了,Socket是WinSock定義的數據類型(整數),相當于句柄,用于標識系統中的每一個Socket連接<br>
<br>
SOCKET socket (<br>
int af, <br>
int type, <br>
int protocol <br>
);<br>
Af協議標志,Internet協議族的標志是PINET。<br>
<br>
Type 協議類型志,SOCISTREAM表明是面向連接的字節流類型,SOCIDGRAM表明是面向無連接的數據報類型。<br>
<br>
Protocol Socket采用的協議類型,如果采用IPPROTO_TCP常數就是采用了TCP協議。<br>
<br>
如果調用失敗,會返回INVALID_SOCKET值,正常的Socket取值范圍是1~INVALID_SOCKET-1;<br>
<br>
下面的函數將Socket與特定的主機進行綁定:<br>
<br>
int bind (<br>
SOCKET s, <br>
const struct sockaddr FAR* name, <br>
int namelen <br>
);<br>
S就是一個Socket,由Socket()函數返回得到的。<br>
<br>
Name是sockaddr結構的變量<br>
<br>
Namelen是Name結構的長度。<br>
<br>
如果函數調用成功,將返回0,如果不成功,將返回SOCKET_ERROR,可以從WSAGetLastError.函數獲得錯誤代碼。<br>
<br>
現在說sockaddr這個結構,在Winsock中其實有兩個功能相似的結構,它們是:<br>
<br>
struct sockaddr {<br>
<br>
u_short sa_family;<br>
<br>
char sa_data[14];<br>
<br>
};<br>
<br>
struct sockaddr_in {<br>
<br>
short sin_family;<br>
<br>
u_short sin_port;<br>
<br>
struct in_addr sin_addr;<br>
<br>
char sin_zero[8];<br>
<br>
};<br>
<br>
這兩個結構在Delphi中被聲明為一個變體記錄,這兩個指定通信的本地主機地址,本地協議端口,另外還有通信過程中使用的協議類型。<br>
<br>
其中:sin_family規定了哪個協議用來實現套按字連接。WinSock必須設置常數AF_INET<br>
<br>
sin_port;:WinSock應用程序使用的端口號<br>
<br>
sin_addr:這個是32位IP地址<br>
<br>
sin_zero[8];這個保留,沒有使用。<br>
<br>
Server端必須調用Bind()函數,設計時可以將地址設定為INADDR_ANY,這樣WinSock會自動加入機器正確的地址.<br>
<br>
以下是客戶端向服務端連接時調用的函數<br>
<br>
int connect (<br>
SOCKET s, <br>
const struct sockaddr FAR* name, <br>
int namelen <br>
);<br>
函數里的參數和bind()一個,不多說了,函數成功時返回0,否則返回SOCKET_ERROR。<br>
<br>
服務端在Bind之后,調用下面函數進行監視。<br>
<br>
int listen (<br>
SOCKET s, <br>
int backlog <br>
);<br>
其中backlog是可以建立的最大連接數,如果值設為SOMAXCONN,將由Socket的服務提供商設定一個合理的值,這個值不確定。<br>
<br>
函數成功時返回0,否則返回SOCKET_ERROR。<br>
<br>
當客戶端連接服務端,服務端調用下面函數接收客戶的請求,并向客戶機發送應答信息<br>
<br>
SOCKET accept (<br>
SOCKET s, <br>
struct sockaddr FAR* addr, <br>
int FAR* addrlen <br>
);<br>
其中中addr是用來保存客戶機地址信息的指針<br>
<br>
Addrlen是addr的長度一般是Sizeof(addr)<br>
<br>
如果函數成功,則返回一個Socket,這個Socket才是與客戶實際通信的套接字,原來的那個Socket繼續監視其他客戶端的連接<br>
<br>
以下幾個是用個服務機和客戶機通信的函數,<br>
int send (SOCKET s,const char FAR * buf, int len,int flags );<br>
在面向連接的情況下發送數據<br>
int recv (SOCKET s, char FAR* buf, int len, int flags );<br>
在面向連接的情況下接收數據<br>
另外還有sendto和recvfrom用于無連接情況,具體查MSDN<br>
其中Buf是發送或接收的緩沖區,Len是緩沖區的大小,Flags是網絡呼叫產生方式標志,值可以為0,MSG_DONTROUTE或 MSG_OOB用途具體看MSDN。<br>
通信完畢后,需要關閉Socket,函數聲明如下:<br>
int closesocket (<br>
SOCKET s <br>
);<br>
在程序的最后,需要調用下面的函數,結束WinSock DLL<br>
int WSACleanup (void);<br>
函數成功時返回0,否則返回SOCKET_ERROR。<br>
<br>
<br>
下面開始TServerSockett和TClientSocket的原代碼閱讀:<br>
不過在分析之前,先要明白,對于WinSock的封裝其實是由ScktComp單元的幾個類一起完成的,它們的關系是:<br>
<br>
TServerSocket繼承于TCustomServerSocket<br>
<br>
TCustomServerSocket繼承于TCustomSocket<br>
<br>
TCustomSocket繼承于TAbstractSocket而TAbstractSockeet的上級則是TComponent了<br>
<br>
TClientSocket繼承于TCustomSocket<br>
<br>
另外還有幾個類:<br>
<br>
TServerWinSocket和TClientWinSocket以及TServerClientWinSocket繼承TCustomSocket<br>
<br>
它們才是真正封裝Socket的類。作為上面提到的類的成員存在<br>
<br>
此外還有幾個用于阻塞式傳輸的類。這里就忽略不講了,以后有機會再補上來吧。 <br>
<br>
<br>
<br>
以ServerSocket和ClientSocket的一次操作流程來跟蹤它們的源代碼,看看他們是怎么樣封WinSocket的。以非阻塞方式來例子。<br>
<br>
<br>
<br>
1. ServerSocket要創建,構造函數如下:<br>
<br>
(11)<br>
<br>
constructor TServerSocket.Create(AOwner: TComponent);<br>
<br>
begin<br>
<br>
inherited Create(AOwner);<br>
<br>
FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);<br>
<br>
InitSocket(FServerSocket);<br>
<br>
FServerSocket.ThreadCacheSize := 10;<br>
<br>
end;<br>
<br>
inherited Create(AOwner);是繼承自TComponent的構造函數。<br>
<br>
接下來創建它的一個成員,這是一個TServerWinSocket的對象,這個才是真正封裝SocketApi的類,等一個討論它。事實上這個構造函數繼承自它的父類TCustomServerSocket<br>
<br>
接下來調用InitSocket(FServerSocket);這是ServerSocket的祖先類TAbstractSocket的一個方法,傳入參數是成員FserverSocket完成的功能是將ServerSocket的事件指針指向TServerWinSocket的事件,使其能處理Socket觸發的事件。<br>
<br>
最后,設置FServerSocket允許連接的線程數,這里為10。<br>
<br>
<br>
<br>
好,現在回過頭來看看FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);做了什么:<br>
<br>
(111)<br>
<br>
constructor TServerWinSocket.Create(ASocket: TSocket);<br>
<br>
begin<br>
<br>
FConnections := TList.Create;<br>
<br>
FActiveThreads := TList.Create;<br>
<br>
FListLock := TCriticalSection.Create;<br>
<br>
inherited Create(ASocket);<br>
<br>
FAsyncStyles := [asAccept];<br>
<br>
end;<br>
<br>
首先創建兩個TList對象,一個是FConnections,代表各個處理客戶連接的Socket,它對應于屬性:property Connections[Index: Integer]: TCustomWinSocket,你可以通過這個屬性對各個客戶連接進行操作。FActiveThreads 管理由Connections 數組確定的的客戶端連接線程TServerClientThread,它對應的屬性是ActiveThreads,這個是只讀屬性,返回當前正在使用的TServerClientThread對象的個數。接下來創建互斥量對象,用于線程同步的處理。<br>
<br>
到這里又調用其父類的構造函數,傳遞的參數就是ServerSocket給的值INVALID_SOCKET,(想想上面提到的這個值的意義)<br>
<br>
好,再繼續跟蹤,到其父類的構造函數去看一下,我們這時應該保留一個問題,按照WinSock的編程模式,剛開始應該是初始化Winsock.DLL,并調用綁定監聽函數,這些API是什么在地方被調用呢?<br>
<br>
(1111)<br>
<br>
constructor TCustomWinSocket.Create(ASocket: TSocket);<br>
<br>
begin<br>
<br>
inherited Create;<br>
<br>
Startup;<br>
<br>
FSocketLock := TCriticalSection.Create;<br>
<br>
FASyncStyles := [asRead, asWrite, asConnect, asClose];<br>
<br>
FSocket := ASocket;<br>
<br>
FAddr.sin_family := PF_INET;<br>
<br>
FAddr.sin_addr.s_addr := INADDR_ANY;<br>
<br>
FAddr.sin_port := 0;<br>
<br>
FConnected := FSocket <> INVALID_SOCKET;<br>
<br>
end;<br>
<br>
首先調用TObject的構造函數,<br>
<br>
再調用Sartup;分析完這個函數再看里面的代碼,這里可以猜測它里面會調用WSAStartup函數。<br>
<br>
接下來看到從ServerSocket傳過來的參數指定給了TCustomWinSocket的成員,還有下面幾個成員的設置,可以肯定,這里是對Socket進行初始化,結合開頭所講的知識,再看看源代碼。應該不難理解了吧。<br>
<br>
再看看Startup的源碼:<br>
<br>
(11111)<br>
<br>
procedure Startup;<br>
<br>
var<br>
<br>
ErrorCode: Integer;<br>
<br>
begin<br>
<br>
ErrorCode := WSAStartup($0101, WSAData);<br>
<br>
if ErrorCode <> 0 then<br>
<br>
raise ESocketError.CreateResFmt(@sWindowsSocketError,<br>
<br>
[SysErrorMessage(ErrorCode), ErrorCode, 'WSAStartup']);<br>
<br>
end;<br>
<br>
這是一個全局函數,看到其中一行重要的代碼了,<br>
<br>
ErrorCode := WSAStartup($0101, WSAData);WinSock就是在這里被初始化的。<br>
<br>
而在服務端沒有建立監聽之時,Socket就在上面被默認設置了。<br>
<br>
好,現在ServerSocket的成員FserverSocket的創建就完成了,再回到最上面,<br>
<br>
FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);之后,便是<br>
<br>
InitSocket(FServerSocket);了,看看它的代碼:<br>
<br>
(12)<br>
<br>
procedure TAbstractSocket.InitSocket(Socket: TCustomWinSocket);<br>
<br>
begin<br>
<br>
Socket.OnSocketEvent := DoEvent;<br>
<br>
Socket.OnErrorEvent := DoError;<br>
<br>
end;<br>
<br>
正好和上面所說的一樣,現在可以認為當Socket發生了事件(比如連接,接收等)之后,就是調用了DoEvent了,錯誤發生了也一樣。不妨看看DoEvent代碼:<br>
<br>
(121)<br>
<br>
procedure TAbstractSocket.DoEvent(Sender: TObject; Socket: TCustomWinSocket;<br>
<br>
SocketEvent: TSocketEvent);<br>
<br>
begin<br>
<br>
Event(Socket, SocketEvent);<br>
<br>
end;<br>
<br>
里面是調用Event,再看看Event的聲明:<br>
<br>
procedure Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);virtual; abstract;<br>
<br>
這是一個抽象函數,由此可以知道,TAbstractSocket是一個抽象的類,它只是封裝了一般的操作,具體地都留到了它的子類中去了,所以它的子類一定覆蓋了這個Event方法,而DoEvent中調用的Event實際上就是調用它子類的Event方法,這個就是典型的模板模式。<br>
<br>
好,看看它的子類有沒有覆蓋這個方法,果然在TCustomSocket中看到了這個方法,并實現了它,看看它的源代碼:<br>
<br>
(1211)<br>
<br>
procedure TCustomSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);<br>
<br>
begin<br>
<br>
case SocketEvent of<br>
<br>
seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);<br>
<br>
seConnecting: if Assigned(FOnConnecting) then FOnConnecting(Self, Socket);<br>
<br>
seConnect:<br>
<br>
begin<br>
<br>
FActive := True;<br>
<br>
if Assigned(FOnConnect) then FOnConnect(Self, Socket);<br>
<br>
end;<br>
<br>
seListen:<br>
<br>
begin<br>
<br>
FActive := True;<br>
<br>
if Assigned(FOnListen) then FOnListen(Self, Socket);<br>
<br>
end;<br>
<br>
seDisconnect:<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -