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; } // 创建socket SOCKET 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; } // 监听客户端连接,排队队列长度为5 if (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); // 程序处理完连接后调用WSACleanup WSACleanup(); }
// 模块名: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; } // 创建socket SOCKET 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); // 程序处理完连接后调用WSACleanup WSACleanup(); }
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; } // 创建socket SOCKET 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); // 程序处理完连接后调用WSACleanup WSACleanup(); }
// 模块名: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; } // 创建socket SOCKET 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); // 程序处理完连接后调用WSACleanup WSACleanup(); }