目录

一、TCP 服务的特点

传输层协议主要有 TCP 协议和 UDP 协议,前者相对于后者的特点是:面向连接、字节流和可靠传输。

使用 TCP 协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP 连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。

TCP 协议的这种连接是一对一的,所以基于广播和多播(目标是多个主机地址)的应用程序不能使用 TCP 服务,而无连接的 UDP 则非常适用于广播和多播。

  • TCP 协议是基于字节流的,基于流的数据没有边界(长度)限制,它源源不断地从通信的一端流入另一端。发送端可以逐个字节地向数据流中写入数据,接收端也可以逐个字节地将它们读出。
  • UDP 协议是基于数据报的,每个 UDP 数据报都有一个长度,接收端必须以该长度为最小单位将其所有内容一次性读出,否则数据将被截断。

上面两种区别应用在实际编程中,表现为通信双方是否必须执行相同次数的读、写操作。

  • 当发送端应用程序连续执行多次写操作时,TCP 模块现将这些数据放入 TCP 发送缓冲区中;当 TCP 模块真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个 TCP 报文段发出。因此 TCP 模块发送出的 TCP 报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。
  • 当接收端收到一个或多个 TCP 报文段后,TCP 模块将他们携带的应用程序数据按照 TCP 报文段的序号一次放入 TCP 接收缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次性将 TCP 接收缓冲区的数据全部读出,也可以分多次读取,这取决于用户指定的应用程序读缓冲区的大小。因此应用程序执行的读操作次数和 TCP 模块接收到的 TCP 报文段个数之间也没有固定的数量关系。

综上所述,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念。而对于 UDP ,发送端应用程序每执行一次写操作,UDP 模块就将其封装成一个 UDP 数据报并发送之。接收端必须及时针对每一个 UDP 数据报执行读操作(通过 recvfrom 系统调用),否则就会丢包。并且,如果用户没有指定足够的应用程序缓冲区来读取 UDP 数据,则数据会被截断。

TCP 传输是可靠的:

  • 首先,TCP 协议采用发送应答机制,即发送端发送的每个 TCP 报文段都必须得到接收方的应答,才认为这个 TCP 报文段传输成功;
  • 其次,TCP 协议采用超时重传机制,发送端在发送出一个 TCP 报文段之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段;
  • 最后,因为 TCP 报文段最终是以 IP 数据报发送的,而 IP 数据报到达接收端可能乱序、重复,所以 TCP 协议还会对接收到的 TCP 报文段重排、整理,再交付给应用层。

UDP 协议和 IP 协议一样,提供不可靠服务,都需要上层协议来处理数据确认和超时重传。

二、TCP 结构

  • 16 位端口号(port number):告知主机该报文段是来自哪里(源端口)以及传给那个上层协议或应用程序(目的端口)。进行 TCP 通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号,定义在 /etc/services 文件中;
  • 32 位序号(sequence number):一次 TCP 通信(从建立连接到断开)过程中某一个传输方向上的字节流的每个字节的编号:
    • 假设主机 A 和 B 进行通信,A 发送给 B 的第一个报文中,序号之被系统初始化为某个随机值 ISN (Initial Sequence Number,初始序号值);
    • 那么在该传输方向上,后续的 TCP 报文段中序号值将被系统设置成 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移;
    • 例如存在 ISN ,此时某个报文起始字节为第 1025 字节,那么该报文序号值为:ISN + 1025 。
  • 32 位确认号(acknowledgement number):用作对另一方发送来的 TCP 报文段的响应,其值是收到的 TCP 报文段的序号值加 1 :
    • 假设两主机通信,A 发送除的 TCP 报文段不仅携带自己的序号,而且包含对 B 发送来的 TCP 报文段的确认号。
  • 4 位头部长度(header length):标识该 TCP 头部有多少个 32 位字,因为 32 位最大表示 15 ,因此 TCP 头部最长是 60 字节;
  • 6 位标志位:
    • URG:标识紧急指针(urgent pointer)是否有效;
    • ACK:标识确认号是否有效,若有效则为确认报文段;
    • PSH:提示接收端应用程序应该立即从 TCP 接收缓冲区中读走数据,为接受后续数据腾出空间;
    • RST:表示要求对方重新建立连接,称携带 RST 标志的 TCP 报文为复位报文段;
    • SYN:表示请求建立一个连接,称携带 SYN 标志的 TCP 报文为同步报文段;
    • FIN:通知对方本端要关闭连接了,称携带 FIN 标志的 TCP 报文为结束报文段;
  • 16 位窗口大小(window size):是 TCP 流量控制的一个字段,此处窗口指接收通告窗口(Receiver Window,RWND),告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,以便对方控制发送数据的速度;
  • 16 位校验和(TCP checksum):由发送端填充,接收端对 TCP 报文段执行 CRC 算法以检验 TCP 报文段在传输过程中是否损坏,包括头部和数据部分;
  • 16 位紧急指针(urgent pointer):是一个正的偏移量,它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此这个字段是紧急指针相对当前序号的偏移,用于发送紧急数据。
  • 选项(options):可变长的可选信息:

    • kind:说明选项的类型,部分报文只有 kind ,而没有后两项;
    • length:指定该选项的总长度,包括 kind 和 length 字段占据的 2 字节;
    • info:选项的具体信息。

