[笔记] Microsoft Windows网络编程《一》WinSock简介(二)

简介: [笔记] Microsoft Windows网络编程《一》WinSock简介(二)

1.6 面向连接的通信

本节讨论接受连接和建立连接所需要的 Winsock 函数。

首先讨论的是如何通过监听客户机连接来开发服务器,并探讨接受或拒绝一个连接的过程。随后讨论的是怎样通过初始化与服务

器的连接来开发客户机。最后讨论数据在面向连接会话中是如何传输的。

在IP 中,面向连接的通信是通过 TCP/IP 协议完成的。TCP 提供两个计算机间可靠无误的数据传输。应用程序使用 TCP 通信时,在源计算机和目标计算机之间,会建立起 个虚拟连接。

建立连接之后,计算机之间便能以双向字节流的方式进行数据交换。

1.6.1 服务器API函数

这里所说的服务器其实是个进程,它需要等待任意数量的客户机与之建立连接,以便为它们的请求提供服务。服务器必须在 -个已知的名称上监听连接。在 TCP/IP 中,这个名称就是本地接口的IP 地址,再加上一个端口编号。每种协议都有一套不同的寻址万案,所以务自的命名方法也不同。在Winsock 中,第·步是用 Socket WSASocker 将给定协议的套接绑定到它已知的名称上,这个过程是通过 bind API调用来完成的。下-步是将套接字置为监听模式,这·步用API函数 isten 来完成的。最后,若一台客户机试图建立连接,服务器必须通过 accept 或 WSAAccept 调用来接受连接。接下来的几个小节将讨论绑定、监听和接受客户机连接所需的每个 AP1 调用。图 1.1 展示了建立通信信道时,服务器和客户机必须执行的基本调用。

1.6.1.1 绑定

一旦为某种协议创建了套接字,就必须将套接绑定到一个已知地址上。bind 函数可将指定的套接字同一个已知地址绑定到–起。该函数的声明如下:

int bind(
SOCKET s,
const struct sockaddr FAR* name,
int namelen
);

其中,

第1个参数 s代表用来等待客户机连接的那个套接字,

第2个参数类型是 struct sockaddr它的作用很简单,就是一个普通的缓冲区。根据所使用的那个协议,必须实际地填充一个地址缓冲区并在调用 bind 时将其转换为一个 struct sockaddr。

Winsock 头文件将SOCKADDR 类型定义为 sttuctsockaddr。为简化起见,本章都将使用这个类型。

第 3 个参数代表要传递的、由协议决定的地址结构的长度。

举个例子来说,下列代码显示了在一个TCP 连接上,如何来做到这一点:

SOCKET s
SOCKADDR_IN tcpaddr ;
int port = 5150;
s = socket(AF INET,SOCK STREAM,IPPROTO TCP)
tcpaddr.sin_family = AF_INET;
tcpaddr.sin_port = htons (port);
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s,(SOCKADDR *)&tcpaddr,sizeof(tcpaddr));

可以看到这个例子创建了一个流套接字。

随后,建立了 TCP/IP 地址结构,并打算在它上面接受客户机连接。

在这种情况下,通过特殊 P 地址INADDR ANY,套接字被绑定到默认的IP 接口,并占据5150端口。

可以指定系统中一个可用的显式IP 地址,不过INADDR_ANY允许将套接字绑定到系统中所有可用的接口,以便将来传到任意接口上的客户机连接(必须在正确的端口上)都可以被监听套接字接受。

bind 调用正式将套接字同IP接口及端口关联到了一起。

一旦出错,bind就会返回SOCKET_ERROR。对bind而言,最常见的错误是WSAEADDRINUSE如果使用的是TCP/IP,那么WSAEADDRINUSE表示另一个进程已经同本地P 接口及端口号绑定到了一起,或者那个P接口和端口号处于TIME WAIT 状态。

假如对一个已被绑定的套接字调用 bind,返回的将是WSAEFAULT 错误。

1.6.1.2 监听

接下来要做的,是将套接字置入监听模式。bind 函数的作用只是将套接字和指定的地址关联在一起。指示套接字等候连接传入的API函数则是 listen,其定义如下:

