Winsock(Windows Sockets)是处理网络的Windows API。许多函数与在BSD Unix中使用的Berkeley套节字函数是相同的。

10.2.1 套节字(Socket)的概念和类型

通信的双方要建立连接,这个连接的终端就是套节字。客户端和服务器端都有一个套节字,每个套节字与特定的IP地址和端口号关联。

几乎所有的Winsock函数都在套节字上进行操作,因为套节字是到连接的句柄。连接的两端都要使用一个套节字,它们与平台无关(例如,Windows和Unix机器可以通过套节字通信)。套节字是双通的,也就是说在同一个套节字上既能够接收也能够发送数据。

套节字主要有两种类型,一种是流套节字(SOCK_STREAM),另一种是数据报套节字(SOCK_DGRAM)。流类型的套节字是为需要可靠连接的应用程序设计的。这些程序经常使用连续的数据流。用于这种类型套节字的协议是TCP。流套节字是最常用的,一些众所周知的协议如HTTP、TCP、SMTP、POP3等都使用它。

数据报套节字使用UDP做为下层协议,是无连接的,有一个最大缓冲区大小(数据包大小的最大值)。它是为那些需要发送小数据包,并且对可靠性要求不高的应用程序设计的。与流套节字不同,数据包套节字并不保证数据会达到终端,也不保证它是以正确的顺序到来的。数据报套节字传输效率相当高,它经常用于音频或视频应用程序。对这些程序来说,速度比可靠性更加重要。

另外,也存在一些不常用的套节字类型,如原始套节字(raw socket)等。

10.2.2 Winsock的寻址方式和字节顺序

1.寻址方式
因为Winsock要兼容几个协议,所以必须使用通用的寻址方式。TCP/IP使用IP地址和端口号来指定一个地址,但是其他协议也许采用不同的形式。如果Winsock强迫使用特定的寻址方式,添加其他协议就不大可能了。Winsock的第一个版本使用sockaddr结构来解决此问题。

struct sockaddr
{ u_short sa_family;  char sa_data[14];
};

在这个结构中,第一个成员sa_family指定了这个地址使用的地址家族。sa_data成员存储的数据在不同的地址家族中可能不同。本书仅仅使用互联网地址家族(TCP/IP),Winsock已经定义了sockaddr结构的TCP/IP版本——sockaddr_in结构。它们本质上是相同的结构,但是第2个更容易操作。

struct sockaddr_in
{  short sin_family; // 地址家族(即指定地址格式)  u_short sin_port; // 端口号 struct in_addr sin_addr; // IP地址 char sin_zero[8]; // 空字节,要设为0
};

此结构的最后8个字节没有使用,是为了与sockaddr结构大小相同才设置的。

sin_addr是IP地址(32位),它被定义为一个联合来处理整个32位的值,两个16位部分或者每个字节单独分开。描述32位IP地址的in_addr结构定义如下。

struct sockaddr_in
{  short sin_family; // 地址家族(即指定地址格式)  u_short sin_port; // 端口号 struct in_addr sin_addr; // IP地址 char sin_zero[8]; // 空字节,要设为0
};

此结构的最后8个字节没有使用,是为了与sockaddr结构大小相同才设置的。

sin_addr是IP地址(32位),它被定义为一个联合来处理整个32位的值,两个16位部分或者每个字节单独分开。描述32位IP地址的in_addr结构定义如下。

