基本的套接字函数

基本的套接字函数

socket函数

概要

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

socket()创建一个用于通讯的端点,返回一个文件描述符。

domain参数

domain参数指定一个地址族。下面是一些常见的地址族:

地址族说明
AF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCAL本地通讯

type参数

type参数指定套接字类型。下面是一些常见的类型:

类型说明
SOCK_STREAM字节流套接字
SOCK_DGRAM数据报套接字
SOCK_SEQPACKET有序分组套接字
SOCK_RAW原始套接字

对于某个地址族,不一定实现了所有的套接字类型。

从Linux 2.6.27开始,type参数的存在有两个目的:除了指定套接字类型外,它也可以包含任何以下值的位或,以修改socket()的行为:

  • SOCK_NONBLOCK:在新的打开的文件描述符上设置O_NONBLOCK文件状态标志。
  • SOCK_CLOEXEC:在新的打开的文件描述符上设置FD_CLOEXEC标志。

protocol参数

protocol参数指定一个特定的协议(协议号)。通常,在给定的地址族中,只有一个协议可以支持特定的套接字类型,在这种情况下,protocol可以指定为0(大部分情况下都是0,以选择domaintype组合的系统默认值)。

在Linux中,通过/etc/protocols文件查看协议的定义。通过getprotoent函数将一个协议名映射到协议号。

返回值

socket()在成功时返回一个新的套接字的文件描述符,错误时返回-1并设置errno

domain和type常见的组合

下面是一些domaintype常见的组合:

AF_INETAF_INET6
SOCK_STREAMTCP | SCTPTCP | SCTP
SOCK_DGRAMUDPUDP
SOCK_SEQPACKETSCTPSCTP
SOCK_RAWIPv4IPv6

对比AF_XXX和PF_XXX(地址族和协议族)

AF_前缀表示地址族,PF_前缀表示协议族。历史上曾有这样的想法:单个协议族可以支持多个地址族,PF_值用来创建套接字,而AF_值用于套接字地址结构。但实际上,支持多个地址族的协议族从来就未实现过,而且头文件<sys/socket.h>中为一给定协议定义的PF_值总是与此协议的AF_值相等。尽管这种相等关系并不一定永远成立,但若有人试图给已有的协议改变这种约定,则许多现存代码都将崩溃。

在4.x BSD下的协议族常量是PF_UNIX,PF_INET等,而AF_UNIX,AF_INET等被用于地址族。然而,已有的BSD手册承诺:协议族通常与地址族相同,并且以后的标准都使用AF_XXX

bind函数

概要

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
         socklen_t addrlen);

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

addr和addrlen参数

当使用socket()创建一个套接字时,它存在于一个命名空间(地址族)中但是没有分配地址给它。bind()通过addr分配地址给sockfd引用的套接字,addrlen指定地址结构的大小。

addr实际结构取决于地址族,把它看成const void*类型即可。

调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可都不指定:

在这里插入图片描述

如果指定端口号为0,那么内核就在bind被调用时选择一个临时端口。然而如果指定IP地址为通配地址,那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报(UDP)时才选择一个本地IP地址。

对于IPv4来说,通配地址由常值INADDR_ANY来指定,其值一般为0。它告知内核去选择IP地址。

对于IPv6来说,通配地址由变量in6addr_any来指定。

返回值

bind函数在成功时返回0,错误时返回-1并设置errno

connect函数

概要

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

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

sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。

客户在调用函数connect前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

如果是TCP套接字,调用connect函数将发送SYN分节并激发TCP的三路握手过程,而且仅在连接建立成功(收到服务器的SYN和附带的ACK分节)或出错时才返回,其中出错返回可能有以下几种情况:

  1. 若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。举例来说,调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等了75s后仍未收到响应则返回本错误。

  2. 若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接(例如服务器进程也许没在运行)。这是一种硬错误(hard error),客户一接收到RST就马上返回ECONNREFUSED错误。

    RST是TCP在发生错误时发送的一种TCP分节。产生RST的三个条件是:目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。

  3. 若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一种软错误(soft error)。客户主机内核保存该消息,并按第一种情况中所述的时间间隔继续发送SYN。若在某个规定的时间(4.4BSD规定75s)后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETYUNREACH错误返回给进程。以下两种情形也是有可能的:一是按照本地系统的转发表,根本没有到达远程系统的路径;二是connect调用根本不等待就返回。