TCP 选项有 7 种:

kind = 0
kind = 1
kind = 2 length = 4 最大 segment 长度(2 字节)
kind = 3 length = 3 移位数(1 字节)
kind = 4 length = 2
kind = 5 ltngth = N * 8 + 2 第 1 块左边沿 第 1 块右边沿 第 2 块左边沿 第 2 块右边沿
kind = 8 length = 10 时间戳值(4 字节) 时间戳回显应答(4 字节)
  • kind = 0:选项表结束选项;
  • kind = 1:空操作选项,没有特殊含义,一般用于将 TCP 选项的总长度填充为 4 字节的整数倍;
  • kind = 2:最大报文段长度选项,TCP 连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS),通常将其设置为 MTU - 40 字节,减掉的 40 字节表示 TCP 头部和 IP 头部,为避免本机发生 IP 分片,通常将其设置为 1460 字节;
  • kind = 3:窗口扩大因子选项,只能出现在同步报文段中,否则将被忽略。TCP 初始化时,通信双方使用该选项协商接收通告窗口的扩大因子。在 TCP 的头部中,接收通告窗口大小是用 16 位表示的,故最大为 65535 字节,但实际上允许接收通告窗口大小远不止这个数,因此采用该因子。假设 TCP 头部中接收通告窗口大小为 N ,窗口扩大因子(移位数)为 M ,那么 TCP 报文段的实际接收通告窗口大小是 N × 2 M N\times2^M N×2M ,或者说 N 左移 M 位,其中 M 取值范围是 0 ~ 14 。内核变量:/proc/sys/net/ipv4/tcp_window_scaling 用于启动或关闭该选项。
  • kind = 4:选择性确认选项(Selective Acknowledgment,SACK),TCP 通信时,如果某个报文段丢失,则会重传最后被确认的 TCP 报文段后续的所有报文段,这样原先被正确传输的报文段可能被重复发送,从而降低性能。SACK 能够只重发丢失的报文段。内核变量:/proc/sys/net/ipv4/tcp_sack 用于启动或关闭该选项。
  • kind = 5:SACK 实际工作的选项,该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个 4 字节的序号,左边沿表示不连续块的第一个数据的序号,右边沿表示不连续块的最后一个数据的序号的下一个序号。这样一对参数之间的数据是没有收到的。由于一个块信息占用 8 字节,所以 TCP 头部选项中实际上最多可以包含 4 个这样的不连续块。
  • kind = 8:时间戳选项,提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的时间。内核变量:/proc/sys/net/ipv4/tcp_timestamps 用于启动或关闭该选项。

三、TCP 连接的建立和关闭

本节参考:简单理解 TCP 三次握手四次挥手(看一遍你就懂)