struct in_addr
{ union {  struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; // 以4个u_char来描述  struct { u_short s_w1,s_w2; } S_un_w; // 以2个u_short来描述  u_long S_addr; // 以1个u_long来描述  } S_un;
};

用字符串“aa.bb.cc.dd”表示IP地址时,字符串中由点分开的4个域是以字符串的形式对in_addr结构中的4个u_char值的描述。由于每个字节的数值范围是0~255,所以各域的值是不可以超过255的。

2.字节顺序
字节顺序是长度跨越多个字节的数据被存储的顺序。例如,一个32位的长整型0x12345678跨越4个字节(每个字节8位)。Intel x86机器使用小尾顺序(little-endian),意思是最不重要的字节首先存储。因此,数据0x12345678在内存中的存放顺序是0x78、0x56、0x34、0x12。大多数不使用小尾顺序的机器使用大尾顺序(big-endian),即最重要的字节首先存储。同样的值在内存中的存放顺序将是0x12、0x34、0x56、0x78。因为协议数据要在这些机器间传输,就必须选定其中的一种方式做为标准,否则会引起混淆。

TCP/IP协议统一规定使用大尾方式传输数据,也称为网络字节顺序。例如,端口号(它是一个16位的数字)12345(0x3039)的存储顺序是0x30、0x39。32位的IP地址也是以这种方式存储的,IP地址的每一部分存储在一个字节中,第一部分存储在第一个字节中。

上述sockaddr和sockaddr_in结构中,除了sin_family成员(它不是协议的一部分)外,其他所有值必须以网络字节顺序存储。Winsock提供了一些函数来处理本地机器的字节顺序和网络字节顺序的转换。

u_short htons(u_short hostshort); // 转化一个u_short类型从主机字节顺序到TCP/IP网络字节顺序
u_long htonl(u_long hostlong); // 转化一个u_long 类型从主机字节顺序到TCP/IP 网络字节顺序
u_short ntohs(u_short netshort); // 转化一个u_short 类型从TCP/IP 网络字节顺序到主机字节顺序
u_long ntohl(u_long netlong); // 转化一个u_long 类型从TCP/IP 网络字节顺序到主机字节顺序

这些 API 是平台无关的。使用它们可以保证程序正确地运行在所有机器上。

3.使用举例

// 填充sockaddr_in结构
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;// 绑定这个套节字到一个本地地址
if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{printf("Failed bind() \n");::WSACleanup();return 0;
}

上例中 inet_addr 函数将一个由小数点分隔的十进制 IP 地址字符串转化成由 32 位二进制数表示的 IP 地址(网络字节顺序)。inet_ntoa 是 inet_addr 函数的逆函数,它将一个网络字节顺序的 32 位 IP 地址转化成字符串。

char * inet_ntoa (struct in_addr in); // 将 32 位的二进制数转化为字符串

in 参数是一个 in_addr 结构,它是需要转化的 32 位的 IP 地址。

10.2.3 Winsock 编程流程

使用 Winsock 编程的一般步骤是比较固定的,可以结合后面的例子程序来理解它们。
1.Winsock 库的装入、初始化和释放
所有的 WinSock 函数都是从 WS2_32.DLL 库导出的,VC++在默认情况下并没有连接到该库,如果想使用 Winsock API,就必须包含相应的库文件。

#pragma commment(lib, "wsock32.lib")

WSAstartup 必须是应用程序首先调用的 Winsock 函数。它允许应用程序指定所需的Windows Sockets API 的版本,获取特定 Winsock 实现的详细信息。仅当这个函数成功执行之后,应用程序才能调用其他 Winsock API。

int WSAStartup( WORD wVersionRequested, // 应用程序支持的最高WinSock 库版本。高字节为次版本号,低字节为主版本号LPWSADATA lpWSAData // 一个指向WSADATA 结构的指针。它用来返回DLL 库的详细信息
);

lpWSAData 参数用来取得 DLL 库的详细信息,结构定义如下。

typedef struct WSAData { WORD wVersion; // 库文件建议应用程序使用的版本WORD wHighVersion; // 库文件支持的最高版本char szDescription[WSADESCRIPTION_LEN+1]; // 库描述字符串char szSystemStatus[WSASYS_STATUS_LEN+1]; // 系统状态字符串unsigned short iMaxSockets; // 同时支持的最大套节字的数量unsigned short iMaxUdpDg; // 2.0 版中已废弃的参数char FAR * lpVendorInfo; // 2.0 版中已废弃的参数
} WSADATA, FAR * LPWSADATA;

函数调用成功返回 0。否则要调用 WSAGetLastError 函数查看出错的原因。此函数的作用相当于 Win32 API GetLastError,它取得最后发生错误的代码。

每一个对WSAStartup 的调用必须对应一个对WSACleanup 的调用,这个函数释放Winsock库。

int WSACleanup(void);

2.套节字的创建和关闭
使用套节字之前,必须调用 socket 函数创建一个套节字对象,此函数调用成功将返回套节字句柄。

SOCKET socket( int af, // 用来指定套节示使用的地址格式,WinSock 中只支持AF_INET int type, // 用来指定套节字的类型int protocol // 配合type 参数使用,用来指定使用的协议类型。可以是IPPROTO_TCP 等
);

type 参数用来指定套节字的类型。套节字有流套节字、数据报套节字和原始套节字等,下面是常见的几种套节字类型定义:

SOCK_STREAM    流套节字,使用 TCP 协议提供有连接的可靠的传输
SOCK_DGRAM     数据报套节字,使用 UDP 协议提供无连接的不可靠的传输
SOCK_RAW       原始套节字,WinSock 接口并不使用某种特定的协议去封装它,而是有程序自行处理数据报以及协议首部

当 type 参数指定为 SOCK_STREAM 和 SOCK_DGRAM 时,系统已经明确确定使用 TCP和 UDP 协议来工作,所以 protocol 参数可以指定为 0。

函数执行失败返回 INVALID_SOCKET(即-1),可以通过调用 WSAGetLastError 取得错误代码。

当不使用 socket 创建的套节字时,应该调用 closesocket 函数将它关闭。如果没有错误发生,函数返回 0,否则返回 SOCKET_ERROR。函数用法如下。

int closesocket(SOCKET s); // 函数惟一的参数就是要关闭的套节字的句柄

3.绑定套节字到指定的 IP 地址和端口号
为套节字关联本地地址的函数是 bind,用法如下。

int bind( SOCKET s, // 套节字句柄const struct sockaddr* name, // 要关联的本地地址int namelen
); // 地址的长度

bind 函数用在没有建立连接的套节字上,它的作用是绑定面向连接的或者无连接的套节
字。当一个套节字被 socket 函数创建以后,他存在于指定的地址家族里,但是它是未命名的。bind 函数通过安排一个本地名称到未命名的 socket 建立此 socket 的本地关联。本地名称包含 3 个部分:主机地址、协议号(分别为 UDP 或 TCP)和端口号。

本节的 10ServerDemo 程序,使用以下代码绑定套节字 s 到本地地址。

// 填充sockaddr_in 结构
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定这个套节字到一个本地地址
if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{ printf("Failed bind() \n"); ::WSACleanup(); return 0;
}

sockaddr_in 结构中的 sin_familly 字段用来指定地址家族,该字段和 socket 函数中的 af 参数的含义相同,所以惟一可以使用的值就是 AF_INET。sin_port 字段和 sin_addr 字段分别指定套节字需要绑定的端口号和 IP 地址。放入这两个字段的数据的字节顺序必须是网络字节顺序。由于网络字节顺序和 Intel CPU 的字节顺序刚好相反,所以必须首先用 htons 函数进行转换。

如果应用程序不关心所使用的地址,可以为互联网地址指定 INADDR_ANY,为端口号指定 0。如果互联网地址等于 INADDR_ANY,系统会自动使用当前主机配置的所有 IP 地址,这简化了程序设计;如果端口号等于 0,程序执行时系统会分配一个惟一的端口号到这个应用程序,其值在 1024 到 5000 之间。应用程序可以在 bind 之后使用 getsockname 来知道为它分配的地址。但是要注意,直到套节字连接上之后 getsockname 才可能填写互联网地址,因为对一个主机来说可能有多个地址是可用的。

4.设置套节字进入监听状态
listen 函数置套节字进入监听状态。

int listen( SOCKET s, // 套节字句柄int backlog // 监听队列中允许保持的尚未处理的最大连接数量
);

为了接受连接,首先使用 socket 函数创建一个套节字,然后使用 bind 函数绑定它到一个本地地址,再用 listen 函数为到达的连接指定一个 backlog,最后使用 accept 接受请求的连接。

listen 仅应用在支持连接的套节字上,如 SOCK_STREAM 类型。函数成功执行之后,套节字 s 进入了被动模式,到来的连接会被通知,排队等候接受处理。

在同一时间处理多个连接请求的服务器通常使用 listen 函数:如果一个连接请求到达,并且排队已满,客户端将接接收 WSAECONNREFUSED 错误。

5.接受连接请求
accept 函数用于接受到来的连接。

SOCKET accept( SOCKET s, // 套节字句柄struct sockaddr* addr, // 一个指向sockaddr_in 结构的指针,用于取得对方的地址信息int* addrlen // 是一个指向地址长度的指针
);

该函数在 s 上取出未处理连接中的第一个连接,然后为这个连接创建一个新的套节字,返回它的句柄。新创建的套节字是处理实际连接的套节字,它与 s 有相同的属性。

程序默认工作在阻塞模式下,这种方式下如果没有未处理的连接存在,accept 函数会一直等待下去直到有新的连接发生才返回。

addrlen 参数用于指定 addr 所指空间的大小,也用于返回返回地址的实际长度。如果 addr或者 addrlen 是 NULL,则没有关于远程地址的信息返回。

客户端程序在创建套节字之后,要使用 connect 函数请求与服务器连接,函数原型如下。

int connect( SOCKET s, // 套节字句柄const struct sockaddr FAR * name, // 一个指向 sockaddr_in 结构的指针,包含了要连接的服务器的地址信息。int namelen // sockaddr_in 结构的长度
);

第一个参数 s 是此连接使用的客户端套节字。另两个参数 name 和 namelen 用来寻址远程套节字(正在监听的服务器套节字)。

6.收发数据
对流套节字来说,一般使用 send 和 recv 函数来收发数据。

int send( SOCKET s, // 套节字句柄const char FAR * buf, // 要发送数据的缓冲区地址int len, // 缓冲区长度int flags // 指定了调用方式,通常设位0
);
int recv( SOCKET s, char FAR * buf, int len, int );

send 函数在一个连接的套节字上发送缓冲区内的数据,返回发送数据的实际字节数。recv函数从对方接收数据,并存储它到指定的缓冲区。flags 参数在这两函数中通常设为 0。

在阻塞模式下,send 将会阻塞线程的执行直到所有的数据发送完毕(或者一个错误发生),而 recv 函数将返回尽可能多的当前可用信息,一直到缓冲区指定的大小。

10.2.4 典型过程图

10.2 Winsock接口相关推荐