按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处的状态)转移到SYN_SENT状态,若成功则再转移到ESTABLISHED状态。若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。

addr和addrlen参数

这两个参数与上面的bind函数中的两个参数参数类似,实际结构取决于地址族。

返回值

connect函数在成功时返回0,错误时返回-1并设置errno

listen函数

概要

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

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

  1. socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态。
  2. listen函数第二个参数规定了内核应该为相应套接字排队的最大连接个数。

这个函数通常应该在调用socketbind这两个函数之后,并在调用accept函数之前调用。

backlog参数

为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:

  1. 未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。
  2. 已完成连接队列(completed connection queue),每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。

在这里插入图片描述

在这里插入图片描述

上面两幅图来自《UNIX网络编程》。

在Linux中,如果backlog参数大于/proc/sys/net/core/somaxconn中的值,那么将被截断为该值。从Linux5.4开始,这个文件中的默认值是4096;在早期的内核中,默认值是128。内核早于2.5.25之前,限制为一个硬编码的值SOMAXCONN,即128。如果连接请求抵达时队列已满,客户将收到一个ECONNREFUSED错误,或者如果底层协议支持重传,请求可能会被忽略,以便之后的尝试将成功连接。

在Linux中,backlog参数在TCP套接字上的行为在Linux 2.2中发生改变。现在它指的是完全建立连接并等待accept的套接字的数量,而不是未完成连接的请求的数量。

返回值

listen函数在成功时返回0,错误时返回-1并设置errno

accept函数

概要

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被阻塞(假定套接字为默认的阻塞方式)。

addr和addrlen参数

参数addraddrlen用来返回已连接的对端进程(客户)的协议地址。为了能处理IPv6地址,应将更大的struct sockaddr_storage赋予给addr。和往常一样,我们应该将struct sockaddr *看成void*,因为实际的地址结构是未知的。

addrlen值-结果参数,调用前,我们将*addr所占的字节数传递给addrlen,返回时addrlen被赋值为addr实际地址结构的字节数。

返回值

accept函数在成功时返回一个非负的整数值,它是被接受套接字的文件描述符,在错误时返回-1并设置errno,且addrlen保持不变。

close函数

概要

#include <unistd.h>

int close(int fd);

close函数可以用来关闭套接字,并终止TCP连接。

被关闭的套接字描述符不能再作为readwrite的第一个参数,然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列(四次挥手)。

fd参数

fd是一个文件描述符,可以为一个套接字描述符。

返回值

close函数在成功时返回0,错误时返回-1并设置errno

描述符引用计数

close已连接的描述符将导致该描述符的引用计数减1,当引用计数减为0时,套接字才被关闭,对于TCP套接字,这将发送FIN分节并引发四分组连接终止序列(四次挥手)。

shutdown函数

概要

#include <sys/socket.h>

int shutdown(int sockfd, int how);

终止网络连接的通常方法是调用close函数。不过close有两个限制,却可以使用shutdown来避免:

  1. close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字。使用shutdown可以不管引用计数就激发TCP的正常连接终止序列。
  2. close终止读和写两个方向的数据传送。既然TCP连接是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据要发送给我们。

how参数

how参数可以为以下3个值:

  • SHUT_RD:关闭连接的读这一半,套接字中不再有数据可接收,且套接字接收缓冲区中的现有数据都被丢弃。进程不能再对这样的套接字调用任何读函数。对一个TCP套接字这样调用shutdown函数后,由该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。
  • SHUT_WR:关闭连接的写这一半(对于TCP套接字将发送FIN分节),对于TCP套接字,这称为半关闭(half-close)。当前留在套接字发送缓冲区中的数据将被发送掉,后跟TCP的正常连接终止序列。不管套接字描述符的引用计数是否等于0,这样的写半部关闭照样执行。进程不能再对这样的套接字调用任何写函数。
  • SHUT_RDWR:连接的读半部和写半部都关闭,这与调用shutdown两次等效,第一次调用指定SHUT_RD,第二次调用指定SHUT__WR。

返回值

shutdown函数在成功时返回0,错误时返回-1并设置errno

参考

《UNIX网络编程》

socket(2)

bind(2)

connect(2)

listen(2)

accept(2)

close(2)

shutdown(2)

相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页