tcp连接的建立与终止

理解TCP连接,需要首先记住以下几点:

  • TCP是双向连接。两个方向的连接可以独立关闭。
  • TCP是基于字节流的连接。每个tcp socket在内核里有接收缓冲区和发送缓冲区。
  • 应用程序只能操纵缓冲区数据,而不能干扰实际的数据发送过程。应用程协议可能有自己的协议格式,但在TCP看来全是一个一个的字节。

下面基于以上3点,谈一下TCP连接的建立、数据传输和终止过程。

建立连接

正常的连接过程需要3路握手,不再赘述。这里主要讲一下连接失败以及异步连接的情况。

正常来讲,在客户端调用connect之前,服务端应该已经调用socket, listen创建好监听套接口了。这样客户端connect应该可以很快返回。在连接建立完成后,服务端调用accept就可以立即返回,否则就阻塞到有客户端连接进来。

如果服务端在相应端口上没有监听socket,那么对于客户端发过来的SYN,服务端就会发送RST,连接失败;

如果客户端connect之前设置socket为O_NONBLOCK,那么调用connect立即返回,并设置errno为EINPROSESS。注意,这并不属于错误,因为这时候内核在为你建立连接。当3步握手完成之后,再调用connect就成功返回了。一般在select/epoll里面,需要监听多个fd的时候,可以使用非阻塞connect,避免应用程序阻塞在建立连接上。

传输数据

发送数据

write/send将数据写入到发送缓冲区。

阻塞型write/send会一直阻塞,直到所有的数据全部写入到发送缓冲区,并返回写入的字节数。

write的行为取决于是否被设置为O_NONBLOCK。

  • 默认为阻塞型,那么write会一直阻塞,直到指定的所有字节全部写入内核缓冲区,除非出错;
  • 如果设置为nonblock,那么一次write只会写入当前可以写入的字节数,并返回已写入字节数;如果缓冲区满,那么返回-1,并设置errno为EAGAIN。

我们知道tcp会对每个发送的字节进行确认,因此被对方ACK的数据就能从发送缓冲区删除,这样缓冲区就又可写了。

write返回,仅表示数据已经写到发送缓冲区。至于数据是否从网卡发送出去了,以及对方是否能够接收到数据,这些还都是未知数。TCP有流控制,每次发送数据的大小,取决于对方ACK里携带的窗口大小,也就是对方接收缓冲区的空闲大小。因此,如果对方应用程序一直不read,它的接收缓冲区就会一直处于饱和状态,那么发送方就不能发送新的数据。这样,发送方的write也就会阻塞。

但是,当对方连接关闭(对方进程死掉了,或者调用了close),那么我方在发送数据过去的时候就会收到对方发过来的RST。这时候,应用程序再调用write/send就会出错,errno是EPIPE,并且会收到SIGPIPE信号。此信号默认会终止进程,因此应用程序不能通过 if (ret < 0) { perror(…);}的形式解析errno,除非捕获SIGPIPE信号。

此外,被中断了,write也会出错,errno为EINTR。此种情况下,一般是重新write一遍,因此需要特殊处理。

接收数据

接收数据,会将接收缓冲区的数据拷贝到用户空间。

可以想象,如果接收缓冲区没有数据,那么read/recv就会阻塞。

对于接收方来说,一次完整的TCP连接,应该从SYN分节开始,以FIN分节结束。每次read都会返回从接收缓冲区拷贝的数据字节数。可能大于0,也可能等于0(收到FIN)。

如果在read FIN之前连接出错,那么read就会返回错误,并可以从errno获得错误信息。例如,被中断了(EINTR)。

注意,以上讨论的都是阻塞型的socket,这也是默认的形式。如果设置socket为O_NONBLOCK,那么缓冲区没数据的话read会返回EWOULDBLOCK或EAGAIN。

对于接受到RST的TCP连接,如果已经收到FIN分节了,那么read会返回0,而不是出错。因为FIN已经表示对方所有的数据都被收到了,没有理由返货错误给应用程序。一般的,应用程序在read返回0时就应该自行处理善后工作了。

终止连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值