  1. win10雷电3接口驱动_微软宣布支持 Windows 10 支持雷电接口,PC 的雷电音频接口春天即将到来?...

    微软宣布Windows 10支持雷电接口的计划,微软官方暂不支持雷电1.2设备,他们可能会与供应商努力开发,但主要致力于对雷电3的支持. 在2015年12月,伴随着Windows 10 Thresho ...

  2. 华为荣耀10显示无服务器,华为荣耀10是什么接口_华为荣耀10充电接口是什么-太平洋IT百科...

    4月19日下午14:30分,荣耀在上海举办新品发布会,便成功吸引了不少网友的关注!该手机前置隐形湿手指纹解锁,这是采用新一代超声波指纹技术,无论干手指还是湿手指下,都能轻松解锁,同时还支持人脸解锁.那 ...

  3. 微信公众帐号开发教程第10篇-解析接口中的消息创建时间CreateTime

    从微信公众平台的消息接口指南中可以看出,每种类型的消息定义中,都包含有CreateTime参数,它表示消息的创建时间,如下图所示: 上图是消息接口指南中4.1-文本消息的定义.注意CreateTime ...

  4. 思科-实验10:路由器接口 PPP 协议封装和 PAP、CHAP 验证配置

    [实验内容] (1) 选择两台C2811 路由器,分别关闭电源后添加WIC-2T 模块,添加位置为插槽0/接口适配器0(提示:在4个插槽中右下角的位置).开启电源之后使用Serial 电缆将两台路由器 ...

