Microsoft Windows網路編程第2版 楊合慶 譯 清華大學出版社
對應的英文書名:Network Programming for Microsoft Windows 2nd
1 winsock是網路介面,不是協議。
2 Winsock有1和2兩個版本,通過函數首碼WSA可以區分,Winsock2函
數前有WSA首碼。但有幾個例外WSAStartup、WSACleanup、WSARecvEx、
WSAGetLastError屬於1.1規範。
3 WinCE只支援Winsock1
winsock1 winsock.h wsock32.lib
winsock2 winsock2.h ws2_32.lib
編程擴充 mswsock.h mswsock.dll
4 使用WSAStartup進行Winsock初始化,具體用法見MSDN和書本
結束時使用WSACleanup釋放由Winsock分配資源,也可以讓作業系統自動
釋放,但這不符合Winsock規範。
5 SOCKADDR_IN用來指定IP和連接埠號碼用
struct sockaddr_in {
short sin_family; // 必須為AF_INET
unsigned short sin_port; // 連接埠號碼
struct in_addr sin_addr; // IP,可用inet_add(char* )得IP
char sin_zero[8]; // 填充,使其與SOCKADDR結構長度一樣
};
6 Inter86處理器上,多位元組是小端(little-endian),Internet連網標準的字
節序是大端模式(big-endian),稱為網路位元組序(network-byte)。
主機位元組序轉網路位元組序函數
htonl、WSAHtonl、htons、WSAHtons
網路位元組序轉主機位元組序
ntohl、WSANtohl、ntohs、WSANtohs
7 建立通訊端函數
socket、WSASocket,建立完通訊端後可用下面函數控制通訊端的選項和通訊端
行為:setsockopt、getsockopt、ioctlsocket、WSAIoctl
下面是TCP伺服器端程式樣本
// 模組名:tcpserver.cpp//// 描 述:此例子示範了如何開發一個簡單的TCP伺服器應用程式,// 此應用程式在5150連接埠上監聽TCP串連並接收資料。此// 例子以控制台的形式實現,當接受串連或接收到資料時// 只是簡單的在控制台上列印出資訊。//// 編 譯:注意要設定ws2_32.lib庫,工程-->設定-->串連,將// ws2_32.lib添加到“對象/庫模組”的最後。#include <winsock2.h>#include <stdio.h>void main(void){// 初始化Winsock,使用2.2版本int Ret;WSADATA wsaData;if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)){// 注意:WSAStartup載入失敗,我們不能像平常那樣使用WSAGetLastError// 來得到錯誤碼,但可以根據返回狀態來判斷printf("WSAStartup失敗,錯誤碼 %d\n", Ret);return;}// 建立socketSOCKET ListeningSocket;if ((ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){printf("建立socket失敗,錯誤碼 %d\n", WSAGetLastError());WSACleanup();return;}// 設定SOCKADDR_IN結構體,注意要將連接埠號碼轉換為網路位元組序 SOCKADDR_IN ServerAddr;int Port = 5150;ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(Port);ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);// 將地址資訊與socket綁定if (bind(ListeningSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR){printf("綁定失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(ListeningSocket);WSACleanup();return;}// 監聽用戶端串連,排隊隊列長度為5if (listen(ListeningSocket, 5) == SOCKET_ERROR){printf("監聽失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(ListeningSocket);WSACleanup();return;}printf("正在連接埠 %d 上等待串連。\n", Port);SOCKADDR_IN ClientAddr;int ClientAddrLen = sizeof(ClientAddr);SOCKET NewConnection;if ((NewConnection = accept(ListeningSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen)) == INVALID_SOCKET){printf("接受串連失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(ListeningSocket);WSACleanup();return;}printf("從 %s:%d 接收到一個串連。\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));// 到此為止,有兩種選擇:一個是使用ListeningSocket繼續accept用戶端串連並// 收發資料。另一種是關閉ListeningSocket不再接收用戶端串連。在這我們選擇// 不再接收用戶端串連,僅是簡單的與剛建立的串連NewConnection進行收發資料closesocket(ListeningSocket);// 使用NewConnection進行收發資料,簡單起見我們只是收一些資料並報告接收到的長度char DataBuffer[1024];printf("正在等待接收資料...\n");if ((Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer), 0)) == SOCKET_ERROR){printf("接收資料失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(NewConnection);WSACleanup();return;}printf("成功接收 %d 位元組資料\n", Ret);// 不再做任何事情,所以關閉用戶端串連printf("關閉用戶端串連\n");closesocket(NewConnection);// 程式處理完串連後調用WSACleanupWSACleanup();}
下面是TCP用戶端程式樣本
// 模組名:tcpclient.cpp//// 描 述:此例子示範了如何開發一個簡單的TCP用戶端應用程式,// 此程式只是簡單的往伺服器監聽的5150連接埠上發送一個// "hello"資訊。此例子以控制台的形式實現,當串連成功// 或將資料發送到伺服器時只是簡單的在控制台上列印出資訊。// // 編 譯:注意要設定ws2_32.lib庫,工程-->設定-->串連,將// ws2_32.lib添加到“對象/庫模組”的最後。#include <winsock2.h>#include <stdio.h>void main(int argc, char** argv){// 必須為IP作為命令列參數if (argc <= 1){printf("用法:tcpclient <伺服器IP>。\n");return;}// 初始化Winsock,使用2.2版本int Ret;WSADATA wsaData;if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)){// 注意:WSAStartup載入失敗,我們不能像平常那樣使用WSAGetLastError// 來得到錯誤碼,但可以根據返回狀態來判斷printf("WSAStartup失敗,錯誤碼 %d\n", Ret);return;}// 建立socketSOCKET s;if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){printf("建立socket失敗,錯誤碼 %d\n", WSAGetLastError());WSACleanup();return;}// 設定SOCKADDR_IN結構體,注意要將連接埠號碼轉換為網路位元組序 SOCKADDR_IN ServerAddr;int Port = 5150;ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(Port);ServerAddr.sin_addr.s_addr = inet_addr(argv[1]);printf("正在串連 %s:%d ...\n", inet_ntoa(ServerAddr.sin_addr), ntohs(ServerAddr.sin_port));if (connect(s, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR){printf("無法串連,錯誤碼 %d\n", WSAGetLastError());closesocket(s);WSACleanup();return;}printf("串連成功。\n");printf("嘗試發送“hello”資訊\n");if ((Ret = send(s, "Hello", 5, 0)) == SOCKET_ERROR){printf("發送失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(s);WSACleanup();return;}printf("成功發送 %d 位元組資料\n", Ret);// 不再做任何事情,所以關閉用戶端串連printf("關閉串連\n");closesocket(s);// 程式處理完串連後調用WSACleanupWSACleanup();}
UDP接收端樣本
// 模組名:udpreceiver.cpp//// 描 述:此例子示範了如何開發一個簡單的UDP接收程式,// 它在5150連接埠上等待資料包。例子以控制台的// 形式實現,當接收到資料時只是簡單的在控制// 台上列印出資訊。//// 編 譯:注意要設定ws2_32.lib庫,工程-->設定-->串連,將// ws2_32.lib添加到“對象/庫模組”的最後。#include <winsock2.h>#include <stdio.h>void main(int argc, char** argv){// 初始化Winsock,使用2.2版本int Ret;WSADATA wsaData;if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)){// 注意:WSAStartup載入失敗,我們不能像平常那樣使用WSAGetLastError// 來得到錯誤碼,但可以根據返回狀態來判斷printf("WSAStartup失敗,錯誤碼 %d\n", Ret);return;}// 建立socketSOCKET ReceivingSocket;if ((ReceivingSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET){printf("建立socket失敗,錯誤碼 %d\n", WSAGetLastError());WSACleanup();return;}// 設定SOCKADDR_IN結構體SOCKADDR_IN ReceiverAddr;int Port = 5150;ReceiverAddr.sin_family = AF_INET;ReceiverAddr.sin_port = htons(Port);ReceiverAddr.sin_addr.s_addr = htonl(INADDR_ANY);// 將地址資訊與socket綁定if (bind(ReceivingSocket, (SOCKADDR*)&ReceiverAddr, sizeof(ReceiverAddr)) == SOCKET_ERROR){printf("綁定失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(ReceivingSocket);WSACleanup();return;}printf("正在連接埠 %d 上等待接收資料包。\n", Port);char ReceiveBuf[1024];int BufLength = 1024;SOCKADDR_IN SenderAddr;int SenderAddrSize = sizeof(SenderAddr);if ((Ret = recvfrom(ReceivingSocket, ReceiveBuf, BufLength, 0,(SOCKADDR*)&SenderAddr, &SenderAddrSize)) == INVALID_SOCKET){printf("recvfrom失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(ReceivingSocket);WSACleanup();return;}printf("從 %s:%d 接收 %d 位元組資料\n", inet_ntoa(SenderAddr.sin_addr), ntohs(SenderAddr.sin_port), Ret);closesocket(ReceivingSocket);// 程式處理完串連後調用WSACleanupWSACleanup();}
UDP發送端樣本
// 模組名:udpsender.cpp//// 描 述:此例子示範了如何開發一個簡單的UDP發送程式,// 它會向接收端5150連接埠上發送一個"hello"訊息。// 例子以控制台的形式實現,當資訊發送到伺服器時// 只是簡單的在控制台上列印出資訊。//// 編 譯:注意要設定ws2_32.lib庫,工程-->設定-->串連,將// ws2_32.lib添加到“對象/庫模組”的最後。#include <winsock2.h>#include <stdio.h>void main(int argc, char** argv){// 必須為IP作為命令列參數if (argc <= 1){printf("用法:udpsender <接收端IP>。\n");return;}// 初始化Winsock,使用2.2版本int Ret;WSADATA wsaData;if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)){// 注意:WSAStartup載入失敗,我們不能像平常那樣使用WSAGetLastError// 來得到錯誤碼,但可以根據返回狀態來判斷printf("WSAStartup失敗,錯誤碼 %d\n", Ret);return;}// 建立socketSOCKET SendingSocket;if ((SendingSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET){printf("建立socket失敗,錯誤碼 %d\n", WSAGetLastError());WSACleanup();return;}// 設定SOCKADDR_IN結構體SOCKADDR_IN ReceiverAddr;int Port = 5150;ReceiverAddr.sin_family = AF_INET;ReceiverAddr.sin_port = htons(Port);ReceiverAddr.sin_addr.s_addr = inet_addr(argv[1]);// 給接收端發送資料包if ((Ret = sendto(SendingSocket, "Hello", 5, 0, (SOCKADDR*)&ReceiverAddr, sizeof(ReceiverAddr))) == SOCKET_ERROR){printf("發送失敗,錯誤碼 %d\n", WSAGetLastError());closesocket(SendingSocket);WSACleanup();return;}printf("成功向 %s:%d 發送 %d 位元組資料\n", inet_ntoa(ReceiverAddr.sin_addr), ntohs(ReceiverAddr.sin_port), Ret);// 發送完資料,關閉串連closesocket(SendingSocket);// 程式處理完串連後調用WSACleanupWSACleanup();}