socket
int socket(
// AF_INET/AF_INET6/AF_LOCAL
int family,
// SOCK_STREAM/SOCK_DGRAM/SOCK_RAW
int type,
// IPPROTO_TCP/IPPROTO_UDP
int protocol);
connect
int connect(
int sockfd,
const struct sockaddr* servaddr,
socklen_t addrlen);
客户发出SYN
,可能的失败情形:
a. SYN
未到达服务端,最终触发超时.
b. 服务端对应端口并未有监听进程,服务端主机发回RST
.
失败时,必须对sockfd
执行close
.
bind
int bind(
int sockfd,
const struct sockaddr* myaddr,
socklen_t addrlen);
1.一个主机有多个网络接口,每个网络接口有一个IP
地址
对TCP
客户,执行bind
指定IP
地址,则为后续sockfd
上发出的IP
数据报限定了源IP
地址
对TCP
客户,执行bind
不指定IP
地址,则为后续sockfd
上发出的IP
数据报的源IP
地址采取发出的网络接口的IP
地址
对TCP
服务器,执行bind
指定IP
地址,则限定后续sockfd
监听时只接收目的IP
为这里指定IP
地址的连接请求
对TCP
服务器,执行bind
不指定IP
地址,则限定后续sockfd
监听时接收目的IP
为主机上任意网络接口IP
地址的连接请求.且采用客户发来SYN
的目的IP
地址作为服务器上对应已经连接套接字的本端IP
地址.
2.IP
地址为INADDR_ANY
表不指定,端口为0
表不指定,端口未指定时,由内核为此sockfd
指定
3.主机可收取IP
地址为其任一网络接口IP
地址的数据报,数据报递送到进程,则需要按数据报的(源IP
地址,源端口,目的IP
地址,目的端口)递送到本机中与其匹配的套接字的缓冲区.随后,可通过匹配套接字调read
读入该套接字缓冲区的数据到进程.
listen
int listen(int sockfd, int backlog);
内核为任何一个给定的监听套接字维护两个队列
1.未完成连接队列
收到SYN
,尚未完成三路握手的连接.一般在未完成连接队列已经满,接着收到SYN
,后续收到的SYN
将忽略(对端会超时重传).
2.已完成连接队列
已经完成三路握手.
已完成连接队列中套接字关联的数据到达主机,即使尚未accept
,该套接字的数据也会放入该套接字的接收缓冲区.待其后续读取.
accept
int accept(
int sockfd,// 监听套接字描述符
// 返回已连接对端进程的协议地址
struct sockaddr* cliaddr,
socklen_t* addrlen);// 值-结果参数
内核为每个服务器进程接受的客户创建一个已连接套接字
close
int close(int sockfd);
每个文件或套接字有一个引用计数,在文件表项中维护.代表引用此文件或套接字的描述符的个数.
fork
造成父进程打开的描述符被复制到子进程,相应的描述符指向文件或套接字的引用计数+1
.
对描述符执行close
时,先将其指向的文件或套接字引用计数-1
,若变为0
,执行清理和释放工作(包含向对端发FIN
).否则,更新引用计数后结束.
套接字引用计数变为0
后,以其为目的地址的write
,将收到RST
.此RST
到达对端后,对端再对其执行write
时,应用层将收到通知.
引用计数变为0
,也会引发向对端发FIN
,FIN
到达对端后,对端发回FIN ACK
,此后对端再对其执行write
,应用层也将收到通知.此后对端仍可对其执行read
,直到读完其发送缓冲区,再读时,应用层将收到通知.
以其为源地址的read
,该套接字发送缓冲区的数据由内核负责继续发给对端.发送完毕后,对端再对其执行read
,应用层将收到通知.该套接字接收缓冲区的数据被直接丢弃.(因为后续也无法在取出尚在缓冲区的数据了)
getsockname和getpeername
// 获取套接字[源IP地址,源端口]
int getsockname(
int sockfd,
struct sockaddr*,
socklen_t*);// 值-结果参数
// 获取套接字[目的IP地址,目的端口]
int getpeername(
int sockfd,
struct sockaddr*,
socklen_t*);// 值-结果参数
信号中断
accept, read, write, select, open
等被信号中断后,应该处理为再次重启
connect
被信号中断后,不可再次connect
,应select
,等待connect
关联的套接字可写,可读(收到对端SYN ACK
后,可写,可读)
并发式服务端注意点
1.对fork
的子进程,需捕获其终止所产生的SIGCHLD
,避免产生僵尸进程
2.需要处理被中断的系统调用(再次执行/connect
下的调select
等待)
3.SIGCHLD
处理函数需要回收所有子进程(防止处理期间产生多次SIGCHLD
,未排队,到导致有的SIGCHLD
被忽略,进而无法回收其对应的子进程的情况)
SIGCHLD处理及其正确性说明
信号语义:
1.一次注册,持续有效
2.信号处理期间,对应信号被屏蔽.且可指定其他屏蔽信号.处理结束恢复信号屏蔽.
3.系统调用使用时需考虑到对信号中断处理
4.信号不排队.阻塞期间产生多次同类信号,只记录一次.该信号不阻塞时,提交一次.
void sig_chld(ing signo)
{
xxx
while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
...
}
xxx
}
对SIGCHLD
处理的原因是,若不处理,则因终止而发送SIGCHLD
的子进程,在被init
回收前将成为僵尸进程.占据系统资源.
对SIGCHLD
的处理要求,不能遗漏调任何发送SIGCHLDL
的子进程.
而信号的特点有,在SIGCHLD
处理过程中,到达的一个或多个SIGCHLD
不会立即再次触发信号处理,只会在待处理信号集合先记录.待SIGCHLD
处理过程结束,若待处理信号集合仍有SIGCHLD
,则再次触发处理过程.
在非SIGCHLD
处理过程中,到达一个SIGCHLD
,会中断进程现有处理转而执行SIGCHLD
处理过程.
所有终止的进程及其相关信息全部记录在内核中,这些信息可以通过waitpid
来获取,并由此让内核释放这些信息.上述处理中,每次处理过程,我们对当前已知的所有终止子进程完成一次处理并结束.
即使,上述while
后的语句执行中,处理函数尚未返回前,又产生了多个子进程终止,这些子进程无法被本次信号处理所回收,且由于信号的特点,在信号处理函数结束后,待处理信号集合SIGCHLD
被设置,再次触发一次SIGCHLD
信号处理,但由于信号处理while
的特点,在本次处理中我们仍然可以回收完全上次未被回收的一个或多个子进程,故整个过程下来,可以保证所有子进程均能得到回收,不会遗漏.
另一个地方是,由于waitpid
指定WNOHANG
,故在没有子进程终止时,可以让回收处理快速结束,不会无意义的阻塞在那里.
TCP套接字归纳
1.内核接收到发往本机的套接字数据
若本机有对应的TCP
连接套接字可接收数据,将到来的数据放入对应套接字的接收缓冲区
若本机没有对应的TCP
连接套接字可接收数据,发回RST响应消息
对接收到的SYN
请求,会交给监听套接字处理,处理结果是产生一个已连接套接字,并发回SYN ACK
& SYN
.
2.进程使用套接字进行read
若此套接字接收缓冲区有数据可读,读取不超过缓冲区大小和可读数据量的数据
若此套接字接收缓冲区无数据可读,若已经收到对端的FIN
,则read
返回0
若已经收到对端的FIN
,且收到对端的RST
(对端异常终止或采用close
而非shut
执行关闭,对端上内核中的不再维护相应套接字信息),read
返回错误(ECONNRESET
)
若未收到对端的FIN
,则read
阻塞等待,若在read
阻塞等待的过程中,由于前面内核从套接字发送缓冲区取数据写往对端时,由于对端主机关机或网络中断造成重传并最终超时时,内核会引发异步通知(前面write
无法写的到对端,内核把此信息在后续调read
时,反馈给应用),进程read
阻塞会被中断,并返回相应错误码ETIMEDOUT/EHOSTUNREACH/ENETUNREACH
.
3.进程使用套接字write
若发送缓冲区可写,写入数据量不超过套接字发送缓冲区可写数据量与要写数据量
若发送缓冲区已满,阻塞等待或返回错误
若进程已经收到对端的RST
,则write
会返回错误EPIPE
并产生SIGPIPE
TCP网络通信数据传递
不同主机上对同类型数据的位数,存储方式不同,在跨越主机传递数据时.
若参与通信各个主机有相同字符集,传递信息统一采取字符串方式,接收后,可以按需求进行解析处理.
若向直接以二进制格式传递(数据对象首字节指针,数据对象大小方式),参与通信各方需针对数据的位数,存储方式达成一致.
缓冲区
TCP
拥有数据接收和发送缓冲区.
本机的read/write
与套接字关联的内核缓冲区交互.
对read
,UDP
内核先完成一个完整的UDP
数据报的接收,然后即会将接收到数据报发给应用.
对write
,UDP
数据报先完整写入内核,然后立即由内核将其发出.