  5. 虚幻4学习笔记(10)蓝图接口、拾取物品-射线检测

    虚幻4学习笔记 蓝图接口 拾取物品-射线检测 骨架网格体和静态网格体的区别 骨架网格体设置碰撞 B站UP谌嘉诚课程:https://www.bilibili.com/video/BV164411Y73 ...

  6. 十次方项目开发系列【10】:接口加密Eureka微服务和网关服务开发

    学习目标 了解接口加密业务需求 掌握常用加密算法和密钥格式 实现十次方的接口加密微服务 文章目录 一 业务场景介绍 二 加密方式 2.1 摘要算法 2.2 对称加密 2.3 非对称加密 2.4 数字签 ...

  7. 比postman好用10倍的接口调试工具ApiPost

    强烈推荐apipost:https://www.apipost.cn/ 提升开发.测试团队效率,可直接生成文档的API调试.管理工具!

  8. Java编程基础10——面向对象_多态抽象类接口

    1.多态的概述及其成员访问特点代码体现 A:多态(polymorphic)概述 事物存在的多种形态 B:多态前提- 1.要有继承关系 2.要有方法重写 3.要有父类引用指向子类对象. C:多态中的成员 ...

  9. 计算机hub体系部件,原来如此!USB Hub接口为啥都是4个7个或10个?