为了保证客户端和服务器端的可靠连接,TCP 必须进行三次握手,这是为了确认双方的接受能力和发送能力是否正常:

最开始的时候双方都处于关闭(CLOSED)状态,主动打开连接的为客户端,被动接受连接的是服务器,TCP 服务器进程需要先创建传输控制块 TCB ,时刻准备接受客户进程的连接请求,此时服务器就进入了 LISTEN 监听状态:

  • 第一次握手:TCP 客户进程先创建传输控制块 TCB ,然后向服务器发出连接请求报文,其中 SYN = 1 ,同时选择一个初始序列号 seq = x ,此时 TCP 客户端进入了 SYN-SENT 同步已发送状态;
  • 第二次握手:TCP 服务器收到请求报文后,如果同意连接,则会向客户端发出确认报文,其中 ACK = 1 ,SYN = 1 ,确认号 seq = x + 1 ,同时也要为自己初始化一个序列号 seq = y,此时 TCP 服务器进程进入了 SYN-RCVD 同步收到状态;
  • 第三次握手:TCP 客户端收到确认后,还要向服务器给出确认,其中 ACK = 1 ,ack = y + 1 ,自己的序列号 seq = x + 1 ,此时 TCP 连接建立,客户端进入 ESTABLISH 已建立连接状态。

进行三次握手的原因如下:

  • 第一次握手:客户端向服务器端发送报文,证明客户端的发送能力正常;
  • 第二次握手:服务器端接收到报文并向客户端发送报文,证明服务器端的接受能力、发送能力正常;
  • 第三次握手:客户端向服务器端发送报文,证明客户端的接受能力正常。

当 TCP 连接结束时,需要进行四次挥手进行终止。数据释放完毕后,双方都可释放连接,最开始时,双方均处于 ESTABLISHED 状态,假设客户端主动关闭,服务器被动关闭:

  • 第一次挥手:客户端发出连接释放报文,并且停止发送数据,其中 FIN = 1 ,序列号 seq = u ,此时客户端进入 FIN-WAIT-1(终止等待 1)状态;
  • 第二次挥手:服务器端接收到连接释放报文后,发出确认报文,其中 ACK = 1 ,ack = u + 1 ,序列号 seq = v ,此时服务器端进入了 CLOSE-WAIT 关闭等待状态;
  • 第三次挥手:客户端接收到服务器的确认请求后,会进入 FIN-WAIT-2(终止等待 2)状态,等待服务器发送连接释放报文。服务器将最后的数据发送完毕后,就像客户端发送连接释放报文,然后进入 LAST-ACK(最后确认)状态,等待客户端的确认;
  • 第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,其中 ACK = 1 ,ack = w + 1 ,序列号 seq = u + 1 ,此时客户端就进入了 TIME-WAIT(时间等待)状态,但此时 TCP 连接还未终止,必须经过 2 MSL(最长报文寿命),当客户端撤销响应的 TCB 后, 客户端才会进入 CLOSED 关闭状态,服务器端接收到确认报文后,会立即进入 CLOSED 关闭状态,到这里连接断开,四次挥手完成。

第四次握手要等待 2MSL 是为了保证给客户端发送的第一个 ACK 报文能到达服务器,因为这个 ACK 报文可能丢失,并且 2MSL 是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃,这样新的连接中不会出现旧连接的请求报文。

四、复位报文段

在某些特殊条件下,TCP 连接的一端会向另一端发送携带 RST 标志的报文段,即复位报文段,以通知对方关闭连接或重新建立连接:

  • 访问不存在的端口:当客户端访问一个不存在的端口(或者处于 TIME_WAIT 状态)时,目标主机将给它发送一个复位报文段,由于该报文段接收通告窗口为 0 ,因此客户端不应回复,而是关闭连接或者重新连接;
  • 异常终止连接:给对方发送一个复位报文段后,发送端所有排队等待发送的数据都将被遗弃,接收端收到后应当关闭连接或重新连接;
  • 处于半打开连接:当一方因为异常终止了连接,而另一方没有收到结束报文段,此时接收方还维持着原来的连接,而发送方没有该连接的任何信息,此时接收端的状态称为半打开连接。如果发送端往接收端的连接写入数据,则对方回应一个复位报文段。

