天天看點

Winsock程式設計基礎介紹 .

相信很多人都對網絡程式設計感興趣,下面我們就來介紹,在網絡程式設計中應用最廣泛的程式設計接口winsock api.

使用winsock api的程式設計,應該了解一些tcp/ip的基礎知識.雖然你可以直接使用winsock

api來寫網絡應用程式,但是,要寫出優秀的網絡應用程式,還是必須對tcp/ip協定有一些了解的.

1. tcp/ip協定與winsock網絡程式設計接口的關系.

在開始之前,我們先說一下winsock和tcp/ip到底是什麼關系.

我碰到很多人問我:怎樣使用winsock協定程式設計?其實,這話說的有點錯誤,winsock并不是一種網絡協定,他隻是一個網絡程式設計接口,也就是說,他不是協定,但是他可以通路很多種網絡協定,你可以把他當作一些協定的封裝.現在的winsock已經基本上實作了與協定無關.你可以使用winsock來調用多種協定的功能.

那麼,winsock和tcp/ip協定到底是什麼關系呢?實際上,winsock就是tcp/ip協定的一種封裝,你可以通過調用winsock的接口函數來調用tcp/ip的各種功能.例如我想用tcp/ip協定發送資料,你就可以使用winsock的接口函數send()來調用tcp/ip的發送資料功能,至于具體怎麼發送資料,winsock已經幫你封裝好了這種功能.

2.tcp/ip協定介紹

現在來介紹一些tcp/ip的原理.tcp/ip協定包含的範圍非常的廣,他是一種四層協定,包含了各種,硬體軟體需求的定義,我們這裡隻介紹軟體方面的知識.tcp/ip協定确切的說法應該是tcp/udp/ip協定.

udp協定(user datagram protocol使用者資料報協定).是一種保護消息邊界的,不保障可靠資料的傳輸.

tcp協定(transmission control protocol

傳輸控制協定).是一種流傳輸的協定.他提供可靠的,有序的,雙向的,面向連接配接的傳輸.

3.保護消息邊界和流

那麼什麼是保護消息邊界和流呢?

保護消息邊界,就是指傳輸協定把資料當作一條獨立的消息在網上傳輸,接收端隻能接收獨立的消息.也就是說存在保護消息邊界,接收端一次隻能接收發送端發出的一個資料包.而面向流則是指無保護消息保護邊界的,如果發送端連續發送資料,接收端有可能在一次接收動作中,會接收兩個或者更多的資料包.

我們舉個例子來說,例如,我們連續發送三個資料包,大小分别是2k,

4k , 8k,這三個資料包,都已經到達了接收端的網絡堆棧中,如果使用udp協定,不管我們使用多大的接收緩沖區去接收資料,我們必須有三次接收動作,才能夠把所有的資料包接收完.而使用tcp協定,我們隻要把接收的緩沖區大小設定在14k以上,我們就能夠一次把所有的資料包接收下來.隻需要有一次接收動作.

這就是因為udp協定的保護消息邊界使得每一個消息都是獨立的.而流傳輸,卻把資料當作一串資料流,他不認為資料是一個一個的消息.

是以有很多人在使用tcp協定通訊的時候,并不清楚tcp是基于流的傳輸,當連續發送資料的時候,他們時常會認識tcp會丢包.其實不然,因為當他們使用的緩沖區足夠大時,他們有可能會一次接收到兩個甚至更多的資料包,而很多人往往會忽視這一點,隻解析檢查了第一個資料包,而已經接收的其他資料包卻被忽略了.是以大家如果要作這類的網絡程式設計的時候,必須要注意這一點.

4。winsock程式設計簡單流程

下面我們介紹一下win32平台的winsock程式設計方法.通訊則必須有伺服器端,和用戶端.我們簡單介紹tcp伺服器端的大體流程.

對于任何基于winsock的程式設計首先我們必須要初始化winsock dll庫.

int wsastarup( word wversionrequested , lpwsadata lpwsadata ).

wversionrequested是我們要求使用的winsock的版本.

