欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

socket通信

系統 1738 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 )。新進程處理此客戶請求,并不需要對其它請求作出應答。服務完成后,關閉此新進程與客戶的通信鏈路,并終止。

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

關閉服務器

客戶方:

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

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

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

從上面所描述過程可知:

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

服務進程一般是先于客戶請求而啟動的。只要系統運行,該服務進程一直存在,直到正常或強迫終止。

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條評論
主站蜘蛛池模板: 亚洲欧美视频一区 | 国产性色视频在线高清 | 十六以下岁女子毛片免费 | aa国产视频一区二区 | 午夜视频在线观看免费视频 | 性夜黄a爽爽免费视频国产 尤物tv在线 | 91成人国产网站在线观看 | 人人插人人澡 | 日韩精品一二三区 | 欧美一二三区在线 | 欧美在线网 | 婷婷久久五月天 | 91福利精品老师国产自产在线 | 欧美精品成人免费视频 | 天天看片天天a免费观看 | 999精品免费视频观看 | 97骚碰 | av在线免费观看播放 | 在线视频一区二区 | 欧美综合成人 | 色综合美国色农夫网 | 久久免费看少妇高潮A片JA小说 | 色综合天天射 | 丝袜美腿一区二区三区 | 国产一区二区三区福利 | 99精品热 | h网站国产 | 一区二区三区视频在线播放 | www国产成人免费观看视频,深夜成人网 | 四虎4hutv永久在线影院 | 成人午夜 | 午夜小视频免费观看 | 中国一级黄色片 | 五月网婷婷 | 网站国产| 精品黑人一区二区三区 | 五月婷婷在线播放 | 激情综合网俺也去 | 看一天影院宅急看在线观看 | 午夜影院18| 操欧美女|