基本TCP客户/服务的套接字函数调用流程图
socket 函数
- socket函数:通常protocol参数填0即可,因为family参数和type参数唯一确定protocol。
family参数:其中AF_xx 和 PF_xx的值是一样的,所以两者可以互相替换(AF:Address, PF:Protocol)
typ参数:分别对应TCP、UDP、SCTP和本地的套接字协议
connect函数
- connect函数:注意connect失败后,不能继续用上一次的sockfd调用connect,而是先close sockfd,再调用socket返回新的sockfd,然后才能调用connect
connect函数需要注意的点多一些:
如果sockfd是TCP套接字:
- 若TCP客户没有收到
SYN
分节,则返回ETIMEOUT
错误。(三次握手的第二次握手失败)- 这里还会在一段时间内按规定间隔重复发送SYN分节,如果都没收到才会返回该错误
- 硬错误:若TCP服务端对TCP客户的SYN分节的响应是
RST
分节(表明服务器主机没有在指定的地址,ip+port,等待连接),返回ECONNREFUSED
- 软错误:若TCP客户发出的
SYN
分节在途中某个路由器引发”目的地不可达“(destination unreachable)ICMP错误,则返回EHOSTUNREACH
或者ENETUNREACH
- 客户主机内核保存该消息,然后和第1中情况一样,重复发送SYN分节,都没有响应就返回该错误
这里补充一下,RST
分节产生的条件:
- 目的地为某个端口的SYN分节到达,但是端口上没有正在监听的服务器
- TCP想取消一个已有连接
- TCP接收到一个根本不存在的连接的分节
bind函数
服务端在调用listen之前必须调用bind
而客户端在调用connect之前可以调用bind,也可以不调用bind
myaddr参数:
bind返回的常见错误:EADDRINUSE
(Address already in use).可以通过SO_REUSEPORT
和SO_REUSEADDR
来消除这种错误。
listen参数
listen函数是TCP服务端专用函数。listen函数有两个需要注意的点:backlog参数的含义和两个队列
当对sockfd调用listen函数时,内核会为sockfd创建两个队列,如下图:
-
未完成连接队列
- 存放正在等待完成三次握手的连接(即处于
SYN_RCVD
状态的连接)
- 存放正在等待完成三次握手的连接(即处于
-
已完成连接队列
- 存放已完成三次握手的连接(即处于
ESTABLISHED
状态的连接)。当对sockfd调用accept函数时,如果已完成队列不空,就立即返回第一个队列给accept函数,否则accept会被阻塞(阻塞套接字情况下),直至有已完成连接进入队列
- 存放已完成三次握手的连接(即处于
-
backlog参数的含义:在linux中指的是已完成连接队列的长度(当然,该长度不一定就是backlog的值,有可能加1,或者其x倍)。而在有些系统,backlog是指两个队列长度之和。可以参考这篇博客:(225条消息) 深入探索 Linux listen() 函数 backlog 的含义_杨博东的博客的博客-CSDN博客。
当以上两个队列为满时,而刚好有一个客户的SYN分节送到,TCP忽略这个分节,而不是以RST分节作响应。
accept函数
accept函数需要注意的就是第三个参数是指针,因为传入的cliaddr参数有可能会改变。如果对客户端的地址不感兴趣,可以将accept函数的第2、3参数都传NULL
fork和exec
这里主要是fork和exec在网络编程的应用,而不是其本身。
父进程在调用fork之前打开的文件描述符,在fork返回后,都和子进程共享。也就是说这些文件描述符的引用计数+1.
exec是指一系列以exec开头的函数,作用都是在一个进程调用另一个程序。
这里同样需要注意exec对于网络编程的影响:进程在调用exec之前打开着的文件描述符通常跨exec继续保持打开。若要改变这一特性,fctnl设置FD_CLOEXEC,意思是当exec之后,关闭这个文件描述符。
close和shutdown
这里的close函数只是考虑关闭的是套接字的情况。
close一个TCP套接字的默认行为就是将其标志为已关闭,然后立即返回到调用进程。如果该连接中还有一些数据(已经从应用缓冲区调用write,拷贝的内核缓冲区),TCP会尝试发送完这些数据,最后发送的是正常的TCP连接终止序列(FIN分节)
该套接字不能再read和write。
注意:close不是立即关闭连接,而是将sockfd的引用计数减一,为0才真正关闭连接
shutdown是真正的关闭TCP连接(发送FIN分节)
#include<sys/socket.h>
int shutdown(int sockfd,int howto); //返回成功为0,出错为-1.
该函数的行为依赖于howto的值:
- SHUT_RD:值为0,关闭连接的读这一半。
- SHUT_WR:值为1,关闭连接的写这一半。
- SHUT_RDWR:值为2,连接的读和写都关闭。
getsockname和getpeername
函数作用顾名思义,不做解释。