五、TCP 数据流

TCP 报文所携带的应用程序数据按照长度分为两种:

  • 交互数据:包含很少的字节,使用交互数据的应用程序(或协议),对实时性要求较高;
  • 成块数据:长度通常为 TCP 报文允许的最大长度,对传输效率要求高,如 ftp 。

1. 交互数据流

服务器在发送确认报文之前,会等待片刻时间,查看本端是否有数据需要发送,如果有则与确认信息一起发出,这被称为延迟确认。而客户端通常由于数据量较小、处理较慢,因此通常直接进行确认而不延迟。

由于广域网数据量大,交互数据流可能经受很大的延迟,并且携带交互数据的微小 TCP 报文段数量一般很多,因此容易导致拥塞发生,可以采用 Nagle 算法进行处理:

  • 首先,它要求通信双方在任意时刻最多只能发送一个未被确认的 TCP 报文,在该报文的确认到达之前不能发送其他报文;
  • 另外,发送方在等待确认的同时收集本端需要发送的微量数据,并在确认到来时以一个 TCP 报文将他们全部发出。

这样就极大地减少了网络上微小 TCP 报文的数量,该算法另一个优点是:确认到达得越快,数据也就发送得越快。

2. 成块数据流

由于成块数据流对传输效率要求较高,因此发送端通常会连续发送多个 TCP 报文段,接收端可以一次性确认这些报文段。而发送端在收到确认报文后能再发送的报文段数量,取决于接收通告窗口的大小。

服务器每发送 4 个 TCP 报文就传送一个 PSH 标志,以通知客户端的应用程序尽快读取数据。

六、带外数据

有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件,因此带外数据比普通数据(带内数据)优先级更高,它应该总是立即被发送,而不论发送缓冲区中是否有排队等待发送的普通数据。带外数据的传输可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中。

UDP 没有实现带外数据传输,TCP 也没有真正的带外数据,而是利用其头部中的紧急指针标志和紧急指针两个字段,给应用程序提供了一种紧急方式。TCP 的紧急方式利用传输普通数据的连接来传输紧急数据,其含义和带外数据类似,因此可以将其称为带外数据。

七、超时重传

TCP 模块为每个 TCP 报文段都维护一个重传定时器,该定时器在 TCP 报文段第一次被发送时启动。如果超时时间内未收到接收方的应答,则将重传该报文并重置定时器。重传时间和重传次数由不同的超时策略决定:

  • /proc/sys/net/ipv4/tcp_retries1 :指定在底层 IP 接管之前 TCP 最少执行的重传次数,默认为 3 ;
  • /proc/sys/net/ipv4/tcp_retries2 :指定连接放弃前 TCP 最多可以执行的重传次数,默认为 15 ;

八、拥塞控制

TCP 模块还能够通过拥塞控制来提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性,Linux 中有很多种拥塞控制的实现方法,如 reno 、vegas 、cubic 等,存放在 /proc/sys/net/ipv4/tcp_congestion_control 文件中。

拥塞控制的最终受控变量是发送端向网络一次连续写入(收到第一条数据的确认之前)的数据量,称为发送窗口(Send Windows,SWND)。由于发送端最终以 TCP 报文来发送数据,因此 SWND 限定了发送端能连续发送 TCP 报文数量,其中 TCP 报文段的最大长度(数据部分)称为 SMSS(Sender Maximum Segment Size,发送者最大段大小),其值一般等于 MSS 。

