天天看點

Winsock程式設計 入門 (zz)

一、初使化Winsock

如果沒有初使化的話,所有的Winsock函數操作都回失敗(反回SOCKET_ERROR),錯誤代碼為WSANOTINITIALISED。

初使化函數:

int WSAStartup(WORD 版本号,LPWSADATA pWSADATA)

版本号的建立可以用用宏:MAKEWORD(x,y)

WSADATA結構:

{

WORD 版本

WORD 高版本

char[] 描述

char[] 系統狀态

unsigned short iMaxSockets(相容低版本保留)

unsigned short iMaxUdpDg(相容低版本保留)

char Far* lpVendorInfo也是相容保留

}

這是我機子上連結後的運作情況

WSAStartup(MAKEWORD(2,2),&wsaData);

wVersion 514 unsigned short
wHighVersion 514 unsigned short
szDescription 0x0012fd18 "WinSock 2.0" char [257]
szSystemStatus 0x0012fe19 "Running" char [129]
iMaxSockets unsigned short
iMaxUdpDg unsigned short
lpVendorInfo 0xcccccccc <錯誤的指針> char *

514就是0x202,也是我們的版本号。最後三項被忽略了

下面一張表是各個平台的支援的winsock版本

Platform Winsock Version
Windows 95 1.1 (2.2)
Windows 98 2.2
Windows Me 2.2
Windows NT 4.0 2.2
Windows 2000 2.2
Windows XP 2.2
Windows CE 1.1

int WSACleanup():

終止使用Winsock函數。

二、錯誤資訊

使用當Winsock函數傳回SOCKET_ERROR時用int WSAGetLastError(void)檢測錯誤代碼。錯誤的代碼所對應的錯誤名稱可以在winsock.h或winsock2.h裡找到。

h_errno為該指定的宏。

三、選擇一個協定

這裡簡單講講通過Internet Protocol(IP)協定建立最基本的Winsock。之是以現在有很大一部分的winsock程式都用它,最主要的原因是它具有廣泛的通用性。winsock還可以用别的協定,比如IPX之類的。

   從設計上講,IP是連接配接協定但不是資料傳輸協定。我們可以用Two higher-level protocols-Transmision Control Protocol(TCP)或者是User Datagram Protocol(UDP),他們都是通過IP,我們一起講就是TCP/IP,UDP/IP。如果你要用IPv4(IP version 4),那你必須要要知道怎樣使用IPv4  

使用IPv4

在IPv4裡面,計算機的配置設定的一個位址是32位,當用戶端想通過TCP或者UDP連接配接,那必須要知道主機的IP位址和端口。同樣,主機要監聽用戶端的請求,那必須要表明一個IP位址和端口。在Winsock裡面,程式表明IP位址和服務端口資訊是通過SOCKADDR_IN結構。他的聲明如下:

struct sockaddr_in

{

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

struct in_addr {

union {

struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

struct { u_short s_w1,s_w2; } S_un_w;

u_long S_addr;

} S_un;

sin_family必須填AF_INET告訴Winsock我們用的是IP位址

sin_port說明我們選擇哪個TCP或者UDP的端口作為我們的通訊端口,對了,有些端口号保留給一些服務,比如說FTP,HTTP等

sin_addr存儲IPV4位址用4個位元組,就像無符号長整型(DWORD),IP位址在網際網路上一般用形如a.b.c.d格式。

sin_zero隻不過是讓SOCKADDR_IN和SOCKADDR結構大小一樣。

下面是一個很有用的函數,把a.b.c.d格式的IP位址轉成無符号長整型。

unsigned long inet_addr(const char FAR* cp);

位元組順序

不同的計算機處理數字有兩種形式,big-endian和little-endian型式(

little-endian格式的資料,例如0X12345678以(0X78 0X56 0X34 0X12)方式儲存、

big-endian格式的資料,例如0X12345678以(0X12 0X34 0X56 0X78)方式儲存 ),這依賴于他們是怎麼設計的,比如Intel的x86處理器,多位元組是用little-endian型式。IP位址和和端口在電腦中是多位元組存放的,他們是host-byte順序,然而當IP位址和端口通過網絡時,必須轉成big-endian形式,也就是network-byte順序

