亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

socket通信

系統 1659 0

1.Soket 發展史以及它和 tcp/ip 的關系

七十年代中,美國國防部高研署 (DARPA) TCP/IP 的軟件提供給加利福尼亞大學 Berkeley 分校后, TCP/IP 很快被集成到 Unix 中,同時出現了許多成熟的 TCP/IP 應用程序接口 (API) 。這個 API 稱為 Socket 接口。今天, SOCKET 接口是 TCP/IP 網絡最為 通用的 API ,也是在 INTERNET 上進行應用開發最為通用的 API 。
  九十年代初,由 Microsoft 聯合了其他幾家公司共同制定了一套 WINDOWS 下的網絡編程接口,即 Windows Sockets 規范。它是 Berkeley Sockets 的重要擴充,主要是增加了一些異步函數,并增加了符合 Windows 消息驅動特性的網絡事件異步選擇機制。 Windows Sockets 規范是一套開放的、支持多種協議的 Windows 下的網絡編程接口。目前,在實際應用中的 Windows Sockets 規范主要有 1.1 版和 2.0 版。兩者的最重要區別是 1.1 版只支持 TCP/IP 協議,而 2.0 版可以支持多協議, 2.0 版有良好的向后兼容 性,目前, Windows 下的 Internet 軟件都是基于 WinSock 開發的。

Socket 實際在計算機中提供了一個通信端口,可以通過這個端口與任何一個具有 Socket 接口的計算機通信。應用程序在網絡上傳輸,接收的信息都通過這個 Socket 接口來實現。在應用開發中就像使用文件 句柄一樣,可以對 Socket 句柄進行讀、寫操作。套接字是網絡的基本構件。它是可以被命名和尋址的通信端點,使用中的每一個套接字都有其類型和一個與之相連進程。套接字存在通信區域(通信區域又稱地址簇)中。套接字只與同一區域中的套接字交換數據(跨區域時,需要執行某和轉換進程才能實現)。 WINDOWS 中的套接字只支持一個域 —— 網際域。套接字具有類型。我們將 Socket 翻譯為套接字,套接字分為以下三種類型:
  字節流套接字 (Stream Socket)  是最常用的套接字類型, TCP/IP 協議族中的 TCP 協議使用此類接口。字節流套接口提供面向連接的 ( 建立虛電路 ) 、無差錯的、發送先后順序一致的、無記錄邊界和非重復的網絡信包傳輸。
數據報套接字 (Datagram Socket) TCP/IP 協議族中的 UDP 協議使用此類接口,它是無連接的服務,它以獨立的信包進行網絡傳輸,信包最大長度為 32KB ,傳輸不保證順 序性、可靠性和無重復性,它通常用于單個報文傳輸或可靠性不重要的場合。數據報套接口的一個重要特點是它保留了記錄邊界。對于這一特點。數據報套接口采用了與現在許多包交換網絡 ( 例如以太網 ) 非常類似的模型。
  原始數據報套接字 (Raw Socket)  提供對網絡下層通訊協議 ( IP 協議 ) 的直接訪問,它一般不是提供給普通用戶的,主要用于開發新的協議或用于提取協議較隱蔽的功能。

2 、 socket 通信概念

* 端口
網絡中可以被命名和尋址的通信端口,是操作系統可分配的一種資源。
按照 OSI 七層協議的描述,傳輸層與網絡層在功能上的最大區別是傳輸層提供進程通信能力。 從這個意義上講,網絡通信的最終地址就不僅僅是主機地址了,還包括可以描述進程的某種標識符。為此, TCP/IP 協議提出了協議端口( protocol port ,簡稱端口)的概念,用于標識通信的進程。
端口是一種抽象的軟件結構(包括一些數據結構和 I/O 緩沖區)。應用程序(即進程)通過系統調 用與某端口建立連接( binding )后,傳輸層傳給該端口的數據都被相應進程所接收,相應進程發給傳輸層的數據都通過該端口輸出。在 TCP/IP 協議的實現中,端口操作類似于一般的 I/O 操作,進程獲取一個端口,相當于獲取本地唯一的 I/O 文件,可以用一般的讀寫原語訪問之。
類似于文件描述符,每個端口都擁有一個叫端口號( port number )的整數型標識符,用于區別不同端口。由于 TCP/IP 傳輸層的兩個協議 TCP UDP 是完全獨立的兩個軟件模塊,因此各自的端口號也相互獨立,如 TCP 有一個 255 號端口, UDP 也可以有一個 255 號端口,二者并不沖突。

* 地址
網絡通信中通信的兩個進程分別在不同的機器上。在互連網絡中,兩臺機器可能位于不同的網絡,這些網絡通過網絡互連設備(網關,網橋,路由器等)連接。因此需要三級尋址:

某一主機可與多個網絡相連,必須指定一特定網絡地址;

網絡上每一臺主機應有其唯一的地址;

每一主機上的每一進程應有在該主機上的唯一標識符。
通常主機地址由網絡 ID 和主機 ID 組成,在 TCP/IP 協議中用 32 位整數值表示; TCP UDP 均使用 16 位端口號標識用戶進程。

