利用winsock編寫網路應用程式服務端的步驟簡述如下
WSAStartup 初始化網路編程庫
socket 建立通訊端
bind 指定地址、連接埠,綁定通訊端
listen 進入監聽狀態
accept 等待接收新串連
send/recv 收發資料
closesocket 關鍵通訊端
WSAStartup 釋放對動態庫的使用
下面詳細介紹各API
1. Winsock初始化
調用int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData )函數
WSAStartup,即WSA(Windows SocKNDs Asynchronous,Windows非同步通訊端)的啟動命令。是Windows下的網路編程介面軟體Winsock1 或 Winsock2 裡面的一個命令(Ps:Winsock 是由Unix下的BSD Socket發展而來,是一個與網路通訊協定無關的編程介面)。
為了在應用程式當中調用任何一個Winsock API函數,首先第一件事情就是必須通過WSAStartup函數完成對Winsock服務的初始化,因此需要調用WSAStartup函數。使用Socket的程式在使用Socket之前必須調用WSAStartup函數。
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested=MAKEWORD(2,2);
if(WSAStartup(wVersionRequested,&wsaData)!=0)
{
//初始化失敗
}
if(wsaData.wVersion != wVersionRequested)
{
//版本不匹配
}
2. 建立通訊端
int socket(int domain, int type, int protocol);
函數說明:
第一個參數指定應用程式使用的通訊協定的協議族,對於TCP/IP協議族,該參數置AF_INET;
第二個參數指定要建立的通訊端類型,流通訊端類型為SOCK_STREAM、資料通訊端類型為SOCK_DGRAM、原始通訊端SOCK_RAW(WinSock介面並不適用某種特定的協議去封裝它,而是由程式自行處理資料包以及協議首部);
第三個參數指定應用程式所使用的通訊協定。此參數可以指定單個協議系列中的不同傳輸協議。在Internet通訊域中,此參數一般取值為0,系統會根據通訊端的類型決定應使用的傳輸層協議。
傳回值:成功返回通訊端描述符,否則返回INVALID_SOCKET。
例子:
if ((m_sk = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
//建立通訊端失敗
}
3. 綁定通訊端
地址結構的定義:
/*
* Socket address, internet style.
*/
struct sockaddr_in {
short sin_family; //協議族
u_short sin_port; //連接埠號碼
struct in_addr sin_addr; //地址資訊
char sin_zero[8];
};
/*
* Internet address (old style... should be updated)
*/
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;
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
注意:s_addr是一個宏,可以方便設定地址
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
注意:s_addr是一個宏,可以方便設定地址
上述定義在winsock2.h中
綁定函數原型:
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
函數說明:
參數列表中,sockfd 表示已經建立的socket編號(描述符);
my_addr 是一個指向sockaddr結構體類型的指標;
參數addrlen表示my_addr結構的長度,可以用sizeof函數獲得。
例子:
sockaddr_in addr;
addr.sin_family = AF_INET; //使用互連網際協議,即IP協議
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
IP地址參數為INADDR_ANY,則由系統核心來自動指定
port為0,則由系統自動指派一個1024~5000之間惟一的連接埠號碼
if (bind(m_sk, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
{
//綁定出錯
}
4. 進入監聽狀態
int listen(int s, int backlog);
函數說明:
s:已綁定的通訊端
backlog:同時能處理的最大串連數
Listen()並未開始接收連線, 只是設定socket 為listen 模式, 真正接收client 端連線的是accept(). 通常listen()會在socket(), bind()之後調用, 接著才調用accept()。
5. 接受串連
int accept(int s,struct sockaddr * addr,int * addrlen);
當有新的串連到來時,accept返回一個新的通訊端描述符。串連請求端的相關資訊儲存在addr中。
參數sockfd
參數sockfd是監聽通訊端,這個通訊端用來監聽一個連接埠,當有一個客戶與伺服器串連時,它使用這個連接埠號碼,而此時這個連接埠號碼正與這個通訊端關聯。當然客戶不知道通訊端這些細節,它只知道一個地址和一個連接埠號碼。
參數addr
這是一個結果參數,它用來接受一個傳回值,這傳回值指定用戶端的地址,當然這個地址是通過某個地址結構來描述的,使用者應該知道這一個什麼樣的地址結構。如果對客戶的地址不感興趣,那麼可以把這個值設定為NULL。
參數len
也是結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的位元組個數。同樣的,它也可以被設定為NULL。
如果accept成功返回,則伺服器與客戶已經正確建立串連了,此時伺服器通過accept返回的通訊端來完成與客戶的通訊
6. 收發資料
int send(int s, const void *buf, int len, int flags);
int recv(int s, void *buf, int len, int flags);
函數返回實際收發的位元組數。出錯返回-1, 需要關閉此串連。
函數預設是阻塞函數,直到發送/接收完成。
注意:如果send 函數傳回值與參數len 不相等,則剩餘的未發送資訊需要再次發送
recv接收到資料之後不會在末尾添加’\0’。
7. 其它API
WSAGetLastError()
擷取上次失敗操作的錯誤狀態
closesocket(int socket)
關閉通訊端
WSACleanup()
終止Winsock 2 DLL (Ws2_32.dll) 的使用