shutdown系统调用关闭socket的读通道、写通道或读写通道(由how参数决定,how参数是FREAD和FWRITE的组合)。对于读通道,shutdown丢弃所有进程还没有读走的数据以及调用shutdown之后到达的数据。对于写通道,shutdown使协议作相应的处理。对于TCP,所有剩余的数据将被发送,发送完成后发送FIN。这就是TCP的半关闭特点。shutdown系统调用的代码如下:
int
shutdown(p, uap, retval)
struct proc *p;
register struct shutdown_args /* {
syscallarg(int) s;
syscallarg(int) how;
} */ *uap;
register_t *retval;
{
struct file *fp;
int error;
if (error = getsock(p->p_fd, SCARG(uap, s), &fp)) /*从文件描述符获取file结构*/
return (error);
return (soshutdown((struct socket *)fp->f_data, SCARG(uap, how))); /*调用soshutdown函数*/
}
soshutdown函数的代码如下:
int
soshutdown(so, how)
register struct socket *so;
register int how;
{
register struct protosw *pr = so->so_proto;
how++;
if (how & FREAD) /*关闭读通道*/
sorflush(so);
if (how & FWRITE) /*关闭写通道*/
return ((*pr->pr_usrreq)(so, PRU_SHUTDOWN, /*以PRU_SHUTDOWN命令调用tcp_usrreq函数*/
(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0));
return (0);
}
关闭读通道的sorflush函数的代码如下:
void
sorflush(so)
register struct socket *so;
{
register struct sockbuf *sb = &so->so_rcv;
register struct protosw *pr = so->so_proto;
register int s;
struct sockbuf asb;
sb->sb_flags |= SB_NOINTR;
(void) sblock(sb, M_WAITOK); /*给接收缓冲区加锁*/
s = splimp();
socantrcvmore(so); /*给socket置上SS_CANTRCVMORE位,并唤醒等待在接收缓冲区的进程*/
sbunlock(sb);
asb = *sb;
bzero((caddr_t)sb, sizeof (*sb)); /*清空socket的表示缓冲区的sockbuf结构*/
splx(s);
sbrelease(&asb); /*丢弃接收缓冲区中的所有数据,同时释放mbuf*/
}
tcp_usrreq函数对PRU_SHUTDOWN命令的处理如下:
/*
* Mark the connection as being incapable of further output.
*/
case PRU_SHUTDOWN:
socantsendmore(so); /*给socket置上SS_CANTSENDMORE位,并唤醒等待在发送缓冲区的进程*/
tp = tcp_usrclosed(tp);
if (tp)
error = tcp_output(tp);
break;
tcp_usrclosed函数使TCP连接的状态转移到下一状态。如果需要发送FIN、ACK或ACK报文段,tcp_output函数会根据状态发送相应的报文段。tcp_usrclosed函数的代码如下:
/*
* User issued close, and wish to trail through shutdown states:
* if never received SYN, just forget it. If got a SYN from peer,
* but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN.
* If already got a FIN from peer, then almost done; go to LAST_ACK
* state. In all other cases, have already sent FIN to peer (e.g.
* after PRU_SHUTDOWN), and just have to play tedious game waiting
* for peer to send FIN or not respond to keep-alives, etc.
* We can let the user exit from the close as soon as the FIN is acked.
*/
struct tcpcb *
tcp_usrclosed(tp)
register struct tcpcb *tp;
{
switch (tp->t_state) {
case TCPS_CLOSED:
case TCPS_LISTEN:
case TCPS_SYN_SENT:
tp->t_state = TCPS_CLOSED;
tp = tcp_close(tp); /*这几个状态直接将状态置为CLOSED,然后调用tcp_close函数释放相关资源*/
break;
/*以下几个状态是TCP断开连接的正常状态*/
case TCPS_SYN_RECEIVED:
case TCPS_ESTABLISHED:
tp->t_state = TCPS_FIN_WAIT_1;
break;
case TCPS_CLOSE_WAIT:
tp->t_state = TCPS_LAST_ACK;
break;
}
if (tp && tp->t_state >= TCPS_FIN_WAIT_2) /*FIN_WAIT_2和TIME_WAIT状态,连接已经算断开了*/
soisdisconnected(tp->t_inpcb->inp_socket); /*socket置上SS_CANTRCVMORE和SS_CANTSENDMORE标志,唤醒相关进程*/
return (tp);
}
tcp_close函数,释放协议相关的资源占用的内存。我删去了一个路由特性相关的代码,简化后的代码如下:
/*
* Close a TCP control block:
* discard all space held by the tcp
* discard internet protocol block
* wake up any sleepers
*/
struct tcpcb *
tcp_close(tp)
register struct tcpcb *tp;
{
register struct tcpiphdr *t;
struct inpcb *inp = tp->t_inpcb;
struct socket *so = inp->inp_socket;
register struct mbuf *m;
/* free the reassembly queue, if any */
t = tp->seg_next;
while (t != (struct tcpiphdr *)tp) { /*释放重组队列中所有乱序报文段占用的内存*/
t = (struct tcpiphdr *)t->ti_next;
m = REASS_MBUF((struct tcpiphdr *)t->ti_prev);
remque(t->ti_prev);
m_freem(m);
}
if (tp->t_template)
(void) m_free(dtom(tp->t_template)); /*释放TCP首部和IP首部模板*/
free(tp, M_PCB); /*释放tcpcb结构*/
inp->inp_ppcb = 0;
soisdisconnected(so); /*socket置上SS_CANTRCVMORE和SS_CANTSENDMORE标志,唤醒相关进程*/
/* clobber input pcb cache if we're closing the cached connection */
if (inp == tcp_last_inpcb)
tcp_last_inpcb = &tcb;
in_pcbdetach(inp); /*释放socket和inpcb等结构*/
tcpstat.tcps_closed++;
return ((struct tcpcb *)0);
}
in_pcbdetach函数的代码如下:
int
in_pcbdetach(inp)
struct inpcb *inp;
{
struct socket *so = inp->inp_socket;
so->so_pcb = 0;
sofree(so); /*sofree的主要工作是释放socket的发送和接收缓冲区的数据,然后释放socket结构。如果socket与描述符关联,sofree直接返回!*/
if (inp->inp_options)
(void)m_free(inp->inp_options); /*释放IP选项*/
if (inp->inp_route.ro_rt)
rtfree(inp->inp_route.ro_rt); /*释放路由*/
ip_freemoptions(inp->inp_moptions); /*释放多播路由选项*/
remque(inp); /*将此inpcb结构从全局链表中移除*/
FREE(inp, M_PCB); /*释放inpcb结构*/
}
对于socket类型的描述符,close系统调用会调用soclose函数,它的代码如下:
/*
* Close a socket on last file table reference removal.
* Initiate disconnect if connected.
* Free socket when disconnect complete.
*/
int
soclose(so)
register struct socket *so;
{
int s = splnet(); /* conservative */
int error = 0;
if (so->so_options & SO_ACCEPTCONN) { /*关闭的是监听socket*/
while (so->so_q0) /*终止队列中每个TCP连接*/
(void) soabort(so->so_q0);
while (so->so_q) /*终止队列中每个TCP连接*/
(void) soabort(so->so_q);
}
if (so->so_pcb == 0)
goto discard;
if (so->so_state & SS_ISCONNECTED) { /*socket已建立连接*/
if ((so->so_state & SS_ISDISCONNECTING) == 0) { /*未开始断开连接的工作*/
error = sodisconnect(so); /*开始断开连接*/
if (error)
goto drop;
}
if (so->so_options & SO_LINGER) { /*socket设置了SO_LINGER选项*/
if ((so->so_state & SS_ISDISCONNECTING) &&
(so->so_state & SS_NBIO))
goto drop;
while (so->so_state & SS_ISCONNECTED) /*close前,等待选项设置的时间*/
if (error = tsleep((caddr_t)&so->so_timeo,
PSOCK | PCATCH, netcls, so->so_linger * hz))
break;
}
}
drop:
if (so->so_pcb) { /*如果socket的inpcb结构还存在*/
int error2 =
(*so->so_proto->pr_usrreq)(so, PRU_DETACH, /*以PRU_DETACH命令调用tcp_usrreq函数*/
(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
if (error == 0)
error = error2;
}
discard:
if (so->so_state & SS_NOFDREF)
panic("soclose: NOFDREF");
so->so_state |= SS_NOFDREF; /*socket不与任何描述符关联*/
sofree(so); /*释放socket的发送和接收缓冲区的数据,然后释放socket结构*/
splx(s);
return (error);
}
soabort函数最终会调用tcp_drop函数来(异常)终止一个连接,tcp_drop函数的代码如下:
/*
* Drop a TCP connection, reporting
* the specified error. If connection is synchronized,
* then send a RST to peer.
*/
struct tcpcb *
tcp_drop(tp, errno)
register struct tcpcb *tp;
int errno;
{
struct socket *so = tp->t_inpcb->inp_socket;
if (TCPS_HAVERCVDSYN(tp->t_state)) { /*如果TCP的已接收过SYN报文段,说明已进行了连接同步*/
tp->t_state = TCPS_CLOSED; /*状态置为CLOSED*/
(void) tcp_output(tp); /*发送RST报文段*/
tcpstat.tcps_drops++;
} else
tcpstat.tcps_conndrops++;
if (errno == ETIMEDOUT && tp->t_softerror)
errno = tp->t_softerror;
so->so_error = errno; /*记录传入的错误码*/
return (tcp_close(tp)); /*调用tcp_close函数释放相关的资源*/
}
sodisconnect函数的代码如下:
int
sodisconnect(so)
register struct socket *so;
{
int s = splnet();
int error;
if ((so->so_state & SS_ISCONNECTED) == 0) { /*连接未建立?*/
error = ENOTCONN;
goto bad;
}
if (so->so_state & SS_ISDISCONNECTING) { /*连接正在断开?*/
error = EALREADY;
goto bad;
}
error = (*so->so_proto->pr_usrreq)(so, PRU_DISCONNECT, /*以PRU_DISCONNECT命令调用tcp_usrreq函数*/
(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
bad:
splx(s);
return (error);
}
tcp_usrreq函数对PRU_DISCONNECT命令调用tcp_disconnect函数处理,它的代码如下:
/*
* Initiate (or continue) disconnect.
* If embryonic state, just send reset (once).
* If in ``let data drain'' option and linger null, just drop.
* Otherwise (hard), mark socket disconnecting and drop
* current input data; switch states based on user close, and
* send segment to peer (with FIN).
*/
struct tcpcb *
tcp_disconnect(tp)
register struct tcpcb *tp;
{
struct socket *so = tp->t_inpcb->inp_socket;
if (tp->t_state < TCPS_ESTABLISHED) /*连接尚未建立,直接调用tcp_close释放相关资源*/
tp = tcp_close(tp);
else if ((so->so_options & SO_LINGER) && so->so_linger == 0) /*设置了SO_LINGER选项,并且so_linger值为0*/
tp = tcp_drop(tp, 0); /*调用tcp_drop函数终止连接。连接不经过TIME_WAIT状态,直接跳到CLOSED状态。*/
else { /*正常的TCP连接断开*/
soisdisconnecting(so); /*socket置上SS_ISDISCONNECTING|SS_CANTRCVMORE|SS_CANTSENDMORE位,唤醒相关进程*/
sbflush(&so->so_rcv); /*丢弃所有滞留在接收缓存中的数据,发送缓存中的数据仍保留,tcp_output函数将试图发送剩余的数据*/
tp = tcp_usrclosed(tp); /*更新连接状态*/
if (tp)
(void) tcp_output(tp); /*根据状态发送FIN、ACK或ACK报文段*/
}
return (tp);
}
tcp_usrreq函数对PRU_DETACH命令的处理代码片段如下:
/*
* PRU_DETACH detaches the TCP protocol from the socket.
* If the protocol state is non-embryonic, then can't
* do this directly: have to initiate a PRU_DISCONNECT,
* which may finish later; embryonic TCB's can just
* be discarded here.
*/
case PRU_DETACH: /*在CLOSED、LISTEN、SYN_SENT和SYN_RECEIVED状态直接调用tcp_close函数释放资源*/
if (tp->t_state > TCPS_LISTEN)
tp = tcp_disconnect(tp); /*其余状态调用tcp_disconnect函数执行正常连接断开*/
else
tp = tcp_close(tp);
break;