* 網絡字節順序
不同的計算機存放多字節值的順序不同,有的機器在起始地址存放低位字節(低價先存),有的存高位字節(高價先存)。為保證數據的正確性,在網絡協議中須指定網絡字節順序。 TCP/IP 協議使用 16 位整數和 32 位整數的高價先存格式,它們均含在協議頭文件中。

* 面向連接

可靠的報文流、可靠的字節流、可靠的連接,如:文件傳輸( FTP )、遠程登錄( Telnet
數字話音。

* 無連接

不可靠的數據報、有確認的數據報、請求-應答,如:電子郵件( E-mail )、電子郵件中的掛號信、網絡數據庫查詢。

* 順序

在網絡傳輸中,兩個連續報文在端-端通信中可能經過不同路徑,這樣到達目的地時的順序

可能會與發送時不同。 " 順序 " 是指接收數據順序與發送數據順序相同。 TCP 協議提供這項服務。

* 差錯控制

保證應用程序接收的數據無差錯的一種機制。檢查差錯的方法一般是采用檢驗、檢查和 Checksum 的方法。而保證傳送無差錯的方法是雙方采用確認應答技術。 TCP 協議提供這項服務。

* 流控制
在數據傳輸過程中控制數據傳輸速率的一種機制,以保證數據不被丟失。 TCP 協議提供這項服務。

* 字節流
字節流方式指的是僅把傳輸中的報文看作是一個字節序列,不提供數據流的任何邊界。 TCP 協議提供字節流服務。

* 報文
接收方要保存發送方的報文邊界。 UDP 協議提供報文服務。

* 全雙工 / 半雙工
端-端間數據同時以兩個方向 / 一個方向傳送。

* 緩存 / 帶外數據
在字節流服務中,由于沒有報文邊界,用戶進程在某一時刻可以讀或寫任意數量的字節。為保證傳輸正確或采用有流控制的協議時,都要進行緩存。但對某些特殊的需求,如交互式應用程序,又會要求取消這種緩存。

* 客戶 / 服務器模式

socket通信

TCP/IP 網絡應用中,通信的兩個進程間相互作用的主要模式是客戶 / 服務器模式( Client/Server model ),即客戶向服務器發出服務請求,服務器接收到請求后,提供相應的服務。客戶 / 服務器模式的建立基于以下兩點:首先,建立網絡的起因是網絡中軟硬件資源、運算能力和信息不均等,需要共享,從而造就擁有眾多資源的主機提供服務,資源較少的客戶請求服務這一非對等作用。其次,網間進程通信完全是異步的,相互通信的進程間既不存在父子關系,又不共享內存緩沖區,因此需要一種機制為希望通信的進程間建立聯系,為二者的數據交換提供同步,這就是基于客戶 / 服務器模式的 TCP/IP
客戶 / 服務器模式在操作過程中采取的是主動請求方式:
首先服務器方要先啟動,并根據請求提供相應服務:

打開一通信通道并告知本地主機,它愿意在某一公認地址上(周知口,如 FTP 21 )接收客戶請求;

等待客戶請求到達該端口;

接收到重復服務請求,處理該請求并發送應答信號。接收到并發服務請求,要激活一新進程來處理這個客戶請求(如 UNIX 系統中用 fork 、 exec )。新進程處理此客戶請求,并不需要對其它請求作出應答。服務完成后,關閉此新進程與客戶的通信鏈路,并終止。

返回第二步,等待另一客戶請求。

關閉服務器

客戶方:

打開一通信通道,并連接到服務器所在主機的特定端口;

向服務器發服務請求報文,等待并接收應答;繼續提出請求 ......

請求結束后關閉通信通道并終止。

從上面所描述過程可知:

客戶與服務器進程的作用是非對稱的,因此編碼不同。

服務進程一般是先于客戶請求而啟動的。只要系統運行,該服務進程一直存在,直到正?;驈娖冉K止。

3.socket 通信五元組

* SOCKET PASCAL FAR socket(int af, int type, int protocol)
該調用要接收三個參數: af 、 type 、 protocol 。參數 af 指定通信發生的區域, UNIX 系統支持的地址族有: AF_UNIX AF_INET AF_NS 等,而 DOS WINDOWS 中僅支持 AF_INET ,它是網際網區域。因此,地址族與協議族相同。參數 type 描述要建立的套接字的類型。參數 protocol 說明該套接字使用的特定協議,如果調用者不希望特別指定使用的協議,則置為 0 ,使用默認的連接模式。根據這三個參數建立一個套接字,并將相應的資源分配給它,同時返回一個整型套接字號。因此, socket() 系統調用實際上指定了相關五元組中的 " 協議 " 這一元。

TCP/IP socket 提供下列三種類型套接字。

流式套接字( SOCK_STREAM
提供了一個面向連接、可靠的數據傳輸服務,數據無差錯、無重復地發送,且按發送順序接收。內設流量控制,避免數據流超限;數據被看作是字節流,無長度限制。文件傳送協議( FTP )即使用流式套接字。

數據報式套接字( SOCK_DGRAM
提供了一個無連接服務。數據包以獨立包形式被發送,不提供無錯保證,數據可能丟失或重復,并且接收順序混亂。網絡文件系統( NFS )使用數據報式套接字。

原始式套接字( SOCK_RAW
該接口允許對較低層協議,如 IP 、 ICMP 直接訪問。常用于檢驗新的協議實現或訪問現有服務中配置的新設備。

* int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen)
參數 s 是由 socket() 調用返回的并且未作連接的套接字描述符 ( 套接字號 ) 。參數 name 是賦給套接字 s 的本地地址(名字),其長度可變,結構隨通信域的不同而不同。 namelen 表明了 name 的長度。

* int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數 s 是欲建立連接的本地套接字描述符。參數 name 指出說明對方套接字地址結構的指針。對方套接字地址長度由 namelen 說明。

* SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
參數 s 為本地套接字描述符,在用做 accept() 調用的參數前應該先調用過 listen() addr 指向客戶方套接字地址結構的指針,用來接收連接實體的地址。 addr 的確切格式由套接字創建時建立的地址族決定。 addrlen 為客戶方套接字地址的長度(字節數)。如果沒有錯誤發生, accept() 返回一個 SOCKET 類型的值,表示接收到的套接字的描述符。否則返回值 INVALID_SOCKET 。

調用 accept() 后,服務器等待從編號為 s 的套接字上接受客戶連接請求,而連接請求是由客戶方的 connect() 調用發出的。當有連接請求到達時, accept() 調用將請求連接隊列上的第一個客戶方套接字地址及長度放入 addr addrlen ,并創建一個與 s 有相同特性的新套接字號。新的套接字可用于處理服務器并發請求。

監聽連接 ── listen()
此調用用于面向連接服務器,表明它愿意接收連接。 listen() 需在 accept() 之前調用,其調用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
參數 s 標識一個本地已建立、尚未連接的套接字號,服務器愿意從它上面接收請求。 backlog 表示請求連接隊列的最大長度,用于限制排隊請求的個數,目前允許的最大值為 5 。如果沒有錯誤發生, listen() 返回 0 。否則它返回 SOCKET_ERROR listen() 在執行調用過程中可為沒有調用過 bind() 的套接字 s 完成所必須的連接,并建立長度為 backlog 的請求連接隊列。
調用 listen() 是服務器接收一個連接請求的四個步驟中的第三步。它在調用 socket() 分配一個流套接字,且調用 bind() s 賦于一個名字之后調用,而且一定要在 accept() 之前調用。

recv() 調用用于在參數 s 指定的已連接的數據報或流套接字上接收輸入數據,格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
參數 s 為已連接的套接字描述符。 buf 指向接收輸入數據緩沖區的指針,其長度由 len 指定。 flags 指定傳輸控制方式,如是否接收帶外數據等。如果沒有錯誤發生, recv() 返回總共接收的字節數。如果連接被關閉,返回 0 。否則它返回 SOCKET_ERROR

send() 調用用于在參數 s 指定的已連接的數據報或流套接字上發送輸出數據,格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
參數 s 為已連接的本地套接字描述符。 buf 指向存有發送數據的緩沖區的指針,其長度由 len 指定。 flags 指定傳輸控制方式,如是否發送帶外數據等。如果沒有錯誤發生, send() 返回總共發送的字節數。否則它返回 SOCKET_ERROR

輸入 / 輸出多路復用 ── select()
select()
調用用來檢測一個或多個套接字的狀態。對每一個套接字來說,這個調用可以請求讀、寫或錯誤狀態方面的信息。請求給定狀態的套接字集合由一個 fd_set 結構指示。在返回時,此結構被更新,以反映那些滿足特定條件的套接字的子集,同時, select() 調用返回滿足條件的套接字的數目,其調用格式如下:
int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
參數 nfds 指明被檢查的套接字描述符的值域,此變量一般被忽略。
參數 readfds 指向要做讀檢測的套接字描述符集合的指針,調用者希望從中讀取數據。參數 writefds 指向要做寫檢測的套接字描述符集合的指針。 exceptfds 指向要檢測是否出錯的套接字描述符集合的指針。 timeout 指向 select() 函數等待的最大時間,如果設為 NULL 則為阻塞操作。 select() 返回包含在 fd_set 結構中已準備好的套接字描述符的總數目,或者是發生錯誤則返回 SOCKET_ERROR 。

select() 的機制中提供一 fd_set 的數據結構,實際上是一 long 類型的數組,每一個數組元素都能與一打開的文件句柄(不管是 Socket 句柄 , 還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成,當調用 select() 時,由內核根據 IO 狀態修改 fd_set 的內容,由此來通知執行了 select() 的進程哪一 Socket 或文件可讀,下面具體解釋:
#include<sys/types.h>
#include<sys/times.h>
#include<sys/select.h>

intselect(nfds,readfds,writefds,exceptfds,timeout)
intnfds;
fd_set*readfds,*writefds,*exceptfds;
structtimeval*timeout;

ndfs
select 監視的文件句柄數,視進程中打開的文件數而定 , 一般設為呢要監視各文件中的最大文件號加一。
readfds
select 監視的可讀文件句柄集合。
writefds:select
監視的可寫文件句柄集合。
exceptfds
select 監視的異常文件句柄集合。
timeout
:本次 select() 的超時結束時間。(見 /usr/sys/select.h ,可精確至百萬分之一秒?。?

readfds writefds 中映象的文件可讀或可寫或超時,本次 select()
就結束返回。程序員利用一組系統提供的宏在 select() 結束時便可判
斷哪一文件可讀或可寫。對 Socket 編程特別有用的就是 readfds 。
幾只相關的宏解釋如下:

FD_ZERO(fd_set*fdset)
:清空 fdset 與所有文件句柄的聯系。
FD_SET(intfd,fd_set*fdset)
:建立文件句柄 fd fdset 的聯系。
FD_CLR(intfd,fd_set*fdset)
:清除文件句柄 fd fdset 的聯系。
FD_ISSET(intfd,fdset*fdset)
:檢查 fdset 聯系的文件句柄 fd 是否可讀寫, >0 表示可讀寫。
(關于 fd_set 及相關宏的定義見 /usr/include/sys/types.h

這樣,你的 socket 只需在有東東讀的時候才讀入,大致如下:

...
intsockfd;
fd_setfdR;
structtimevaltimeout=..;
...
for(;;){
FD_ZERO(&fdR);
FD_SET(sockfd,&fdR);
switch(select(sockfd+1,&fdR,NULL,&timeout)){
case-1:
errorhandledbyu;
case0:
timeouthanledbyu;
default:
if(FD_ISSET(sockfd)){
nowureadorrecvsomething;
/*ifsockfdisfatherand
serversocket,ucannow
accept()*/
}
}
}

所以一個 FD_ISSET(sockfd) 就相當通知了 sockfd 可讀。 至于 structtimeval 在此的功能,請 manselect 。不同的 timeval 設置使使 select() 表現出超時結束、無超時阻塞和輪詢三種特性。由于 timeval 可精確至百萬分之一秒,所以 Windows SetTimer() 根本不算什么。你可以用 select() 做一個超級時鐘。

服務器方程序:
/* File Name: streams.c */
#include
#include
#define TRUE 1
/*
這個程序建立一個套接字,然后開始無限循環;每當它通過循環接收到一個連接,則打印出一個信息。當連接斷開,或接收到終止信息,則此連接結束,程序再接收一個新的連接。命令行的格式是: streams */
main( )
{
int sock, length;
struct sockaddr_in server;
struct sockaddr tcpaddr;
int msgsock;
char buf[1024];
int rval, len;
/*
建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("opening stream socket");
exit(1);
}
/*
使用任意端口命名套接字 */
server.sin_family = AF_INET;
server.sin_port = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("binding stream socket");
exit(1);
}
/*
找出指定的端口號并打印出來 */
length = sizeof(server);
if (getsockname(sock, (struct sockaddr *)&server, &length) < 0) {
perror("getting socket name");
exit(1);
}
printf("socket port #%d/n", ntohs(server.sin_port));
/*
開始接收連接 */
listen(sock, 5);
len = sizeof(struct sockaddr);
do {
msgsock = accept(sock, (struct sockaddr *)&tcpaddr, (int *)&len);
if (msgsock == -1)
perror("accept");
else do{
memset(buf, 0, sizeof(buf));
if ((rval = recv(msgsock, buf, 1024)) < 0)
perror("reading stream message");
if (rval == 0)
printf("ending connection /n");
else
printf("-->%s/n", buf);
}while (rval != 0);
closesocket(msgsock);
} while (TRUE);
/*
因為這個程序已經有了一個無限循環,所以套接字 "sock" 從來不顯式關閉。然而,當進程被殺死或正常終止時,所有套接字都將自動地被關閉。 */
exit(0);
}
客戶方程序:
/* File Name: streamc.c */
#include
#include
#define DATA "half a league, half a league ..."
/*
這個程序建立套接字,然后與命令行給出的套接字連接;連接結束時,在連接上發送
一個消息,然后關閉套接字。命令行的格式是: streamc 主機名 端口號
端口號要與服務器程序的端口號相同 */
main(argc, argv)
int argc;
char *argv[ ];
{
int sock;
struct sockaddr_in server;
struct hostent *hp, *gethostbyname( );
char buf[1024];
/*
建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("opening stream socket");
exit(1);
}
/*
使用命令行中指定的名字連接套接字 */
server.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == 0) {
fprintf(stderr, "%s: unknown host /n", argv[1]);
exit(2);
}
memcpy((char*)&server.sin_addr, (char*)hp->h_addr, hp->h_length);
sever.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
perror("connecting stream socket");
exit(3);
}
if (send(sock, DATA, sizeof(DATA)) < 0)
perror("sending on stream socket");
closesocket(sock);
exit(0);
}

4.Windows socket 程序設計

Windows Sockets 是從 Berkeley Sockets 擴展而來的,其在繼承 Berkeley Sockets 的基礎上,又進行了新的擴充。這些擴充主要是提供了一些異步函數,并增加了符合 WINDOWS 消息驅動特性的網絡事件異步選擇機制。

Windows Sockets 由兩部分組成:開發組件和運行組件。
開發組件: Windows Sockets 實現文檔、應用程序接口 (API) 引入庫和一些頭文件。
運行組件: Windows Sockets 應用程序接口的動態鏈接庫 (WINSOCK.DLL) 。

Microsoft Windows 下開發 Windows Sockets 網絡程序與在 UNIX 環境下開發 Berkeley Sockets 網絡程序有一定的差別,這主要時因為 Windows 是非搶先多任務環境,各任務之間的切換是通過消息驅動的。因此,在 Windows 下開發 Sockets 網絡程序要盡量避開阻塞工作方式,而使用 Windows Sockets 提供的基于消息機制的網絡事件異步存取接口。

Windows Sockets 為了支持 Windows 消息驅動機制,使應用程序開發者能夠方便地處理網絡通信,它對網絡事件采用了基于消息的異步存取策略?;谶@一策略, Windows Sockets 在如下方面作了擴充:

* 異步選擇機制

UNIX Sockets 對于異步事件的選擇是靠調用 select() 函數來查詢的,這種方式對于 Windows 應用程序來說是難以接受的。 Windows Sockets 的異步選擇函數提供了消息機制的網絡事件選擇,當使用它登記的網絡事件發生時, Windows 應用程序相應的窗口函數將收到一個消息,消息中指示了發生的網絡事件,以及與事件相關的一些信息。

* 異步請求函數

在標準 Berkeley Sockets 中,請求服務是阻塞的。 Windows Sockets 除了支持這一類函數外,還增加了相應的異步請求服務函數 WSAASyncGetXByY() 。這些異步請求函數允許應用程序采用異步方式獲取請求信息,并且在請求的服務完成時給應用程序相應的窗口函數發送一個消息。

* 阻塞處理方法

Windows Sockets 為了實現當一個應用程序的套接字調用處于阻塞時,能夠放棄 CPU 讓其它應用程序運行,它在調用處于阻塞時便進入一個叫“ HOOK ”的例程,此例程負責接收和分配 Windows 消息,這使得其它應用程序仍然能夠接收到自己的消息并取得控制權。 Windows Sockets 還提供了兩個函數 (WSASetBlockingHook() WSAUnhookBlockingHook()) 讓用戶設置和取消自己的阻塞處理例程,以支持要求復雜消息處理的應用程序(如多文檔界面)。

* 出錯處理

Windows Sockets 為了和以后多線程環境(如 Windows/NT )兼容,它提供了兩個出錯處理函數 WSAGetLastError() WSASetLastError() 來獲取和設置當前線程的最近錯誤號,而不使用 Berkeley Sockets 中的全局變量 errno h_errno 。

* 啟動與終止

對于所有在 Windows Sockets 上開發的應用程序,在它使用任何 Windows Sockets API 調用之前,必須先調用啟動函數 WSAStartup() ,它完成 Windows Sockets DLL 的初始化;協商版本支持,分配必要的資源。在應用程序完成了對 Windows Sockets 的使用之后,它必須調用函數 WSACleanup() 來從 Windows Sockets 實現中注銷自己,并允許實現釋放為其分配的任何資源。

* 服務器端操作 socket (套接字)

在初始化階段調用 WSAStartup()
此函數在應用程序中初始化 Windows Sockets DLL ,只有此函數調用成功后,應用程序才可以再調用其他 Windows Sockets DLL 中的 API 函數。在程式中調用該函數的形式如下: WSAStartup((WORD)((1<<8|1) ,( LPWSADATA &WSAData) ,其中 (1<<8|1) 表示我們用的是 WinSocket1.1 版本, WSAata 用來存儲系統傳回的關于 WinSocket 的資料。

建立 Socket
  初始化 WinSock 的動態連接庫后,需要在服務器端建立一個 監聽的 Socket ,為此可以調用 Socket() 函數用來建立這個監聽的 Socket ,并定義此 Socket 所使用的通信協議。此函數調用成功返回 Socket 對象,失敗則返回 INVALID_SOCKET( 調用 WSAGetLastError() 可得知原因,所有 WinSocket 的函數都可以使用這個函數來獲取失敗的原因 ) 。

SOCKET PASCAL FAR socket( int af, int type, int protocol )
參數 : af: 目前只提供 PF_INET(AF_INET) ;
type
Socket 的類型 (SOCK_STREAM 、 SOCK_DGRAM) ;
protocol
:通訊協定 ( 如果使用者不指定則設為 0) ;

如果要建立的是遵從 TCP/IP 協議的 socket ,第二個參數 type 應為 SOCK_STREAM ,如為 UDP (數據報)的 socket ,應為 SOCK_DGRAM 。

綁定端口

  接下來要為服務器端定義的這個監聽的 Socket 指定一個地址及端口( Port ),這樣客戶端才知道待會要連接哪一個地址的哪個端口,為此我們要調用 bind() 函數,該函數調用成功返回 0 ,否則返回 SOCKET_ERROR 。
int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR *name,int namelen );

參 數: s Socket 對象名;
name
Socket 的地址值,這個地址必須是執行這個程式所在機器的 IP 地址;
namelen
name 的長度;

如果使用者不在意地址或端口的值,那么可以設定地址為 INADDR_ANY ,及 Port 0 Windows Sockets 會自動將其設定適當之地址及 Port (1024 5000 之間的值 ) 。此后可以調用 getsockname() 函數來獲知其被設定的值。

監聽

當服務器端的 Socket 對象綁定完成之后 , 服務器端必須建立一個監聽的隊列來接收客戶端的連接請求。 listen() 函數使服務器端的 Socket 進入監聽狀態,并設定可以建立的最大連接數 ( 目前最大值限制為 5, 最小值為 1) 。該函數調用成功返回 0 ,否則返回 SOCKET_ERROR 。

int PASCAL FAR listen( SOCKET s, int backlog );
參 數: s :需要建立監聽的 Socket ;
backlog
:最大連接個數;

異步選擇
服務器端的 Socket 調用完 listen ()后,如果此時客戶端調用 connect ()函數提出連接申請的話, Server 端必須再調用 accept() 函數,這樣服務器端和客戶端才算正式完成通信程序的連接動作。為了知道什么時候客戶端提出連接要求,從而服務器端的 Socket 在恰當的時候調用 accept() 函數完成連接的建立,我們就要使用 WSAAsyncSelect ()函數,讓系統主動來通知我們有客戶端提出連接請求了。該函數調用成功 返回 0 ,否則返回 SOCKET_ERROR 。

int PASCAL FAR WSAAsyncSelect( SOCKET s, HWND hWnd,unsigned int wMsg, long lEvent );
參數: s Socket 對象;
hWnd
:接收消息的窗口句柄;
wMsg
:傳給窗口的消息;
lEvent
: 被注冊的網絡事件,也即是應用程序向窗口發送消息的網路事件,該值為下列值 FD_READ 、 FD_WRITE FD_OOB FD_ACCEPT FD_CONNECT 、 FD_CLOSE 的組合,各個值的具體含意為 FD_READ :希望在套接字 S 收到數據時收到消息; FD_WRITE :希望在套接字 S 上可以發送數據時收到消息; FD_ACCEPT :希望在套接字 S 上收到連接請求時收到消息; FD_CONNECT :希望在套接字 S 上連接成功時收到消 息; FD_CLOSE :希望在套接字 S 上連接關閉時收到消息; FD_OOB :希望在套接字 S 上收到帶外數據時收到消息。
  具體應用時, wMsg 應是在應用程序中定義的消息名稱,而消息結構中的 lParam 則為以上各種網絡事件名稱。所以,可以在窗口處理自定義消息函數中使用以下結構來響應 Socket 的不同事件:  

switch(lParam)
{

case FD_READ:

break;
case FD_WRITE


break;

}

服務器端接受客戶端的連接請求
Client 提出連接請求時, Server hwnd 視窗會收到 Winsock Stack 送來我們自定義的一 個消息,這時,我們可以分析 lParam ,然后調用相關的函數來處理此事件。為了使服務器端接受客戶端的連接請求,就要使用 accept() 函數,該函數新建一 Socket 與客戶端的 Socket 相通,原先監聽之 Socket 繼續進入監聽狀態,等待他人的連接要求。該函數調用成功返回一個新產生的 Socket 對象,否則返回 INVALID_SOCKET 。

SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr,int FAR *addrlen );
參數: s Socket 的識別碼;
addr
:存放來連接的客戶端的地址;
addrlen
addr 的長度

結束 socket 連接
結束服務器和客戶端的通信連接是很簡單的,這一過程可以由服務器或客戶機的任一端啟動,只要調用 closesocket() 就可以了,而要關閉 Server 端監聽狀態的 socket ,同樣也是利用此函數。另外,與程序啟動時調用 WSAStartup() 憨數相對應,程式結束前,需要調用 WSACleanup() 來通知 Winsock Stack 釋放 Socket 所占用的資源。這兩個函數都是調用成功返回 0 ,否則返回 SOCKET_ERROR 。

int PASCAL FAR closesocket( SOCKET s );
參 數: s Socket 的識別碼;
int PASCAL FAR WSACleanup( void );
參 數: 無

* 客戶端 Socket 的操作

建立客戶端的 Socket
客戶端應用程序首先也是調用 WSAStartup() 函數來與 Winsock 的動態連接庫建立關系,然后同樣調用 socket() 來建立一個 TCP UDP socket (相同協定的 sockets 才能相通, TCP TCP , UDP UDP )。與服務器端的 socket 不同的是,客戶端的 socket 可以調用 bind() 函數,由自己來指定 IP 地址及 port 號碼;但是也可以不調用 bind() ,而由 Winsock 來自動設定 IP 地址及 port 號碼。

提出連接申請
  客戶端的 Socket 使用 connect() 函數來提出與服務器端的 Socket 建立連接的申請,函數調用成功返回 0 ,否則返回 SOCKET_ERROR

int PASCAL FAR connect( SOCKET s, const struct sockaddr FAR *name, int namelen );
參 數: s Socket 的識別碼;
name
Socket 想要連接的對方地址;
namelen
name 的長度

* 數據的傳送
雖然基于 TCP/IP 連接協議(流套接字)的服務是設計客戶機 / 服務器應用程序時的主流標準,但有些服務也是可以通過無連接協議(數據報套接字)提供的。先介紹一下 TCP socket UDP socket 在傳送數據時的特性: Stream (TCP) Socket 提供雙向、可靠、有次序、不重復的資料傳送。 Datagram (UDP) Socket 雖然提供雙向的通信,但沒有可靠、有次序、不重復的保證,所以 UDP 傳送數據可能會收到無次序、重復的資料,甚至資料在傳輸過程中出現遺漏。由于 UDP Socket 在傳送資料時,并不保證資料能完整地送達對方,所以絕大多數應用程序都是采用 TCP 處理 Socket ,以保證資料的正確性。一般情況下 TCP Socket 的數據發送和接收是調用 send() recv() 這兩個函數來達成,而 UDP Socket 則是用 sendto() recvfrom() 這兩個函數,這兩個函數調用成功發揮發送或接收的資料的長度,否則返回 SOCKET_ERROR

int PASCAL FAR send( SOCKET s, const char FAR *buf,int len, int flags );
參數: s Socket 的識別碼
buf
:存放要傳送的資料的暫存區
len buf
:的長度
flags
:此函數被調用的方式
對于 Datagram Socket 而言,若是 datagram 的大小超過限制,則將不會送出任何資料,并會傳回錯誤值。對 Stream Socket 言, Blocking 模式下,若是傳送系統內的儲存空間不夠存放這些要傳送的資料, send() 將會被 block 住,直到資料送完為止;如果該 Socket 被設定為 Non-Blocking 模式,那么將視目前的 output buffer 空間有多少,就送出多少資料,并不會被 block 住。 flags 的值可設為 0 MSG_DONTROUTE MSG_OOB 的組合。

int PASCAL FAR recv( SOCKET s, char FAR *buf, int len, int flags );
參數: s Socket 的識別碼
buf
:存放接收到的資料的暫存區
len buf
:的長度
flags
:此函數被調用的方式
  對 Stream Socket 言,我們可以接收到目前 input buffer 內有效的資料,但其數量不超過 len 的大小。

* 自定義的 CMySocket 類的實現代碼:
根據上面的知識,自定義了一個簡單的 CMySocket 類,下面是定義的該類的部分實現代碼:

CMySocket::CMySocket() : file:// 類的構造函數
{
WSADATA wsaD;
memset( m_LastError, 0, ERR_MAXLENGTH );
// m_LastError 是類內字符串變量 , 初始化用來存放最后錯誤說明的字符串;
// 初始化類內 sockaddr_in 結構變量,前者存放客戶端地址,后者對應于服務器端地址 ;
memset( &m_sockaddr, 0, sizeof( m_sockaddr ) );
memset( &m_rsockaddr, 0, sizeof( m_rsockaddr ) );
int result = WSAStartup((WORD)((1<<8|1) , &wsaD);// 初始化 WinSocket 動態連接庫 ;
if( result != 0 ) // 初始化失敗;
{ set_LastError( "WSAStartup failed!", WSAGetLastError() );
return;
}
}

//////////////////////////////
CMySocket::~CMySocket() { WSACleanup(); }//
類的析構函數;
////////////////////////////////////////////////////
int CMySocket::Create( void )
{// m_hSocket 是類內 Socket 對象,創建一個基于 TCP/IP Socket 變量,并將值賦給該變量;
if ( (m_hSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP )) == INVALID_SOCKET )

{
set_LastError( "socket() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
///////////////////////////////////////////////
int CMySocket::Close( void )//
關閉 Socket 對象;
{
if ( closesocket( m_hSocket ) == SOCKET_ERROR )
{
set_LastError( "closesocket() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
file:// 重置 sockaddr_in 結構變量;
memset( &m_sockaddr, 0, sizeof( sockaddr_in ) );
memset( &m_rsockaddr, 0, sizeof( sockaddr_in ) );
return ERR_SUCCESS;
}
/////////////////////////////////////////
int CMySocket::Connect( char* strRemote, unsigned int iPort )//
定義連接函數;
{
if( strlen( strRemote ) == 0 || iPort == 0 )
return ERR_BADPARAM;
hostent *hostEnt = NULL;
long lIPAddress = 0;
hostEnt = gethostbyname( strRemote );// 根據計算機名得到該計算機的相關內容;
if( hostEnt != NULL )
{
lIPAddress = ((in_addr*)hostEnt->h_addr)->s_addr;
m_sockaddr.sin_addr.s_addr = lIPAddress;
}
else
{
m_sockaddr.sin_addr.s_addr = inet_addr( strRemote );
}
m_sockaddr.sin_family = AF_INET;
m_sockaddr.sin_port = htons( iPort );
if( connect( m_hSocket, (SOCKADDR*)&m_sockaddr, sizeof( m_sockaddr ) ) == SOCKET_ERROR )
{
set_LastError( "connect() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;

}
///////////////////////////////////////////////////////
int CMySocket::Bind( char* strIP, unsigned int iPort )//
綁定函數;
{
if( strlen( strIP ) == 0 || iPort == 0 )
return ERR_BADPARAM;
memset( &m_sockaddr,0, sizeof( m_sockaddr ) );
m_sockaddr.sin_family = AF_INET;
m_sockaddr.sin_addr.s_addr = inet_addr( strIP );
m_sockaddr.sin_port = htons( iPort );
if ( bind( m_hSocket, (SOCKADDR*)&m_sockaddr, sizeof( m_sockaddr ) ) == SOCKET_ERROR )
{
set_LastError( "bind() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
//////////////////////////////////////////
int CMySocket::Accept( SOCKET s )//
建立連接函數, S 為監聽 Socket 對象名;
{
int Len = sizeof( m_rsockaddr );
memset( &m_rsockaddr, 0, sizeof( m_rsockaddr ) );
if( ( m_hSocket = accept( s, (SOCKADDR*)&m_rsockaddr, &Len ) ) == INVALID_SOCKET )
{
set_LastError( "accept() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
/////////////////////////////////////////////////////
int CMySocket::asyncSelect( HWND hWnd, unsigned int wMsg, long lEvent )
file://
事件選擇函數;
{
if( !IsWindow( hWnd ) || wMsg == 0 || lEvent == 0 )
return ERR_BADPARAM;
if( WSAAsyncSelect( m_hSocket, hWnd, wMsg, lEvent ) == SOCKET_ERROR )
{
set_LastError( "WSAAsyncSelect() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
////////////////////////////////////////////////////
int CMySocket::Listen( int iQueuedConnections )//
監聽函數;

{
if( iQueuedConnections == 0 )
return ERR_BADPARAM;
if( listen( m_hSocket, iQueuedConnections ) == SOCKET_ERROR )
{
set_LastError( "listen() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
////////////////////////////////////////////////////
int CMySocket::Send( char* strData, int iLen )//
數據發送函數;
{
if( strData == NULL || iLen == 0 )
return ERR_BADPARAM;
if( send( m_hSocket, strData, iLen, 0 ) == SOCKET_ERROR )
{
set_LastError( "send() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ERR_SUCCESS;
}
/////////////////////////////////////////////////////
int CMySocket::Receive( char* strData, int iLen )//
數據接收函數;
{
if( strData == NULL )
return ERR_BADPARAM;
int len = 0;
int ret = 0;
ret = recv( m_hSocket, strData, iLen, 0 );
if ( ret == SOCKET_ERROR )
{
set_LastError( "recv() failed", WSAGetLastError() );
return ERR_WSAERROR;
}
return ret;
}
void CMySocket::set_LastError( char* newError, int errNum )
file://WinSock API
操作錯誤字符串設置函數;
{
memset( m_LastError, 0, ERR_MAXLENGTH );
memcpy( m_LastError, newError, strlen( newError ) );
m_LastError[strlen(newError)+1] = '/0';
}
有了上述類的定義,就可以在網絡程序的服務器和客戶端分別定義 CMySocket 對象,建立連接,傳送數據了。例如,為了在服務器和客戶端發送數據,需要在服務器端定義兩個 CMySocket 對象 ServerSocket1 ServerSocket2 ,分別用于監聽和連接,客戶端定義一個 CMySocket 對象 ClientSocket ,用于發送或接收數據,如果建立的連接數大于一,可以在服務器端再定義 CMySocket 對象,但要注意 連接數不要大于五。

VC 中進行 WINSOCK API 編程開發的時候,需要在項目中使用下面三個文件,否則會出現編譯錯誤。
1
WINSOCK.H: 這是 WINSOCK API 的頭文件,需要包含在項目中。
2
WSOCK32.LIB: WINSOCK API 連接庫文件。在使用中,一定要把它作為項目的非缺省的連接庫包含到項目文件中去。
3
WINSOCK.DLL: WINSOCK 的動態連接庫,位于 WINDOWS 的安裝目錄下。

socket通信


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 亚洲人成在线免费观看 | 亚洲综合色网站 | 日韩久操 | 国产精品素人福利 | 亚洲性夜夜综合久久麻豆 | www.不卡视频| 久热这里只精品热在线观看 | 国产一级毛片在线 | 久操免费 | 国产成人高清亚洲一区91 | 久草在线视频精品 | 成人国产精品一级毛片天堂 | 亚洲mv在线观看 | 欧美日韩亚洲成人 | 欧美成人免费高清二区三区 | 国产精品国偷自产在线 | 国产精品热久久毛片 | 亚洲精品一区二区在线播放 | 四虎免费网址 | 黄色.www| 日本一级大黄毛片一级 | 精品国产乱码一区二区三区麻豆 | 中文线码中文高清播放中 | 91久久爱 | 波多野结衣一区二区三区在线观看 | 亚洲精品一区二区综合 | 五月天精品| 成人影院午夜久久影院 | 97视频| 九一国产 | 亚洲精品入口一区二区在线观看 | 国产高清视频免费 | 中文一级国产特级毛片视频 | 色综合网亚洲精品久久久 | 中文字幕在线观看免费视频 | 91在线网站| 久久99精品国产麻豆婷婷 | 99国产精品视频免费观看 | 九九视频免费观看 | 天天操天天摸天天爽 | www色中色|