1. 程式人生 > >流式套接字程式設計

流式套接字程式設計

1、網路資料的傳輸是通過套接字來實現的。套接字有三種類型:流式套接字(SOCK_STREAM)資料報套接字(SOCK_DGRAM)及原始套接字(RAW);

2、流式套接字是面向連線的、提供雙向、有序、無重複且無記錄邊界的資料流服務,適用於處理大量資料,可靠性高,但開銷也大。

3、伺服器端程式設計步驟:

(1)呼叫WSAStartup()初始化:此函式在應用程式中初始化Windows Sockets DLL,只有此函式呼叫成功後,應用程式才可以再呼叫其他的API函式。

呼叫形式如下:

int WSAStartup(

WORD wVersionRequested,// 所使用的WinSocket版本

LPWSADATA lpWSAData// 存數系統返回的WinSocket資訊

);

(2)建立socket

初始化WinSock的動態連結庫後,需要在伺服器端建立一個Socket,為此可以呼叫socket()函式來建立這個監聽的Socket,並定義此Socket所使用的通訊協議。

SOCKET socket(

int af,// 目前只提供PF_INET(AF_INET)

int type,// Socket的型別(SOCK_STREAM、SOCK_DGRAM)

int protocol,// 通訊協議(如果使用者不指定,則設為0)

);

呼叫成功後返回Socket物件,失敗則返回INVALID_SOCKET(呼叫WSAGetLastError()可得知原因,所有的函式都可以使用這個函式返回來獲取失敗的原因)。

(3)繫結埠

接下來要為伺服器端定義的監聽Socket指定一個地址以及埠,這樣客戶端才能夠知道要連線到何處,為此需要呼叫bind()函式,該函式呼叫成功返回0,否則返回SOCKET_ERROR。

int bind(

SOCKET s,// Socket物件名

const struct sockaddr FAR *name,// Socket的地址值,即所在及其的IP地址

int namelen// name的長度

);

如果使用者不在意地址或埠的值,那麼可以設定地址為INADDR_ANY,及Port為0,Windows Socket會自動將其設定為適當的地址以及Port(1024到5000之間的值)。此後,可以呼叫getsockname()函式來獲知其被設定的值。

(4)監聽

當伺服器端的Socket物件繫結完成之後,必須建立一個監聽的佇列來接收客戶端的連線請求。listen()函式使伺服器的Socket進入監聽狀態,並設定可以建立的最大連線數,該函式呼叫成功返回0,否則返回SOCKET_ERROR。

int listen(

SOCKET s,// 需要建立監聽的Socket

int backlog,// 最大連線個數

);

伺服器端的Socket呼叫完listen()之後,如果此時客戶端呼叫connect()函式提出連線申請的話,伺服器端必須再呼叫accept()函式,這樣服務端和客戶端才算正式完成通訊程式的連線動作。

為了知道什麼時候客戶端提出連線要求,從而伺服器端的Socket在恰當的時候呼叫accept()函式完成連線的建立,我們就要使用WSAAsyncSelect()函式,讓系統主動通知我們有客戶端提出連線請求了,該函式呼叫成功返回0,呼叫失敗返回SOCKET_ERROR。

int WSAAsyncSelect(

SOCKET s,// Socket物件

HWND hwnd,// 接收訊息的視窗控制代碼

unsigned int wMsg,// 傳給視窗的訊息

long lEvent// 被註冊的網路事件

);

被註冊的網路事件lEvent就是應用程式向視窗傳送訊息的網路事件,該值為下列值的組合:

FD_READ:希望在套接字s收到資料時收到訊息。

FD_WRITE:希望在套接字s上可以傳送資料時收到訊息。

FD_ACCEPT:希望在套接字s上收到連線請求時收到訊息。

FD_CONNECT:希望在套接字s上連線成功是收到訊息。

FD_CLOSE:希望在套接字s上連線關閉時收到訊息。

FD_OOB:希望在套接字s上收到OOB資料時收到訊息。

具體應用時,wMsg是在應用程式中定義的訊息名稱,而訊息結構體中lParam則為以上各種網路事件的名稱。所以,可以在視窗處理自定義訊息的函式中使用以下結構來響應Socket的不同事件:

switch (lParam) {

case FD_READ:

...

break;

case FD_WRITE:

...

break;

...

}

(5)伺服器端接受客戶端的連線請求

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

SOCKET accept(

SOCKET s,// Socket的識別碼

struct sockaddr FAR *addr,// 存放連線的客戶端地址

int FAR *addrlen// 地址長度

);

(6)結束Socket連線

結束服務端和客戶端的連線是很簡單的,這一過程可以由伺服器或客戶機的任一端啟動,只要呼叫closesocket()就可以了,而要關閉服務端監聽狀態的Socket,同樣也是利用此函式。

另外,與程式啟動時呼叫WSAStartup()函式相對應,程式結束前需要呼叫WSACleanup()來通知Winsock Stack釋放Socket所佔用的資源。這兩個函式都是呼叫成功返回0,否則返回SOCKET_ERROR。

int closesocket(

SOCKET s;// Socket的識別碼

);

(7)最後呼叫WSACleanup

int WSACleanup(void);

4、客戶端程式設計步驟:

(1)建立客戶端Socket

客戶端應用程式首先也是呼叫WSAStartup()函式來與Winsock的動態連結庫建立關係,然後同樣呼叫socket()來建立一個TCP或UDP Socket(相同協定的Socket才能想通,TCP對TCP,UDP對UDP)。與服務端Socket不同的是,客戶端的Socket可以呼叫bind(0函式,有自己來指定IP地址以及Port號碼,但是也可以不呼叫bind()函式,而由Winsock來自動設定IP地址以及埠號。

(2)提出連線請求

客戶端的Socket使用connect()函式來提出與服務端的Socket建立連線的申請,函式呼叫成功返回0,否則返回SOCKET_ERROR。

int connect(

SOCKET s,// 服務端Socket的識別碼

const struct sockaddr FAR *name,// Socket想要連線的對方地址

int namelen// 地址長度

);

5、資料的傳送

基於TCP/IP連線協議(流式套接字)的服務是設計客戶機/伺服器應用程式時的主流標準,但有些服務是可以通過無連線協議(資料報套接字)提供的。一般情況下TCP Socket的資料傳送和接收是呼叫send()和recv()兩個函式來達成的,而UDP Socket則是使用sendto()以及recvfrom()這兩個函式,這兩個函式呼叫成功返回傳送或接收資料的長度,否則返回SOCKET_ERROR。

int send(

SOCKET s,// Socket的識別碼

const char FAR *buf,// 存放要傳送的資料的快取區

int len, // buf的長度

int flags// 此函式被呼叫的方式

);

對於Datagram Socket而言,若是Datagram的大小超出限制,則將不會送出任何資料,並會傳回錯誤值。對於Stream Socket而言,在Blocking模式下,若是傳送系統內的儲存空間不夠存放這些要傳送的資料,send()將會被block住,直到資料送完為止;如果該Socket被設定為Non-Blocking模式,那麼output buffer空間有多少,就送出多少資料,並不會被block住。

flags的值可設為0或MSG_DONTROUTE及MSG_OOB的組合。

int recv(

SOCKET s,// Socket的識別碼

char FAR *buf,// 存放接收到的資料的快取區

int len,// buf的長度

int flags// 此函式被呼叫的方式

);