Windows下的網絡應用開發大部分是通過Winsock完成的(除了Winsock 以外還有其他的),Winsock有兩種開發模式,一種是阻塞模式,另一種是非阻塞模式。阻塞模式是基本同步的開發模式,非阻塞模式是基於異步的開發模式。非阻塞模式結合了Windows的消息機制,更符合Windows下的開發。
Winsock的相關函數
每個需要使用Winsock進行網絡開發的Widnows 應用程序都必須包含Winsock2.h(這是的二個版本的Winsock庫),除了這個以外,還有一個靜態庫ws2_32.lib 。在使用它們的時候要對這個庫進行一次初始化,使用完畢之後要釋放該庫,下面分別介紹這兩個函數。
首先來看初始化 ws2_32.dll 動態鏈接庫的函數:
[cpp] int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
); 這個函數是用來初始化 ws2_32.dll動態鏈接庫的,這個動態鏈接庫是所有網絡應用程序都會加載的動態鏈接庫,在使用這個動態鏈接庫時就需要用 WSAStartup() 函數進行初始化。如果不初始化這個動態鏈接庫,其余相關的基於這個動態鏈接庫的網絡函數的調用都會失敗。
參數說明如下:
(1)wVersionRequested : Windows Sockets API 提供的調用方可使用的最高版本號。高位字節指出副版本(修正)號,低位字節指出主版本號。
(2)lpWSAData : 指向 WSADATA 數據結構的的指針,用來接收 Windows sockets 實現的細節。
釋放 ws2_32.dll 動態鏈接庫:
[cpp] WSACleanup();//釋放套接字庫
WSACleanup();//釋放套接字庫 這個函數是結束這個動態鏈接庫的,一般在程序退出時使用。
創建套接字:
[cpp] SOCKET socket(
int af,
int type,
int protocol
);
SOCKET socket(
int af,
int type,
int protocol
); 參數說明如下:
(1) af :指定應用程序使用的通信協議族,對於TCP/IP 協議族,該參數始終為 PF_INET 。也有一些使用的是 AF_INET。AF_INET 是地址族,雖然使用這個沒錯,但還是建議使用 PF_INET.
(2) type :指定要創建的套接字的類型,流套接字類型為 SOCK_STREAM, 數據包套接字類型為 SOCK_DGRAM。前者通常被TCP協議使用,後者通常是被UDP協議使用。
(3) protocal :指定應用程序所使用的通信協議。該參數根據第二個參數的不同而不同,第二個參數為 SOCK_STREAM,該參數為 IPPROTO_TCP ;如果第二個參數為 SOCK_DGRAM,那麼該參數為 IPPROTO_UDP.
該函數的返回值是一個新創建的SOCKET 的套接字的描述符。
關閉套接字:
[cpp] int closesocket(
SOCKET s
);
int closesocket(
SOCKET s
);
程序結束時要對Socket創建的套接字進行關閉,完成資源的釋放。
參數說明如下:
s :socket() 函數創建的套接字描述符。
當創建了一個Socket後,服務器必須要綁定一個IP地址和特定的端口號。客戶程序不需要綁定端口號和IP地址,因為Socket會選擇合適的IP地址和端口號來使用。
綁定IP地址和端口號:
[cpp] int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
參數說明如下:
(1)s :指定待綁定的Socket描述符。
(2)name :指定一個sockaddr 結構,該結構的定義如下:
[cpp] struct sockaddr { u_short sa_family; char sa_data[14]; }; struct sockaddr {
u_short sa_family;
char sa_data[14];
};
函數中提供的參數類型是sockaddr,在實際使用過程中,結構體是 sockaddr_in ,該結構的定義如下:
[cpp] struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
成語變量sin_family 設置為PF_INET ;sin_port 設置為端口號;sin_addr 結構體中只包含一個公用體,in_addr的定義如下:
[cpp] 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; }; 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;
}; 該成員變量時一個整數,一般用函數inet_addr() 把字符串形式的IP地址轉換為unsigned long 整型的整數值。
namelen:指定name緩沖區的長度。
inet_addr函數的原型如下:
[cpp] unsigned long inet_addr(
const char FAR *cp
);
unsigned long inet_addr(
const char FAR *cp
); 參數cp為一個點分多進制的IP地址
inet_addr 函數的逆函數如下:
[cpp] char FAR * inet_ntoa(
struct in_addr in
);
char FAR * inet_ntoa(
struct in_addr in
);
參數為一個addr_in 類型的變量。
監聽端口:
[cpp] int listen(
SOCKET s,
int backlog
);
int listen(
SOCKET s,
int backlog
); 參數說明如下:
(1)s:使流套接字s處於監聽狀態。
(2)backlog :為處於監聽狀態的流套接字s維護一個客戶連接請求隊列。
接受請求:
[cpp] SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
); 服務端程序調用該函數從處於監聽狀態的流套接字的客戶端請求隊列中取出第一個請求並創建一個新的套接字與客戶端進行通信。
參數說明如下:
(1)s:指定監聽狀態的套接字。
(2)addr :用來返回新創建的套接字的地址。
(3)addrlen :用來返回新創建的套接字的地址結構的長度。
連接函數如下:
[cpp] int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
); 客戶端程序調用該函數來完成與遠程服務器端的連接。
參數說明如下:
(1)s :客戶端創建的套接字。
(2)name:該結構中包含了要服務器端中的IP地址和端口號。
(3)namelen:指定name緩沖區的長度。
具體進行通信的函數分為兩類,一類是基於TCP協議的,一類是基於UDP協議的。數據的通信主要體現在數據的收發上,分別看一下這兩種協議的收發數據的函數定義。
基於TCP的發送函數:
[cpp] int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
); 參數說明如下:
(1)s:指定發送端套接字描述符。
(2)buf:指明一個存放應用程序要發送數據的緩沖區。
(3)len:指明實際要發送的數據的字節數。
(4)flags:一般設置為0、
基於TCP的接收函數:
[cpp] int recv( SOCKET s, char FAR *buf, int len, int flags ); int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
參數說明如下:
(1)s:指定接收端套接字描述符。
(2)buf:指定一個緩沖區,用來存放接收到的數據。
(3)len:指定緩沖區的長度。
(1)一般設置為0。
基於UDP的發送函數:
[cpp] int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
);
int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
); 基於UDP的接收函數:
[cpp] int recvfrom( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen ); int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR *from,
int FAR *fromlen
);
字節順序:
在Socket 套接字編程中,傳輸數據的排列順序以網絡字節順序和主機字節順序為主。通常情況下,如果用戶將數據通過網絡發送時,需要將數據轉換成以網絡字節順序排列,否則可能造成數據損壞。如果用戶是將網絡接收到的數據存儲在本地計算機上,那麼需要將數據轉換成以主機字節順序排列。
注意:IP地址結構 in_addr 中的成員S_addr 的值均是以網絡字節順序排列。
在Winsock 中提供了幾個關於網絡字節順序與主機字節順序之間的轉換函數。函數定義如下:
本地字節順序轉換為網絡字節順序:
[cpp] u_short htons( u_short hostshort );
u_long htonl( u_long hostlong );
unsigned long inet_addr( const char FAR *cp );unsigned long inet_addr( const char FAR *cp );//將一個字符串IP轉換到以網絡字節順序排列的IP地址
u_short htons( u_short hostshort );
u_long htonl( u_long hostlong );
unsigned long inet_addr( const char FAR *cp );unsigned long inet_addr( const char FAR *cp );//將一個字符串IP轉換到以網絡字節順序排列的IP地址 網絡字節順序轉換為本地字節順序:
[cpp]
[cpp] u_short ntohs( u_short netshort );
u_long ntohl( u_long netlong );
char FAR * inet_ntoa( struct in_addr in );//將一個以網絡字節順序排列的IP地址轉換為衣蛾字符串IP
u_short ntohs( u_short netshort );
u_long ntohl( u_long netlong );
char FAR * inet_ntoa( struct in_addr in );//將一個以網絡字節順序排列的IP地址轉換為衣蛾字符串IP
簡單的通信程序:
下面我們用Windock 寫一個基於TCP和UDP的“Hello World”小程序。
基於TCP協議的“Hello World !”
服務器端代碼編寫的流程如下:
[cpp] WSAStartup() -> socket() -> bind() -> listen() -> accept() -> send() / recv() ->closesocket() -> WSACleanup()
WSAStartup() -> socket() -> bind() -> listen() -> accept() -> send() / recv() ->closesocket() -> WSACleanup() 只要把這些函數依次寫完,服務器端的代碼就寫完了。
服務器端代碼如下:
[cpp] #include<windows.h>
#pragma comment (lib, "ws2_32")//顯式連接套接字庫
int main(int argc, char *argv)
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);//初始化套接字庫
SOCKET s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
sockaddr_in socketaddr;
socketaddr.sin_family = PF_INET;
socketaddr.sin_addr.S_un.S_addr = inet_addr("127.1.1.0");
socketaddr.sin_port = htons(827);
bind(s,(SOCKADDR*)&socketaddr,sizeof(SOCKADDR));
listen(s,1);
SOCKADDR clientAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clientSock;
clientSock = accept(s,(SOCKADDR*)&clientAddr,&nSize);
send(clientSock,"hello client \r\n",strlen("hello client \r\n")+sizeof(char),NULL);
closesocket(clientSock);
closesocket(s);
WSACleanup();//釋放套接字庫
return 0;
}
#include<windows.h>
#pragma comment (lib, "ws2_32")//顯式連接套接字庫
int main(int argc, char *argv)
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);//初始化套接字庫
SOCKET s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
sockaddr_in socketaddr;
socketaddr.sin_family = PF_INET;
socketaddr.sin_addr.S_un.S_addr = inet_addr("127.1.1.0");
socketaddr.sin_port = htons(827);
bind(s,(SOCKADDR*)&socketaddr,sizeof(SOCKADDR));
listen(s,1);
SOCKADDR clientAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clientSock;
clientSock = accept(s,(SOCKADDR*)&clientAddr,&nSize);
send(clientSock,"hello client \r\n",strlen("hello client \r\n")+sizeof(char),NULL);
closesocket(clientSock);
closesocket(s);
WSACleanup();//釋放套接字庫
return 0;
}
客戶端的代碼編寫流程如下:
[cpp] WSAStartup() -> socket() -> connect() -> send() / recv() ->closesocket() -> WSACleanup()
WSAStartup() -> socket() -> connect() -> send() / recv() ->closesocket() -> WSACleanup()
客戶端的流程比服務端的流程要少一些,主要是省去了綁定IP和端口、監聽等一些步驟。
客戶端代碼如下:
[cpp] #include<stdio.h>
#include<winsock.h>
#pragma comment (lib,"ws2_32")//顯示連接套接字庫
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);//初始化套接字庫
SOCKET s = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);//創建TCP套接字
sockaddr_in socketAddr;
socketAddr.sin_family = PF_INET;
socketAddr.sin_addr.S_un.S_addr = inet_addr("127.1.1.0");
socketAddr.sin_port = htons(827);
connect(s,(SOCKADDR*)&socketAddr,sizeof(SOCKADDR));
char szBuffer[MAXBYTE] = {0};
recv(s,szBuffer,MAXBYTE,NULL);
printf("szBuffer = %s \r\n",szBuffer);
closesocket(s);
WSACleanup();//釋放套接字庫
return 0;
}
#include<stdio.h>
#include<winsock.h>
#pragma comment (lib,"ws2_32")//顯示連接套接字庫
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);//初始化套接字庫
SOCKET s = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);//創建TCP套接字
sockaddr_in socketAddr;
socketAddr.sin_family = PF_INET;
socketAddr.sin_addr.S_un.S_addr = inet_addr("127.1.1.0");
socketAddr.sin_port = htons(827);
connect(s,(SOCKADDR*)&socketAddr,sizeof(SOCKADDR));
char szBuffer[MAXBYTE] = {0};
recv(s,szBuffer,MAXBYTE,NULL);
printf("szBuffer = %s \r\n",szBuffer);
closesocket(s);
WSACleanup();//釋放套接字庫
return 0;
}
基於UDP協議的“Hello World !”
UDP客戶端與服務器的編寫方法與TCP的相似,只要主要其中的差別就行了。
服務器端代碼如下:
[cpp] #include<winsock.h>
#include <stdio.h>
#pragma comment (lib,"ws2_32")
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
SOCKET s=socket(PF_INET,SOCK_STREAM,IPPROTO_UDP);
sockaddr_in sockAddr;
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sockAddr.sin_family = PF_INET;
sockAddr.sin_port = htons(827);
bind(s,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));
sockaddr_in clientAddr;
int len=sizeof(sockaddr_in);
char buf[MAXBYTE] = {0};
recvfrom(s,buf,MAXBYTE,0,(SOCKADDR*)&clientAddr,&len);
printf("%s \r\n",buf);
sendto(s,"hello world client",strlen("hello world client")+sizeof(char),0,(SOCKADDR*)&clientAddr,sizeof(SOCKADDR));
closesocket(s);
WSACleanup();
return 0;
}
#include<winsock.h>
#include <stdio.h>
#pragma comment (lib,"ws2_32")
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
SOCKET s=socket(PF_INET,SOCK_STREAM,IPPROTO_UDP);
sockaddr_in sockAddr;
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sockAddr.sin_family = PF_INET;
sockAddr.sin_port = htons(827);
bind(s,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));
sockaddr_in clientAddr;
int len=sizeof(sockaddr_in);
char buf[MAXBYTE] = {0};
recvfrom(s,buf,MAXBYTE,0,(SOCKADDR*)&clientAddr,&len);
printf("%s \r\n",buf);
sendto(s,"hello world client",strlen("hello world client")+sizeof(char),0,(SOCKADDR*)&clientAddr,sizeof(SOCKADDR));
closesocket(s);
WSACleanup();
return 0;
}
客戶端代碼如下:
[cpp] #include<winsock.h>
#include<stdio.h>
#pragma comment (lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
SOCKET s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
sockaddr_in sockAddr;
sockAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(827);
sendto(s,"hello world server",strlen("hello world server")+sizeof(char),0,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));
sockaddr_in clientAddr;
int len=sizeof(sockaddr_in);
char buf[MAXBYTE] = {0};
recvfrom(s,buf,MAXBYTE,0,(SOCKADDR*)&clientAddr,&len);
printf("%s \r\n",buf);
closesocket(s);
WSACleanup();
return 0;
}
#include<winsock.h>
#include<stdio.h>
#pragma comment (lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
SOCKET s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
sockaddr_in sockAddr;
sockAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(827);
sendto(s,"hello world server",strlen("hello world server")+sizeof(char),0,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));
sockaddr_in clientAddr;
int len=sizeof(sockaddr_in);
char buf[MAXBYTE] = {0};
recvfrom(s,buf,MAXBYTE,0,(SOCKADDR*)&clientAddr,&len);
printf("%s \r\n",buf);
closesocket(s);
WSACleanup();
return 0;
}