調用這個接口函數可以幫我們初始化winsock .然後我們必須建立一個套接字(socket). socket socket( int

af , int type , int protocol );

套接字可以說是winsock通訊的核心.winsock通訊的所有資料傳輸,都是通過套接字來完成的,套接字包含了兩個資訊,一個是ip位址,一個是port端口号,使用這兩個資訊,我們就可以确定網絡中的任何一個通訊節點.

當我們調用了socket()接口函數建立了一個套接字後,我們必須把套接字與你需要進行通訊的位址建立聯系,我們可以通過綁定函數來實作這種聯系.

int bind(socket s , const struct sockaddr far* name , int namelen ) ;

struct sockaddr_in{

 short         sin_family ;

 u_short         sin_prot ;

 struct in_addr  sin_addr ;

 char            sin_sero[8] ;

}

就包含了我們需要建立連接配接的本地的位址,包括,位址族,ip和端口信

息.sin_family字段我們必須把他設為af_inet,這是告訴winsock使

用的是ip位址族.sin_prot 就是我們要用來通訊的端口号.sin_addr

就是我們要用來通訊的ip位址資訊.

在這裡,必須還得提一下有關'大頭(big-endian)'小頭(little-endian)'.

因為各種不同的計算機處理資料時的方法是不一樣的,intel 86處理

器上是用'小頭'形勢來表示多位元組的編号,就是把低位元組放在前面,

把高位元組放在後面,而網際網路标準卻正好相反,是以,我們必須把主機

位元組轉換成網絡位元組的順序.winsock api提供了幾個函數.

把主機位元組轉化成網絡位元組的函數;

u_long htonl( u_long hostlong );

u_short htons( u_short hostshort );

把網絡位元組轉化成主機位元組的函數;

u_long ntohl( u_long netlong ) ;

u_short ntohs( u_short netshort ) ;

這樣,我們設定ip位址,和port端口時,就必須把主機位元組轉化成網絡

位元組後,才能用bind()函數來綁定套接字和位址.

當綁定完成之後,伺服器端必須建立一個監聽的隊列來接收用戶端的

連接配接請求.

int listen( socket s ,int backlog );

這個函數可以讓我們把套接字轉成監聽模式.

如果用戶端有了連接配接請求,我們還必須使用

int accept( socket s , struct sockaddr far* addr , int far* addrlen );

來接受用戶端的請求.

現在我們基本上已經完成了一個伺服器的建立,

而用戶端的建立的流程則是初始化winsock ,然後建立socket套接字

,再使用

int connect( socket s , const struct sockaddr far* name , int namelen ) ;

來連接配接服務端.

下面是一個最簡單的建立伺服器端和用戶端的例子:

伺服器端的建立 :

wsadata wsd ;

socket slisten ;

socket sclient ;

uint port = 800 ;

int iaddrsize ;

struct sockaddr_in local , client ;

wsastartup( 0x11 , &wsd );

slisten = socket ( af_inet , sock_stream , ippoto_ip ) ;

local.sin_family = af_inet ;

local.sin_addr = htonl( inaddr_any ) ;

local.sin_port = htons( port ) ;

bind( slisten , (struct sockaddr*)&local , sizeof( local ) ) ;

listen( slisten , 5 ) ;

sclient = accept( slisten , (struct sockaddr*)&client , &iaddrsize ) ;

用戶端的建立:

char szip[] = "127.0.0.1" ;

struct sockaddr_in server ;

sclient = socket ( af_inet , sock_stream , ippoto_ip ) ;

server.sin_family = af_inet ;

server.sin_addr = inet_addr( szip ) ;

server.sin_port = htons( port );

connect( sclient , (struct sockaddr*)&server , sizeof( server ) ) ;

當伺服器端和用戶端建立連接配接以後,無論是用戶端,還是伺服器端都

可以使用

int send( socket s , const char far* buf , int len , int flags );

int recv( socket s , char far* buf , int len , int flags );

函數來接收和發送資料,因為,tcp連接配接是雙向的.

