socket、connect、bind、listen函数

如下给出了在一对 TCP 客户和服务器进程之间发生的一些典型事件的时间表。
在这里插入图片描述
服务器首先启动,稍后某个客户启动,它试图连接到服务器。我们假设客户给服务器发送一个请求,服务器处理该请求,并给客户发回一个响应。这个过程持续下去,直至客户关闭连接的客户端,从而给服务器发送一个 EOF (文件结束) 通知为止。服务器接着也关闭连接的服务器端,然后结束运行或等待新的客户连接。

socket 函数

为执行网络 I/O,一个进程必须做的第一件事就是调用 socket 函数,指定期望的通信协议类型 (使用 IPv4 的 TCP、使用 IPv6 的 UDP、Unix 域字节流协议)。

#include <sys/socket>
int socket(int family,int type,int protocol);

family 指明协议族,该参数也往往被称为协议域。
type 指明套接字类型。
protocol 指明具体协议,也可以设置为 0,以选择所给定 family 和 type 组合的系统默认值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
socket 函数在成功时返回一个小的非负整数,它与文件描述符类似,我们称之为 套接字描述符 ,简称为 sockfd。为得到这个套接字描述符,我们需要指定协议族、套接字类型。

对比 AF_XXX 和 PF_XXX

  • AF_前缀表示地址族 (地址划分的标准集合),PF_前缀表示协议族 (一组协议的集合)。
  • 历史上曾有这样一种想法:单个协议族可以支持地址族,PF_ 值用于创建套接字,而 AF 值用于套接字地址结构。但是实际上,支持多个地址族的协议族从未被实现,而且头文件 <sys/sockets> 中为一给定协议定义的 PF_ 值总是与此协议的 AF_ 值相等。
  • 历史中各标准的实现中对此也有一些弄不清楚。

connect 函数

TCP 客户使用 connect 函数来建立与 TCP 服务器的连接。

#include <sys/socket>
int connect(int sockfd,const struct sockaddr *servaddr,socklent_t addrlen);

sockfd 指明待连接的套接字描述符
servaddr 指明待连接服务器的通用套接字地址结构
addrlen 指明上一个参数的大小
客户在调用函数 connect 前不必非得调用 bind 函数,因为如果需要的话,内核会确定源 IP 地址,并选择一个临时端口作为源端口。
如果是 TCP 套接字,调用 connect 函数将激发 TCP 的三路握手,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:

  • 若 TCP 客户没有收到 SYN 分节的响应,则返回 ETIMEDOUT 错误。举例说明:调用 connect 函数时,客户发送一个 SYN,若无相应则等待 6s 后再发送一个,若仍无响应则等待 24s 后再发送一个,若总共等待 75s 后仍未收到响应则返回本错误。
  • 若对客户 SYN 的响应为 RST,则表明该服务器主机并未开放该端口连接,这是一种 硬错误 ,客户接收到 RST 立马返回 ECONNREFUSED 错误。
  • 若客户发出的 SYN 在中间某个路由器上引发 “destination unreachable” ICMP 错误,则认为是一种 软错误 。客户主机内核保存此消息,并按照第一种情况所述时间间隔继续发送 SYN,若在某个规定时间内仍未收到响应,则将保存的 ICMP 错误作为 EHOSTUNREACH 或 ENETUNREACH 错误返回进程。

bind 函数

bind 函数将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是 32 位的 IPv4 地址或 128 位的 IPv6 地址与 16 位的 TCP 或 UDP 端口号组合。

#include <sys/socket>
int bind(int sockfd,const struct *myaddr,socklent_t addrlen);

sockfd 指明待连接的套接字描述符
myaddr 指明本地的通用套接字地址结构
addrlen 指明上一个参数的大小
对于 TCP ,调用 bind 函数可以指定一个端口号,或者指定一个 IP 地址,也可以两者都指定,还可以都不指定。

  • 服务器在启动时捆绑它们的众所周知的端口。如果一个 TCP 客户或服务器未曾调用 bind 捆绑端口,则当调用 listen 或 connect 时,内核就会为相应的套接字选择一个临时端口。让内核来选择临时端口,对于客户来说是正常的,然而对服务器来说是极为罕见的,因为服务器是通过它们众所周知的端口被大家认识的 (服务器使用临时端口的情况出现在远程过程调用)。
  • 进程可以把一个特定的 IP 地址捆绑到它的套接字上,但是这个 IP 地址必须属于主机的网络接口。对于 TCP 客户,这就为在该套接字上发送的 IP 数据报指定了源 IP 地址。对于 TCP 服务器,这就限定该套接字只接收那些目的地址为该 IP 地址的客户连接。TCP 客户通常不把 IP 地址捆绑到它的套接字上,当连接套接字时,内核将根据所用外出网络接口自动选择源 IP 地址。如果 TCP 服务器没有将 IP 地址捆绑到它的套接字上,内核就把客户发送 SYN 的目的 IP 地址作为服务器的源 IP 地址。

