[笔记] 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
相关文章
|
2月前
|
Java Spring
【编程笔记】在 Spring 项目中使用 RestTemplate 发送网络请求
【编程笔记】在 Spring 项目中使用 RestTemplate 发送网络请求
94 0
|
4月前
|
NoSQL Redis
Redis原理之网络通信协议笔记
1. RESP协议 ​2. 自定义Socket连接Redis
|
4月前
|
NoSQL Linux Redis
Redis原理之网络模型笔记
Redis采用单线程模型,这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O(Blocking I/O)、非阻塞I/O(Non-blocking I/O)、I/O多路复用(I/O Multiplexing)、信号驱动I/O(Signal-driven I/O)以及异步I/O(Asynchronous I/O)。
|
2天前
|
编解码 Linux Windows
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
本文档介绍了在Windows环境下如何为FFmpeg集成libopus和libvpx库。首先,详细阐述了安装libopus的步骤,包括下载源码、配置、编译和安装,并更新环境变量。接着,同样详细说明了libvpx的安装过程,注意需启用--enable-pic选项以避免编译错误。最后,介绍了重新配置并编译FFmpeg以启用这两个库,通过`ffmpeg -version`检查是否成功集成。整个过程参照了《FFmpeg开发实战:从零基础到短视频上线》一书的相关章节。
16 0
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
|
3天前
|
编解码 Linux Windows
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
在Windows环境下,为FFmpeg集成音频编解码库,包括libogg、libvorbis和opencore-amr,涉及下载源码、配置、编译和安装步骤。首先,安装libogg,通过配置、make和make install命令完成,并更新PKG_CONFIG_PATH。接着,安装libvorbis,同样配置、编译和安装,并修改pkgconfig文件。之后,安装opencore-amr。最后,重新配置并编译FFmpeg,启用ogg和amr支持,通过ffmpeg -version检查是否成功。整个过程需确保环境变量设置正确,并根据路径添加相应库。
20 1
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
|
25天前
|
Linux 编译器 C语言
FFmpeg开发笔记(二)搭建Windows系统的开发环境
在Windows上学习FFmpeg通常较困难,但通过安装预编译的FFmpeg开发包可以简化流程。首先需要安装MSYS2来模拟Linux环境。下载并执行MSYS2安装包,然后修改msys2_shell.cmd以继承Windows的Path变量。使用pacman安装必要的编译工具。接着,下载预编译的FFmpeg Windows包,解压并配置系统Path。最后,在MSYS2环境中运行`ffmpeg -version`确认安装成功。欲深入学习FFmpeg开发,推荐阅读《FFmpeg开发实战:从零基础到短视频上线》。
32 4
FFmpeg开发笔记(二)搭建Windows系统的开发环境
|
2月前
|
Linux iOS开发 MacOS
|
2月前
|
缓存 网络协议 Unix
Windows 命令提示符(CMD)操作(四):网络通信
Windows 命令提示符(CMD)操作(四):网络通信
|
3月前
|
Windows
windows逆向 -- Debug工具简介
windows逆向 -- Debug工具简介
27 0
|
3月前
|
存储 网络协议 物联网
《物联网技术》课程笔记——第四章 物联网通信技术之计算机网络
《物联网技术》课程笔记——第四章 物联网通信技术之计算机网络