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();
}
下面是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;
}
// 创建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();
}
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;
}
// 创建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();
}