    USB  HUb无疑是普及度最高的外设之一,但我们在享用它们便捷的同时,是否想过一个问题:为啥USB Hub身上的USB接口不是4个,7个就是10个呢? USBHub的4.7.10情怀 USB Hub ...

最新文章

  1. C/C++中memset()函数
  2. JVM方法区内存分配
  3. 调度场算法与逆波兰表达式
  4. 2020牛客国庆集训派对day8G-Shuffle Cards【Splay】
  5. VS2010中不可忽视的部分——VSTO
  6. vb全局热键的写法(占很少的资源)
  7. 如何设置电脑自动锁屏_Apple ID密码忘了怎么重置?丨如何让面容和指纹解锁立马失效?...
  8. 函数式编程与命令式编程的学习难度比较
  9. 互联网项目文科程序员的自白
  10. 1t硬盘怎么分区最好_还在用128G Macbook?699元升级1T英睿达SSD
  11. 广数25i系统倒刀回刀m代码_史上最全数控系统代码信息!错过没有了!
  12. python 提取百度网盘下载_百度网盘直链提取工具下载
  13. antd系列之Select
  14. 大学计算机构成课程论文,大学计算机课程论文
  15. 计算机内存怎样清理,怎么样清理电脑内存 电脑清理内存方法【图文】
  16. kafka的topic管理常用命令
  17. 柔宇科技掀起的柔性电子潮流,正在加速改变我们的生活
  18. 支持win7的node.js版本+node和npm版本不匹配问题解决
  19. 如何学好编程?学习经验汇总
  20. BZOJ 2314 士兵的放置(play) 树形DP

热门文章

  1. 计算机组成原理指导,计算机组成原理课程复习指导课件
  2. css3仿山猫侧边栏
  3. Mysql数据备份的概念
  4. 对封装、继承、多态、抽象的理解
  5. bert获得词向量_手把手教你完成句子分类,最好上手的BERT初级使用指南
  6. ObjectARXWizards AutoCAD .NET Wizards 下载地址
  7. 数据结构与算法 实验5 树、二叉树和森林的基本操作
  8. 计算机二级选择题记忆知识点
  9. c语言绝对值——abs和fabs
  10. 收藏,核心期刊的投稿、审稿、出刊流程详解