正如上面所说,调用 bind 可以指定 IP 地址或端口,可以两者都指定,也可以两者都不指定。指定 IP 地址或端口的相关组合如下示。
在这里插入图片描述

  • 如果指定端口号为 0,那么内核就在 bind 被调用时选择一个临时端口。如果需要获取这个临时端口,只能通过 getsockname 函数获得。
  • 如果指定 IP 地址为通配地址,那么内核将等到套接字已连接 TCP 或已在套接字上发送数据报时才选择一个 IP 地址。对于 IPv4 来说,通配地址由常值 INADDR_ANY 指定,对于 IPv6 来说,则由系统分配的 in6addr_any 变量指定。

从 bind 函数返回的一个常见错误是 EADDRINUSE (地址已使用)。

listen 函数

listen 函数仅由 TCP 服务器调用,它完成两件事:

  • 当 socket 函数创建一个套接字时,默认为一个主动套接字,也就是说它是一个将调用 connect 发起连接的客户套接字。listen 函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接收指向该套接字的连接请求。
  • 本函数的第二个参数规定内核应该为相应套接字排队的最大连接个数。
#include<sys/socket>
int listen(int sockfd,int backlog);

本函数通常在调用 socket 和 bind 之后,调用 accept 之前调用。
为理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接字维护了两个队列 (如图示):

  • 未完成连接队列
    每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器则会那个在等待完成相应的三次握手过程,这些套接字处于 SYN_RCVD 状态。
  • 已完成连接队列
    每个已完成 TCP 三次握手过程的客户对应其中一项。这些套接字处于 ESTABLISHED 状态。

在这里插入图片描述
当来自客户的 SYN到达时,TCP 在未完成连接队列中创建一个新项,然后服务器对此发出响应。这一项一直保留在未完成连接队列中,直至客户对服务器的 SYN 发出响应或者该项超时为止。如果客户对服务器的 SYN 发出响应,则该项从未完成连接队列转移到已完成连接队列的队尾。如果该项超时,则将该项从未完成连接队列中删去。当进程调用 accept 时,已完成连接队列中的对头项将返回给进程,或者如果该队列为空,那么进程将被投入休眠,直至 TCP 在该队列中放入一项才唤醒它。
关于这两个队列的处理,以下几点需要注意:

  • listen 函数的 backlog 参数曾被规定为这两个队列总和的最大值。但是目前手册对其定义基本上都是 “由未处理连接构成的队列可能增长到的最大长度”,但是并未解释 “未处理连接构成的队列” 这一名词。
  • 源自 Berkeley 的实现给 backlog 增设了一个模糊因子:把它乘以 1.5 得到未处理队列最大长度。关于该模糊因子增设的原因已无从考证,但是如果将 backlog 看做内核为某套接字排队的最大已完成连接数目,那么增加模糊因子的理由就是把队列中未完成连接也计算在内。
  • 不要将 backlog 定义为 0,对此不同实现有不同的解释。如果你不想任何客户连接,请关闭该监听套接字。
  • 历来沿用的样例代码中总是给出值为 5 的 backlog,但是如今这个值是不可行的,因此需要调整该值大小以适应服务器连接。
  • 当一个客户 SYN到达时,如果这些队列是满的,TCP 就忽略该分节,也就是不发送 RST。这么做的原因在于:这种情况是暂时的,客户 TCP 将重发 SYN,期望不久就能在队列中找到可用空间。如果服务器 TCP 立即响应一个 RST,客户的 connect 函数将立即返回一个错误,强制应用程序处理这种情况,而不是让 TCP 的正常重传机制来处理。另外客户无法区分响应 SYN 的 RST 究竟意味着 “该端口没有服务器在监听” 还是 “该端口有服务器在监听,但是队列满了”。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值