  有一系列函數完成兩者之間的轉換。比如

host-byte序轉network-byte序

u_long htonl(u_long hostlong);

int WSAHtonl( SOCKET s, u_long hostlong, u_long FAR * lpnetlong );

u_short htons(u_short hostshort);

int WSAHtons( SOCKET s, u_short hostshort, u_short FAR * lpnetshort );

network-byte序轉host-byte序

u_long ntohl(u_long netlong);

int WSANtohl( SOCKET s, u_long netlong, u_long FAR * lphostlong );

u_short ntohs(u_short netshort);

int WSANtohs( SOCKET s, u_short netshort, u_short FAR * lphostshort );

例:

SOCKADDR_IN addr;

INT port = 8080;

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr("216.239.57.99");

addr.sin_port = htons(port);

四、建立socket

通過API SOCKET socket(int af,int type,int protocol);

第一個參數是協定的位址類别,比如我們前面講用的是IPv4,那麼af就是AF_INET,

第二個參數是協定的socket類型,你用TCP/IP時,type=SOCK_STREAM,你用UDP/IP時,type=SOCK_DGRAM,

第三個參數是協定是(未詳),如果是TCP的話,則該處是IPPROTO_TCP,如果是UDP的話,則該處是IPPROTO_UDP

五、伺服器API函數

伺服器是一個程序,用來等待不定數目的用戶端連接配接,響應用戶端的請求。一個伺服器必須有一個可供用戶端定位的名字,在TCP/IP裡,這個名字是IP位址和端口。

第一步:用socket或者WSASocket建立socket,并用bind綁定

第二步:socket進入監聽模式。(listen)

最後:當用戶端送出請求時響應請求。(accept或者WSAAccept)

綁定(Binding)

int bind( SOCKET s, const struct sockaddr FAR* name, int namelen );

第一個參數是要綁定的socket;

第二個參數是表明你在使用的協定

第三個參數表明你指定協定位址結構的長度。

例:

SOCKET s;

SOCKADDR_IN addr;

int port = 5555;

s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

addr.sin_family = AF_INET;

addr.sin_port = htons(port);

addr.sin_addr.s_addr = htonl(INADDR_ANY);

bind(s, (SOCKADDR *)&addr, sizeof(addr));

監聽(Listening)

把socket轉成監聽模式。bind隻不過是綁定,listen是告知socket進入等待進入的連接配接。

int listen( SOCKET s, int backlog );

第一個參數是綁定過的socket

第二個參數是最大隊列長度,比如說這個數設為二,與此同時有三個客戶連入,那麼先進來的二個進入隊列,第三個則會收到WSAECONNREFUSED錯誤資訊。注意伺服器Accept了一個連接配接,這個連接配接就會從隊列中移除。

如果你沒bind而直接listen的話會收到 WSAEINVAL 出錯資訊。

同意連接配接(Accepting Connectino)

SOCKET accept( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );

第二個參數是你收到的用戶端位址。

第三個參數addrlen 表明addr的長度

六、用戶端API函數

第一步:建立socket

第二步:設定你要連接配接對象的SOCKADDR位址

第三步:用connect 或者WSAConnect連接配接。

TCP狀态

  起初每個socket都是CLOSED狀态,當用戶端初使化一個連接配接,他發送一個SYN包到伺服器,用戶端進入SYN_SENT狀态。

伺服器接收到SYN包,回報一個SYN-ACK包,用戶端接收後返饋一個ACK包用戶端變成ESTABLISHED狀态,如果長時間沒收到SYN-ACK包,用戶端逾時進入CLOSED狀态。

  當伺服器綁定并監聽某一端口時,socket的狀态是LISTEN,當客戶企圖建立連接配接時,伺服器收到一個SYN包,并回報SYN-ACK包。伺服器狀态變成SYN_RCVD,當用戶端發送一個ACK包時,伺服器socket變成ESTABLISHED狀态。

  當一個程式在ESTABLISHED狀态時有兩種圖徑關閉它, 第一是主動關閉,第二是被動關閉。如果你要主動關閉的話,發送一個FIN包。當你的程式closesocket或者shutdown(标記),你的程式發送一個FIN包到peer,你的socket變成FIN_WAIT_1狀态。peer回報一個ACK包,你的socket進入FIN_WAIT_2狀态。如果peer也在關閉連接配接,那麼它将發送一個FIN包到你的電腦,你回報一個ACK包,并轉成TIME_WAIT狀态。

  TIME_WAIT狀态又号2MSL等待狀态。MSL意思是最大段生命周期(Maximum Segment Lifetime)表明一個包存在于網絡上到被丢棄之間的時間。每個IP包有一個TTL(time_to_live),當它減到0時則包被丢棄。每個路由器使TTL減一并且傳送該包。當一個程式進入TIME_WAIT狀态時,他有2個MSL的時間,這個充許TCP重發最後的ACK,萬一最後的ACK丢失了,使得FIN被重新傳輸。在2MSL等待狀态完成後,socket進入CLOSED狀态。

  被動關閉:當程式收到一個FIN包從peer,并回報一個ACK包,于是程式的socket轉入CLOSE_WAIT狀态。因為peer已經關閉了,是以不能發任何消息了。但程式還可以。要關閉連接配接,程式自已發送給自已FIN,使程式的TCP socket狀态變成LAST_ACK狀态,當程式從peer收到ACK包時,程式進入CLOSED狀态。

Winsock程式設計 入門 (zz)

connect

int connect( SOCKET s, const struct sockaddr FAR* name, int namelen );

第二個參數是你要連接配接的名字

第三個參數是你加接的名字參數的長度

如果連接配接失敗了則返饋WSAECONNREFUSED錯誤。

send和WSASend

int send( SOCKET s, const char FAR * buf, int len, int flags );

第二個參數是要發送的資料。

第三個參數是發送資料的長度。

第四個參數可以是0,MSG_DONTROUTE或者是MSG_OOB,這幾個參數之間能用or連接配接。

正常傳回:發送的位元組。

int WSASend(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

最後兩個參數用于重疊I/O,重疊I/O是一個種異步I/O模型。

WSASendDisconnect

int WSASendDisconnect ( SOCKET s, LPWSABUF lpOutboundDisconnectData );