发送端需要合理的选择 SWND 的大小,由于接收方可通过其接收通告窗口(RWND)来控制发送端的 SWND ,但是对于拥塞控制来说还需在发送端引入拥塞窗口(Congestion Window,CWND)的状态变量。实际的 SWND 值是 RWND 与 CWND 中的较小者,需要注意的是 CWND 实际上是以字节为单位的:

  • 慢启动:TCP 连接建立好后,CWND 被设置为初始值 IW(Initial Window),大小为 2 ~ 4 个 SMSS(新 Linux 内核提高了初始值以减小传输滞后),此时发送端最多能发送 IW 字节的数据。此后发送端每收到接收端的一个确认,其 C W N D + = m i n ( N , S M S S ) CWND += min(N, SMSS) CWND+=min(N,SMSS) ,其中 N 是此次确认中包含的之前未被确认的字节数,这样一来 CWND 将按照指数形式扩大。之所以设计成这样是因为 TCP 模块一开始并不知道网络状况,因此需要一种平滑地方式增加 CWND 。由于指数级增长最终会导致拥塞,因此定义了一个状态变量:慢启动门限(slow start threshold size,ssthresh),当 CWND 的大小超过该值时,TCP 拥塞控制将进入拥塞避免阶段。
  • 拥塞避免:将 CWND 指数型增长的方式改为线性增长,有两种方式:
    • 每个 RTT 时间内按照指数型计算,而不论该 RTT 时间内发送端收到多少个确认;
    • 每收到一个对新数据的确认报文段,如下更新: C W N D + = S M S S × S M S S C W N D CWND+=SMSS\times\frac{SMSS}{CWND} CWND+=SMSS×CWNDSMSS​ 。

当发生拥塞时,判断依据有两个,倘若第一种情况先发生,则不论第二种情况是否发生都采用第一种处理方式:

  • 传输超时,或者说 TCP 重传定时器溢出。此时发生拥塞,采用慢启动和拥塞避免,此时将改变门限值: s s t h r e s h = max ⁡ ( F l i g h t S i z e 2 , 2 × S M S S ) C W M D ≤ S M S S ssthresh=\max(\frac{FlightSize}{2},2\times SMSS) CWMD\le SMSS ssthresh=max(2FlightSize​,2×SMSS)CWMD≤SMSS ,其中 FightSize 是已经发送但未收到确认的字节数;
  • 接收到重复的确认报文段。此时采用快速重传和快速恢复。

下面讨论第二种处理方式,由于发送端收到接收端重复确认有多种可能(TCP 报文丢失、接收端收到乱序 TCP 报文段并重排等),因此拥塞算法需要确认丢失原因从而判断网络是否真的发生拥塞:如果连续收到 3 个重复的确认报文段,则认为未发生拥塞,此时采用快速重传和快速恢复策略:

  • 当收到第 3 个重复的确认报文段时,按传输超时的方式修改门限值,然后立即重传丢失的报文段,最后设置: C W N D = s s t h r e s h + 3 × S M S S CWND=ssthresh+3\times SMSS CWND=ssthresh+3×SMSS ;
  • 每次收到 1 个重复的确认时,设置 C W N D = C W N D + S M S S CWND=CWND+SMSS CWND=CWND+SMSS ,此时发送端可以发送新的 TCP 报文段;
  • 当收到新数据的确认时,设置 C W N D = s s t h r e s h CWND=ssthresh CWND=ssthresh 。