int listen(
SOCKET s,
int backlog

第 1 个参数同样是一个被绑定的套接字。backlog 参数指定了被搁置的连接的大队列长度。因为完全可能同时出现儿个服务器的连接请求,所以这个参数非常重要。例如,假定 backlog 参数为2.如果有3 个客户机同时发出请求,那么头两个会被放在个“并起”队列中,以便应用程序依次为它们提供服务。第3 个连接请求会失败,返网一个 WSAECONNREFUSED 错误。注意,”-旦服务器接受了一个连接,那个连接请求就会从队列中删去,以便别人可继续发出请求。backlog 参数其实本身就存在着限制,这个限制是由下层的协议提供程序决定的,如果这个参数出现非法值,那么系统会用与之最接近的一个合法值来取代。另外,对于如何找出实际的 backlog 值, 还不存在一种标准的方案。

listen 相关的错误是非常直观的。到目前为什么,取常见的错误是 WSAEINVAL。该错误通常意味着,在调用 listen 之前没有调用 bind,另外,与 bind 调用相反,使用 listen 时可能接收到WSAEADDRINUSE错误。这个错误通常是在进行 bind 调用时发生的。

1.6.1.3 接受连接

现在,我们已做好了接受客户机连的准备。这是通过 accept、WSAAccept 或 AcceptEx 两数来完成的(AcceptEx 是accpt 的扩展版本,第6章中对它作广详描述)。

Aceept 的原型如下:

SOCKET accept(
SOCKET s,
struct sockaddr FAR* addr,
int FAR* addrler
);

其中,参数 s 是·个被绑定的接字,它处于监听模式。

第 2 个参数应该是一个有效的SOCKADDR_IN 结构的地址,而 addrlen 应该是 SOCKADDR_IN 结构的长度。对于属于另一种协议的套接字,应当用与那种协议对应的 SOCKADDR 结构来替换 SOCKADDR_IN

通过对 accpet 函数的调用,可以为被搁置的连接队列中的第 1 个连接请求提供服务accpet 函数返回后,addr 结构中会包含发出连接请求的那个客户机的IPv4地址信息,而addrlen参数则指出addr 结构的长度。

此外, accpet会返回一个新的套接字描述符,它对应于已经被接受的那个客户机连接、该客户机后续的所有操作都应该使用这个新的套接字。全于原来那个监听套接子,它仍然用于接受其他客户机连接,而且仍处于监听模式。

如果出错,INVALID_SOCKET 将被返回。在监听套接字为异步或非阻塞模式,并且没有连接被接受时,而常见的错误是 WSAEWOULDBLOCK,阻塞、非阻塞以及其它套接字模式将在第 5章中作介绍。

Winsock2引入了WSAAccept 函数,根据条件雨数的返回值,该函数可有条件地接受··个连接第 10章将详细地讲述WSAAccept 函数。

到此为止,已经介绍过了创建简单的 Winsock TCP/IP 服务器应用程序所需的全部元素。下面的程序片断展示了如何编写一个能够接受 TCP/IP 连接的服务器。程序中没有对调用实施任何错误检查这样可以使代码显得清晰易读。本应用程序的完整版本可在补充材料中的 TCPSERVER 文件中找到

finclude <winsock2.h>
void main(vold)
{
WSADATA wsaData;
SOCKET ListeningSocket;
SOCKET NewConnection;
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int Port = 5150;
//初始化 qinsock 版本 2.2
WSAStartup (MAKEWORD(2,2),weaDatal;
//创建-个新的套接字来监听客户机连接
ListeningSocket = socket(AF INET,SOCK STREAM,IPPROTO TCP);//建立一个S0CKADDR IN 构,这个结构将告知 bind 我们想要在 5150 端口监听所有接口上
//的连接
//请留意这里是如何将端口变量从机字节顺序转换为网络字节顺序的
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons[Port);
ServerAddr.sin_addr.s_addr = htonl(INADDR ANY):
//使用 bind 将这个地址信息和套接字关联起来
bind(ListeningSocket,(SOCKADDR)&ServerAddr,sizeof(ServerAddr));
//监听客户机连接。这里使用 5 个 backlog,许多应用程序一般都使用这个数量
listen(ListeningSocket,5);
//连接到达时,接受一个新连接
NewConnection = accept(ListeningSocket,(SOCKADDR *)&ClientAddr, &ClientAddrlen));
//此刻在这些套接字上可以做两件事。是在 ListeningSocket 上再次调用 accept//等候更多的连接到来。二是开始在 newConnection 上收发数据。本章稍后将讲述怎
//样进行数据收发
//在 NewConnection 套接字上完成数据收发,以及在 ListeningSocket 套接字上
//完成接受新连接府,应该用 closesocket API 关闭这些套接宁
//本章稍后将进述套接字的关闭
closcsocket(Newconnection);
closesocket(ListeningSocket);
//应用程序完成对连接的处理后,谢用 WSACleanup
WSACleanup();

在了解到怎样创建一个能够接收客户机连接的服务器之后,下面接着讲述如何创建客户机。

1.6.2 客户端API函数

客户机的创建要简单得多,建立成功连接所需的步骤也要少得多。创建客户机只需 3 步操作:

  1. 创建一个套接字。
  2. 建立·个 SOCKADDR 地址结构,结构名称为准备连接到的服务器名(以下层协议为准)。对于 TCP/IP,这是客户机应用程序所监听的服务器的 IP 地址和端口号。
  3. 用 connect 或 WSAConnect 给化客户机与服务器的连接。

由于已经知道如何建立套接字和建立SOCKADDR 结构,所以现在只有一步未做,那就是建立一个连接。

TCP 状态

作为一名 Winsock 程序员,通常没必要了解实际的 TCP 状态,但了解了TCP 状态,就能更好地理解 Winsock API调用如何对下层协议中的变化产生影响。

此外,许多程序员在关闭套接字时,会碰到一个常见问题,那就是围绕套接字关闭时的 TCP 状态,这是我们目前最感兴趣的问题对每个套接字来说,它的初始状态都是 CLOSED,若客户机初始化了一个连接,就会向服务器发送一个 SYN 包,同时将客户机套接字状态置为 SYN SENT

服务器收到SYN包后,会发出一个SYN-ACK包,客户机需要用一个ACK 包对它做出响应此时,客户机的套接字将处于ESTABLISHED状态,如果服务器一直不发送 SYN-ACK 包,客户机就会超时,并返回CLOSED状态若服务器的套接字同本地接口及端口绑定起来,并在它上面进行监听,那么套接宇的状态便是LISTEN,客户机试图与服务器连接时,服务器就会收到一个 SYN包,并用一个SYN-ACK包做出回应,服务器套接字的状态就变成SYN RCVD,最后,客户机发出一个ACK 包,它将使服务器套接字的状态变成ESTABLISHED.

一旦应用程序处于ESTABLISHED状态,就可以通过两种方法来关闭它。

如果由应用程序来关闭便叫作“主动套接字关闭”, 否则,套接字的关闭便是被动的。

图 1.2 对两种关闭方法进行了解释。

如主动关闭,应用程序便会发出一个 FIN 包。应用程序调用 closesocketshutdown 时(把SD_SEND当作第 2个参数),会向通信对方发出一个 FIN包,而且套接字的状态将变成FIN_WAIT1.正常情况下,通信对方会用一个 ACK 包作为回应,套接宇的状态随之变成FIN WAIT 2,如通信对方也关闭了连接,它会发出一个 FIN包,我们的机器则会以一个ACK 包作为回应,并将套接字的状态置为TIME WAIT.

TIME WAIT状态也叫作 2MSL 等待状态。其中,MSL 代表“分段最长生存时间”(MaximumSegment Lifetime)表示一个数据包在被丢弃之前,可在网络上存在多长时间,每个1 包都含有一个TTL(time-to-live,生存时间)字段,若它递减到0、包便会被丢弃。 一个包经过网络上的每个路由器时TTL值都会减1,然后继续传递,一旦应用程序进入 TIME WAIT状态,那么就会一直持续两倍 MSI的时间。这样,如果最后一个ACK包丢失,TCP 就可以重新发送它,也就是说,FIN会被重新传送出去。两倍于MSL 时间的等待状态结束之后,套接字使进入 CLOSED状态。

采取主动关闭措施时,沿两条路径可以进入 TIME WAIT 状态。在以前的讨论中,只有一方发出一个FIN,并接收一个ACK 回应。然而,另一方仍然可以自由地发送数据,直到它也被关闭为止。

因此,需要另外两条路径发挥作用,在一条路径中(即同步关闭),一台计算机与之连接的通信对方会同时要求关闭: 计算机向对方送出一个FIN款据包,并从它那里接收一个 FIN 数据包。随后,计算机会发出一个ACK 数据包,对对方的 FIN 包作出回应,并将自己的套接字置为 CLOSING 状态。计算机从对方那里接收到最后一个ACK 包之后,它的套接字状态会变成TIME WAIT,主动关闭时,另一条路径其实就是同步关闭的变体:

  • 套接字从 FIN WAIT 状态直接变成TIME WAIT 状态。

若应用程序发出一个 FIN 数据包,但马上又从对方那里接收到一个 FIN-ACK包时,这种情况就会发生。在这种情况下,对方会确认是否收到了应用程序的 FIN 包,并送出自己的FIN包对于这个包,应用程序会以一个 ACK 包做出回应

TIME WAIT 状态的主要作用是,在TCP 连接处于2MSL等待状态的时候,定义那个连接的一对套接宇不可以被重新使用。

这对套接宇由本地 IP 端口以及远程IP 端口组成。某些TCP 实施方案不许重新使用处于 TIME WAIT 状态下的套接字对中的任何端口号,在微软实现的 TCP 中,不会存在这个问题,然而,若试图通过一对已处于TIME WAIT 状态的套接字建立连接,连接的建立就会失败并返回 WSAEADDRINUSE 错误。要解决这一问题(除了等待使用本地端口的套接字对脱离TIML WAIT 状态之外),一个办法是使用套接字选项 SO REFUSEADDR,第7章将对这个选项进行详细讨论.

在被动关闭情况下,应用程序会从对方那里接收到一个 FIN包,并用一个 ACK 包做出回应。此时,应用程序的套接宇会变成 CLOSE WAIT 状态。由于对方已关闭自己的终端,所以它不能再发送数据了,但应用程序却不同,它能一直发送数据,直到它关闭了自己的连接终端为止,要想连接终端应用程序需要发出自己的FIN,今应用程序的套接宇状态变成LAST ACK,应用程序从对方接收到一个 ACK包后,它的套接字就会回到 CLOSED 状态。要想了解TCP/IP 协议的有关详情,请访问 http://www.rfc-editor.org,查阅RFC793文件。

connect 函数

连接套接字是通过调用 connect、WSAConnect 或 ConnectEx 丽数来完成的。先来看看 connect 函数的Winsock1版本,其定义如下

int connect (
SOCKET s,
const struct sockaddr FAR* name,
int namelen,
);

这个函数的参数含义是相当明显的:

  • 参数s是即将在其上面建立连接的那个有效 TCP 套接字
  • name 参数是TCP 的套接字地址结构 SOCKADDR_IN,表示要连接到的服务器
  • namelen 则是name参数的长度。

如果想连接的计算机没有进程用于监听给定端口,connect 调用就会失败,并产生WSAECONNREFUSED

另一个错误可能是WSAETIMEDOUT,这种情况,般发生在试图连接的计算机不可用时(也可能因为到主机之间的路由上出现硬件故障,或主机目前没有联网)。

下面的程序片断展示了如何编写一个能够连接到前述服务器应用程序的简单客户机。程序中没有对调用实施任何错误检查,以使代码显得清晰易读。本应用程序的完整版本可在补充材料中名为TCPCLIENT 的文件中找到。

#inelude <winsock2.h>
void main(void){
WSADATA wsaData;
SOCKET s;
SOCKADDR_IN ServerAddr;
int Port = 5150;
//初始化 winsock 2.2 版本
WSAStartup(MAKEWORD(2,2),wsaData);
//创建·个新的套接宁来建立客户机连接
s =  socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//建立一个 SOCKADDR IN 结构,用它来连接到在5150 端的监听服务器//作为示范,这里假设我们的服务器 IP 地址是 1361493.29//显然,应该提示用户输入 IP 地址,并将用户数据填入这个字段
ServerAddr.sin_famlly = AF_INET;
ServerAddr.sin_port = htons (Port);
ServerAddr.sin_addr.s_addr = inet_addr("136.149,3.29);
//用会接子 s 创建个到服务器的连接
connect(s,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
//此时可以在套接 s 上收发数据了。本章稍后将叙述怎//样进行数据收发
//在套接字 s 上结束数据收发后,应该用 closesocket API 来关闭它
//本章稍后将叙述套接字的关闭
closesocket(s);
//应用程序完成对连接的处理后,调用 WSACleanup
WSACleanup();
}

既然已经能够为面向连接的服务器和客户机建立通信了,接下来便开始处理数据传输

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
5天前
|
机器学习/深度学习 计算机视觉 知识图谱
【YOLOv8改进】MobileViT 更换主干网络: 轻量级、通用且适合移动设备的视觉变压器 (论文笔记+引入代码)
MobileViT是针对移动设备的轻量级视觉Transformer网络,结合CNN的局部特征、Transformer的全局注意力和ViT的表示学习。在ImageNet-1k上,它以600万参数实现78.4%的top-1准确率,超越MobileNetv3和DeiT。MobileViT不仅适用于图像分类,还在目标检测等任务中表现出色,且优化简单,代码已开源。YOLOv8引入了MobileViT块,整合卷积和Transformer结构,提升模型性能。更多详情可参考相关专栏和链接。
|
5天前
|
机器学习/深度学习 数据可视化 计算机视觉
【YOLOv8改进】MCA:用于图像识别的深度卷积神经网络中的多维协作注意力 (论文笔记+引入代码)
YOLO目标检测专栏介绍了YOLO的创新改进和实战案例,包括多维协作注意力(MCA)机制,它通过三分支架构同时处理通道、高度和宽度注意力,提高CNN性能。MCA设计了自适应组合和门控机制,增强特征表示,且保持轻量化。该模块适用于各种CNN,实验证明其在图像识别任务上的优越性。此外,文章还展示了如何在YOLOv8中引入MCA层的代码实现和相关任务配置。
|
5天前
|
机器学习/深度学习 计算机视觉
【YOLOv8改进】 YOLOv8 更换骨干网络之GhostNetV2 长距离注意力机制增强廉价操作,构建更强端侧轻量型骨干 (论文笔记+引入代码)
该专栏聚焦YOLO目标检测的创新改进与实战,介绍了轻量级CNNs和注意力机制在移动设备上的应用。文章提出了一种名为GhostNetV2的新架构,结合了硬件友好的DFC注意力机制,强化了特征表达能力和全局信息捕获,同时保持低计算成本和高效推理。GhostNetV2在ImageNet上以167M FLOPs达到75.3%的top-1准确率,优于同类模型。创新点包括DFC注意力、模型结构优化和效率提升。源代码可在GitHub和MindSpore平台上找到。此外,还提到了YOLOv8的相关实现和任务配置。
|
5天前
|
机器学习/深度学习 存储 测试技术
【YOLOv8改进】 YOLOv8 更换骨干网络之 GhostNet :通过低成本操作获得更多特征 (论文笔记+引入代码).md
YOLO目标检测专栏探讨了卷积神经网络的创新改进,如Ghost模块,它通过低成本运算生成更多特征图,降低资源消耗,适用于嵌入式设备。GhostNet利用Ghost模块实现轻量级架构,性能超越MobileNetV3。此外,文章还介绍了SegNeXt,一个高效卷积注意力网络,提升语义分割性能,参数少但效果优于EfficientNet-L2。专栏提供YOLO相关基础解析、改进方法和实战案例。
|
5天前
|
计算机视觉
【YOLOv8改进】 AFPN :渐进特征金字塔网络 (论文笔记+引入代码).md
YOLO目标检测专栏介绍了YOLO的有效改进和实战案例,包括AFPN——一种解决特征金字塔网络信息丢失问题的新方法。AFPN通过非相邻层直接融合和自适应空间融合处理多尺度特征,提高检测性能。此外,还展示了YOLOv8中引入的MPDIoU和ASFF模块的代码实现。详情可参考提供的专栏链接。
|
5天前
|
编解码 计算机视觉 网络架构
【YOLOv8改进】BiFPN:加权双向特征金字塔网络 (论文笔记+引入代码)
该专栏深入研究了YOLO目标检测的神经网络架构优化,提出了加权双向特征金字塔网络(BiFPN)和复合缩放方法,以提升模型效率。BiFPN通过双向跨尺度连接和加权融合增强信息传递,同时具有自适应的网络拓扑结构。结合EfficientNet,构建了EfficientDet系列检测器,在效率和准确性上超越先前技术。此外,介绍了YOLOv8如何引入MPDIoU并应用BiFPN进行可学习权重的特征融合。更多详情可参考提供的专栏链接。
|
10天前
|
编解码 5G Linux
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器
AVS3是中国首个8K及5G视频编码标准,相比AVS2和HEVC性能提升约30%。解码器libuavs3d支持8K/60P视频实时解码,兼容多种平台。《FFmpeg开发实战》书中介绍了在Windows环境下如何集成libuavs3d到FFmpeg。集成步骤包括下载源码、使用Visual Studio 2022编译、调整配置、安装库文件和头文件,以及重新配置和编译FFmpeg以启用libuavs3d。
29 0
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器
|
13天前
|
数据库 Windows
第五十章 使用 ^SystemPerformance 监视性能 - Microsoft Windows 平台的 InterSystems IRIS 性能数据报告
第五十章 使用 ^SystemPerformance 监视性能 - Microsoft Windows 平台的 InterSystems IRIS 性能数据报告
22 0
|
19天前
|
安全 网络协议 网络安全
网络安全笔记整理,你花了多久弄明白架构设计
网络安全笔记整理,你花了多久弄明白架构设计
|
19天前
|
安全 网络协议 网络安全
网络安全笔记整理(1),字节跳动网络安全内部学习资料泄露
网络安全笔记整理(1),字节跳动网络安全内部学习资料泄露