一、初使化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 | 0 | unsigned short |
iMaxUdpDg | 0 | 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狀態。 |
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 );