Linux 网络编程学习笔记——三、TCP 协议详解相关推荐

  1. Linux网络编程学习笔记(TCP)

    文章目录 1 字节序 1.1 定义 1.2 字节序转换函数 2 Socket地址 2.1 通用socket地址(实际开发不使用) 2.2 专用socket地址 2 IP地址转换 3 TCP通信流程 3 ...

  2. [Linux网络编程学习笔记]索引

    一.Linux基本知识 [学习笔记]Linux平台的文件I/O操作 [学习笔记]Linux平台的文件,目录及操作 [Linux学习笔记]标准输入输出 [Linux学习笔记]进程概念及控制 [Linux ...

  3. 编程开发:Linux网络编程学习笔记

    非常全面.通俗易懂.值得借鉴的Linux网络编程学习笔记.关键字:linux linux编程 网络编程 linux网络编程 下载地址:点我下载 特别说明:本资源收集于网络,版权归原作者及版权商所有,仅 ...

  4. Linux 网络编程学习笔记

    前言: 本文是学习<Linux 高性能服务器编程(游双 著)>时所记录的重点知识. 一.TCP/IP 协议族 二.IP 协议详解 三.TCP 协议详解 四.HTTP 通信 五.Linux ...

  5. Linux网络编程学习笔记

    声明:1.未经过原作者许可,不可用于商业行为:2.本笔记仅用于知识学习,如有侵权,立即删除. 1.学习链接 黑马程序员-Linux网络编程:https://www.bilibili.com/video ...

  6. [Linux网络编程学习笔记]套接字地址结构

    好久没有看那Linux网络编程这本书了,今天看到了重点部分-TCP套接字.下面先来看看套接字的地址结构 Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构.在头文件<li ...

  7. cdt规约报文用程序解析_程序员必备的学习笔记《TCP/IP详解(二)》

    把这三个协议放到一起学习是因为这三个协议处于同一层,ARP 协议用来找到目标主机的 Ethernet 网卡 Mac 地址,IP 则承载要发 送的消息.数据链路层可以从 ARP 得到数据的传送信息,而从 ...

  8. [读书笔记]C#学习笔记三: C#类型详解..

    前言 这次分享的主要内容有五个, 分别是值类型和引用类型, 装箱与拆箱,常量与变量,运算符重载,static字段和static构造函数. 后期的分享会针对于C#2.0 3.0 4.0 等新特性进行. ...

  9. 【Linux从青铜到王者】第十五篇:Linux网络编程套接字两万字详解

    系列文章目录 文章目录 系列文章目录 前言 一.网络数据的五元组信息 1.理解源IP地址和目的IP地址 2.理解 "端口号" 和 "进程ID" 3.理解源端口号 ...

最新文章

  1. linux 本机内存型号,linux怎么看存储空间型号
  2. llist对象两个属性相乘在相加_Java8使用stream实现list中对象属性的合并(去重并求和)...
  3. dj鲜生-26-登陆时-记住用户名的操作
  4. 【ES6】Set Map数据结构、Iterator遍历器
  5. log4j:WARN Please initialize the log4j system properly 问题解决
  6. Oracle 12C 创建用户失败 - ORA-65096 invalid common user or role name
  7. 如何在app应用中添加支付宝支付功能(解惑版)
  8. 高等数学(第七版)同济大学 习题1-9 个人解答
  9. java解压zip压缩包
  10. I2C协议关于ack和nack的思考
  11. 各位老铁,善财读书会试运营了
  12. 阿里云Linux服务器如何安装ClamAV杀毒软件-最全详细教程
  13. 科研论文配色参考【不断更新】
  14. 正在进行 | 用友企业数智化财务峰会落地广州 高能不断
  15. 20秋PHP作业1,南开17秋学期(清考)《电子商务理论与实践》在线作业1
  16. 【C库函数】 strstr函数详解
  17. Java整合Python方法总结
  18. 抖音话题怎么选热门?为什么要做抖音话题
  19. 凭算法突围,一战赚了 1090 亿,“恐怖” 的张一鸣!
  20. 【原创】java+swing+mysql教室管理系统设计与实现

热门文章

  1. 如何使用win10自带的播放器ACG倍速播放
  2. 宝藏又小众的摄像机3dm犀牛模型素材网站分享
  3. centos8搭建owncloud
  4. 全球及中国嵌入式芯片行业市场调查及投资战略研究报告2022-2028年
  5. 从运放内部工艺讲解钳位机理
  6. 基于android的团购app设计与实现,基于Android平台的团购系统设计与实现
  7. ubuntu 安装中文字库_Linux(Ubuntu,Cent OS)环境安装mkfontscale mkfontdir命令以及中文字库...
  8. 那些简历造假的人,后来都怎么样了?
  9. 多任务看门狗, 喂狗方法
  10. 【企业电子杂志制作】云展网教程 | 如何隐藏电子杂志和书橱中的分享按钮?