socket
1 | int socket(int domain, int type, int protocol); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | domain | 设置网络通信的域,函数根据这个参数选择通信协议的族。常用参数AF_INET、AF_INET分别代表IPV4和IPV6 | 补充:type参数自从Linux2.6.27开始,还可指定socket的行为,分别为SOCK_NONBLOCK,SOCK_CLOEXEC |
type | 用于设置套接字通信的类型,常用参数有: SOCK_STREAM:流式套接字,提供顺序的,可靠的,双向的,基于连接的字节流,常用TCP SOCK_DGRAM :数据报套接字,支持数据报(最大长度固定的无连接,不可靠消息),常用UDP SOCK_RAW:原始套接字,提供原始网络协议访问 |
||
protocol | 用于指定某个协议,常写0,自动选择,更多用法待补充 | ||
返回值 | int | 成功返回套接字的文件描述符,失败返回-1 |
setsockopt/getsockopt
1 | int setsockopt(int socket, int level, int option_name, |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | socket | 待修改属性的套接字 | 该函数参数说明见下表, 表中get/set代表该选项是否支持getsockopt/setsockopt 套接字选项粗分为两大类: 一是启用或禁止某个特性的二元选项(下表中“标志”给出二元选项), 二是获取并返回我们可以设置或检查的特定值的选项 备注:本文中仅列出了作者目前可能遇到的选项,待后续学习补充 |
level | 指定系统中解释后面选项参数的影响范围: SOL_SOCKET: 通用套接字选项 IPPROTO_IP: IPv4套接字选项 IPPROTO_TCP: TCP套接字选项 其余暂不列出,待补充 |
||
option_name | 选项名,具体参数见下表 | ||
option_value | 具体选项对应的输入/输出数据,该参数为指针 | ||
option_len | option_value的大小 | ||
返回值 | int | 成功返回0,失败返回-1 |
level:SOL_SOCKET
option_name | get | set | 说明 | 标志 | 数据类型 |
---|---|---|---|---|---|
SO_BROADCAST | √ | √ | 允许发送广播数据报,只有使用UDP协议并且开启此选项,才能发送广播报文,该标志目的是为了防止用户错误的输入目的地址为广播地址,此时若未开启此选项则sendto返回EACCES错误 | √ | int |
SO_DEBUG | √ | √ | 开启跟踪调试,该选项仅由TCP支持 | √ | int |
SO_DONTROUTE | √ | √ | 绕过外出路由表查询,即数据报只发往直接相连的主机,不经路由器转发,不可达时sendto会返回ENETUNREACH错误 | √ | int |
SO_ERROR | √ | 获取待处理错误并清除,当一个套接字上发生错误时,源自Berkeley的内核中的协议模块将该套接字的名为so_error的变量设为标准的Unix Exxx值中的一个,我们称它为该套接字的待处理错误(pending error),内核能够以下面两种方式之一立即通知进程这个错误: ①如果进程阻塞在对该套接字的select调用上,那么无论是检查可读条件还是可写条件,select均返回并设置其中一个或所有两个条件 ②如果进程使用信号驱动式I/O模型,那就给进程或进程组产生一个SIGIO信号。 进程然后可以通过访问SO_ERROR套接字选项获取so_error的值。由getsockopt返回的整数值就是该套接字的待处理错误。so_error随后由内核复位为0 |
int | ||
SO_KEEPALIVE | √ | √ | 周期性的测试连接是否仍存活,用于TCP,若两小时连接双方均无数据,则TCP自动给对端发送一个保持存活探测分解(keep-alive probe) | √ | int |
SO_LINGER | √ | √ | 若有数据待发送则延迟关闭 | linger{} | |
SO_OOBINLINE | √ | √ | 让接收到的带外数据将被留在正常的输入队列中(即在线留存) | √ | int |
SO_RCVBUF | √ | √ | 接收缓冲区大小 | int | |
SO_SNDBUF | √ | √ | 发送缓冲区大小 | int | |
SO_RCVLOWAT | √ | √ | 接收低水位标记 | int | |
SO_SNDLOWAT | √ | √ | 发送低水位标记 | int | |
SO_RCVTIMEO | √ | √ | 接收超时 | timeval{} | |
SO_SNDTIMEO | √ | √ | 发送超时 | timeval{} | |
SO_REUSEADDR | √ | √ | 允许重用本地地址 | √ | int |
SO_REUSEPORT | √ | √ | 允许重用本地端口 | √ | int |
SO_TYPE | √ | 返回套接字的类型,例如SOCK_STREAM或SOCK_DGRAM | int | ||
SO _USELOOPBACK | √ | √ | 路由套接字取得所发送数据的副本 | √ | int |
level:IPPROTO_IP
option_name | get | set | 说明 | 标志 | 数据类型 |
---|---|---|---|---|---|
IP_HDRINCL | √ | √ | 随数据包含的IP首部 | √ | int |
IP_OPTIONS | √ | √ | IP首部选项 | 待补充 | |
IP_RECVDSTADDR | √ | √ | 返回目的IP地址 | √ | int |
IP_RECVIF | √ | √ | 返回接收接口索引 | √ | int |
IP_TOS | √ | √ | 服务类型和优先权 | int | |
IP_TTL | √ | √ | 存活时间 | int | |
IP_MUTICAST_IF | √ | √ | 指定外出接口 | in_addr{} | |
IP_MULTICAST_TTL | √ | √ | 指定外出TTL | u_char | |
IP_MULTICAST_LOOP | √ | √ | 发送低水位标记 | u_char | |
IP_ADD_MEMBERSHIP | √ | 加入多播组 | ip_mreq{} | ||
IP_DROP_MEMBERSHIP | √ | 离开多播组 | ip_mreq{} | ||
IP_BLOCK_SOURCE | √ | 阻塞多播组 | ip_mreq_source{} | ||
IP_UNBLOCKSOURCE | √ | 解除阻塞多播组 | ip_mreq_source{} | ||
IP_ADD_SOURCE_MEMBERSHIP | √ | 加入源特定多播组 | ip_mreq_source{} | ||
IP_DROP_SOURCE_MEMBERSHIP | √ | 离开源特定多播组 | ip_mreq_source{} |
level:IPPROTO_IP
option_name | get | set | 说明 | 标志 | 数据类型 |
---|---|---|---|---|---|
TCP_MAXSEG | √ | √ | TCP最大分节大小 | int | |
TCP_NODELAY | √ | √ | 禁止Nagle算法 | √ | int |
bind
1 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | socket | 待绑定的套接字 | 套接字绑定的地址,地址的内容可以只指定一个端口号,或只指定一个IP地址,或者IP和端口同时指定,IP地址必须属于其所在主机的网络接口之一。 1.若不调用该函数绑定IP地址和端口 (1)服务器在启动时捆绑它们的众所周知端口。如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口。让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口;然而对于TCP服务器来说却极为罕见,因为服务器是通过它们的众所周知端口被大家认识的 (2)对于TCP客户端通常不绑定IP地址,当连接套接字时,内核将根据所用外出网络接口来选择源IP地址,而所用外出接口取决于到达服务器所需路径,若TCP服务器未绑定IP地址,内核就把客户端发送的SYN的目的IP地址作为服务器的源IP地址 2.若调用该函数时,端口号为0,则内核为其分配一个临时端口 3.若调用该函数时,IP地址为统配地址(INADDR_ANY,即0),那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报(UDP)时才选择一个本地IP地址 |
addr | 指向于一个特定协议的结构体指针 | ||
addrlen | addr的大小 | ||
返回值 | int | 成功返回0,失败返回-1,错误值在errno中指出,通常是EADDRINUSE(“Address already in use”),此时需要setsockopt的SO_REUSEADDR选项 |
connect
函数说明
1 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | socket | 待连接的套接字 | 该函数常用于TCP协议,但UDP套接字其实也可使用该函数,具体说明见下文 |
addr | 要建立连接的服务器地址 | ||
addrlen | addr的大小 | ||
返回值 | int | 成功返回0,失败返回-1 |
注意事项
如果是TCP套接字,调用
connect
函数将激发TCP的三路握手过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。
若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口 没有进程在等待与之连接(例如服务器进程也许没在运行)。这是一种硬错误(hard error),客户一接收到RST就马上返回ECONNREFUSED错误
RST是TCP在发生错误时发送的一种TCP分节。产生RST的三个条件是:目的地为某端口的 SYN到达,然而该端口上没有正在监听的服务器(如前所述);TCP想取消一个已有连接;TCP 接收到一个根本不存在的连接上的分节。
若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一种软错误(soft error)。客户主机内核保存该消息,并按第一种情况中所述的时间间隔继续发送SYN。若在某个规定的时间(4.4BSD规定75s)后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。以下两种情形也是有可能的:
- 一是按照本地系统的转发表,根本没有到达远程系统的路径
- 二是 connect调用根本不等待就返回
按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处的状态)转移到SYN_SENT状态,若成功则再转移到ESTABLISHED状态。若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。在代码设计时,当循环调用函数connect为给定主机尝试各个IP地址直到有一个成功时,在每次connect失败后,都必须close当前的套接字描述符并重新调用socket
如果是UDP套接字,则不会触发三次握手过程,一旦UDP套接字调用了connect系统调用,那么这个UDP上的连接就变成一对一的连接,但是通过这个UDP连接传输数据的性质还是不变的,仍然是不可靠的UDP连接。一旦变成一对一的连接,在调用系统调用发送和接受数据时也就可以使用TCP那一套系统调用了。
我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址。可以给已连接的UDP套接字调用sendto,但是不能指定目的地址。sendto的第五个参数必须为空指针,第六个参数应该为0.
不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect指定协议地址的数据报。这样就限制一个已连接UDP套接字能且仅能与一个对端交换数据报。
由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接的UDP套接字不接收任何异步错误。
当有一个已连接UDP套接字的进程可出于下列两个目的之一再次调用connect:
- 指定新的IP地址和端口号。
- 为了断开一个已UDP套接字连接,再次调用connect时把套接字地址结构的地址族设置为AF_UNSPEC。
UDP客户进程或服务器进程只在使用自己的UDP套接字与确定的唯一对端进行通信时,才可以调用connect。
listen
1 | int listen(int sockfd, int backlog); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | sockfd | 待操作套接字 | 该函数仅由TCP服务器调用,应在socket和bind函数后,accept函数前调用. |
backlog | 规定内核应该为响应套接字排队的最大连接个数 | ||
返回值 | int | 成功返回0,失败返回-1 |
为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:
- 未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项: 已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态
- 已完成连接队列(completed connection queue),每个已完成TCP三路握手过程的客户对 应其中一项。这些套接字处于ESTABLISHED状态
下图描绘了监听套接字的这两个队列:
从Linux2.2内核开始,backlog参数代表已完成连接队列。未完成连接队列大小由/proc/sys/net/ipv4/tcp_max_syn_backlog文件修改,若backlog参数比/proc/sys/net/core/somaxconn中的值大,那么内核会自动将其校准为/proc/sys/net/core/somaxconn中的值
accept
1 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | sockfd | 待操作套接字 | 1.该函数由TCP服务器调用 2.若对客户端的地址不感兴趣,则地址参数可全填写为NULL 3.后续的数据收发操作皆基于该函数返回的已连接套接字 4.如果没有连接请求在等待,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式,accept会返回-1,并将errno设置为EAGAIN或EWOULDBLOCK |
addr | 用来保存已连接的对端程序的地址 | ||
addrlen | addr参数的大小,函数返回时,该参数指出实际返回的地址大小 | ||
返回值 | int | 成功返回0,失败返回-1 |
send/sendto
函数说明
1 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | sockfd | 待操作套接字 | |
buf | 待发送数据 | ||
len | 发送数据长度 | ||
flags | MSG_CONFIRM:指示数据链路层协议支持监听对方的回应,直到得到答复,它仅能用于SOCK_DGRAM和SOCK_RAW类型的socket(待日后补充理解) MSG_DONTROUTE:本标志告知内核目的主机在某个直接连接的本地网络上,因而无需执行路由表查找,直接将数据发送到本地局域网内的主机。和setsockopt的SO_DONTROUTE设置一致,区别是MSG_DONTROUTE为单词操作 MSG_DONTWAIT:本标志在无需打开相应套接字的非阻塞标志的前提下,把单个I/O操作临时指定为非阻塞,接着执行I/O操作,然后关闭非阻塞标志。 MSG_MORE:告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入TCP发送缓冲区后一并发送。这样可以放置TCP发送过多的报文段,从而提高传输效率 MSG_NOSIGNAL:往读端关闭的管道或者socket连接中写数据时不引发SIGPIPE信号 MSG_OOB:对于send,本标志指明即将发送带外数据。 |
||
dest_addr | 目的地址 | ||
addrlen | dest_addr参数大小 | ||
返回值 | int | 成功返回发送的字节数,失败返回-1 |
注意事项
- UDP支持发送长度为0的数据,TCP不支持(长度为0代表关闭连接)
- send函数只有在connect后的套接字才可使用(这样才知道预期的接收者是谁,因为send函数中没有目的地址)
send和write的唯一区别就是flags的存在,send和sendto的唯一区别就是目的地址的存在 - 当套接字为已connect模式的,那么使用sendto函数的dest_addr和addrlen必须为NULL和0,否则返回错误EISCONN,反之亦然,若dest_addr和addrlen是NULL和0时,发现套接字不是已连接状态的,也会返回ENOTCONN错误
- 如果消息太长而无法通过底层协议,则返回错误EMSGSIZE,并且不发送消息
- 当消息不适合套接字的发送缓冲区时,除非套接字已置于非阻塞I / O模式,否则send()通常会阻塞。 在非阻塞模式下,在这种情况下,它将失败并显示错误EAGAIN或EWOULDBLOCK。 select调用可用于确定何时可以发送更多数据
recv/recvfrom
1 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | sockfd | 待操作套接字 | |
buf | 保存接收到的数据 | ||
len | 欲读取的数据长度,实际读取的字节数可能少于该参数 | ||
flags | MSG_DONTWAIT:本次操作为非阻塞 MSG_OOB:对于recv,本标志指明即将接收带外数据。 MSG_PEEK:本标志适用于recv和recvfrom,它允许我们查看缓冲区中的数据,而且系统不在recv或recvfrom返回后丢弃这些数据。 MSG_WAITALL:它告知内核不要在尚未读入请求数目的字节之前让一个读操作返回 即使指定了MSG_WAITALL,如果发生下列情况之一:(a)捕获一个信号, (b)连接被终止,(c)套接字发生一个错误,相应的读函数仍有可能返回比 所请求字节数要少的数据 |
||
src_addr | 数据来源地址,若不关心可填NULL | ||
addrlen | src_addr参数大小,若不关心可填NULL | ||
返回值 | int | 成功返回接收的字节数,失败返回-1 若套接字连接对端关闭,则该函数成功返回0 若UDP发送长度为0的数据时,该函数也返回0 |
获取地址函数
1 | int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
名称 | 说明 | 备注 | |
---|---|---|---|
参数 | sockfd | 待操作套接字 | 注意:如果提供的缓冲区太小,返回的地址将被截断。 在这种情况下,addrlen将返回一个大于输入的值 getsockname应用场景: 1.在一个没有调用bind的TCP客户上,connect成功后使用该函数返回由内核赋予该连接的本地IP地址和本地端口号 2.在以端口号0调用bind(告知内核去选择本地端口号)后,getsockname用于返回由内 核赋予的本地端口号 3.在一个以通配IP地址调用bind的TCP服务器上,与某个客户的连接一旦建立 (accept成功返回),getsockname就可以用于返回由内核赋予该连接的本地IP地址。在这样的调用中,套接字描述符参数必须是已连接套接字的描述符,而不是监听套接字的描述符 getpeername应用场景: 当一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它能够获取客户身份的唯一途径便是调用getpeername |
addr | 获取的套接字地址结果由该参数返回 | ||
addrlen | 指出addr参数的大小,函数返回时,该参数指出实际返回的地址大小 | ||
返回值 | int | 成功返回0,失败返回-1 |