當要關閉通訊連結的時候,任何一方都可以調用

int shutdown( socket s , int how ) ;

來關閉套接字的指定功能。再調用

int closesocket( socket s) ;

來關閉套接字句柄。這樣一個通訊過程就算完成了。

注意:上面的代碼沒有任何檢查函數傳回值,如果你作網絡程式設計就一定要

檢查任何一個winsock api函數的調用結果,因為很多時候函數調用

并不一定成功.上面介紹的函數,傳回值類型是int的話,如果函數調

用失敗的話,傳回的都是socket_error.

5。winsock程式設計的五種模型

上面介紹的僅僅是最簡單的winsock通訊的方法,而實際中很多網絡

通訊的卻很多難以解決的意外情況.

例如,winsock提供了兩種套接字模式:鎖定和非鎖定.當我們使用鎖

定套接字的時候,我們使用的很多函數,例如accpet,send,recv等等,

如果沒有資料需要處理,這些函數都不會傳回,也就是說,你的應用程

序會阻塞在那些函數的調用處.而 如果使用非阻塞模式,調用這些函

數,不管你有沒有資料到達,他都會傳回,是以,有可能我們在非阻塞

模式裡,調用這些函數大部分的情況下會傳回失敗,是以就需要我們

來處理很多的意外出錯.

這顯然不是我們想要看到的情況.我們可以采用winsock的通訊模型

來避免這些情況的發生。

winsock提供了五種套接字i/o模型來解決這些問題.他們分别是

select(選擇),wsaasyncselect(異步選擇),

wsaeventselect (事件選擇), overlapped(重疊) , completion

port(完成端口) .

我們在這裡詳細介紹一下select,wsaasyncselect兩種模型.

select模型是最常見的i/o模型.

使用

int select( int nfds , fd_set far* readfds , fd_set far* writefds , fd_set far* exceptfds ,

      const struct timeval far * timeout ) ;

函數來檢查你要調用的socket套接字是否已經有了需要處理的資料.

select包含三個socket隊列,分别代表:

readfds ,檢查可讀性,writefds,檢查可寫性,exceptfds,例外資料.

timeout是select函數的傳回時間.

例如,我們想要檢查一個套接字是否有資料需要接收,我們可以把套

接字句柄加入可讀性檢查隊列中,然後調用select,如果,該套接字沒

有資料需要接收,select函數會把該套接字從可讀性檢查隊列中删除

掉,是以我們隻要檢查該套接字句柄是否還存在于可讀性隊列中,就

可以知道到底有沒有資料需要接收了.

winsock提供了一些宏用來操作套接字隊列fd_set.

fd_clr( s,*set) 從隊列set删除句柄s.

fd_isset( s, *set) 檢查句柄s是否存在與隊列set中.

fd_set( s,*set )把句柄s添加到隊列set中.

fd_zero( *set ) 把set隊列初始化成空隊列.

wsaasyncselect(異步選擇)模型:

wsaasyncselect模型就是把一個視窗和套接字句柄建立起連接配接,套接

字的網絡事件發生時時候,就會把某個消息發送到視窗,然後可以在

視窗的消息響應函數中處理資料的接收和發送.

int wsaasyncselect( socket s, hwnd hwnd , unsigned int wmsg , long levent ) ;

這個函數可以把套接字句柄和視窗建立起連接配接,

wmsg 是我們必須自定義的一個消息.

levent就是制定的網絡事件.包括fd_read , fd_write , fd_accept

, fd_connect , fd_close .

幾個事件.

例如,我需要接收fd_read , fd_write , fd_close的網絡事件.可

以調用

wsaasyncselect( s , hwnd , wm_socket , fd_read | fd_write | fd_close ) ;

這樣,當有fd_read , fd_write或者

fd_close網絡事件時,視窗

hwnd将會收到wm_socket消息,消息參數的lparam标志了是什麼事件

發生.

其實大家應該見過這個模型,因為mfc的csocket類,就是使用這個模

型.

繼續閱讀