精华内容
下载资源
问答
  • linux TCP连接配置

    千次阅读 2013-11-15 13:58:56
    tcp_syn_retries :INTEGER 默认值是5 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃...这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1 决定的) tcp_synack_retries :INTEGER 默认值是5

    tcp_syn_retries :INTEGER
    默认值是5
    对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右时间。(对于大负载而物理通信良好的网络而言,这个值偏高,可修改为2.这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1 决定的)

    tcp_synack_retries :INTEGER
    默认值是5
    对于远端的连接请求SYN,内核会发送SYN + ACK数据报,以确认收到上一个 SYN连接请求包。这是所谓的三次握手( threeway handshake)机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。不应该大于255,默认值是5,对应于180秒左右时间。(可以根据上面的 tcp_syn_retries 来决定这个值)

    tcp_keepalive_time :INTEGER
    默认值是7200(2小时)
    当keepalive打开的情况下,TCP发送keepalive消息的频率。(由于目前网络攻击等因素,造成了利用这个进行的攻击很频繁,曾经也有cu的朋友提到过,说如果2边建立了连接,然后不发送任何数据或者rst/fin消息,那么持续的时间是不是就是2小时,空连接攻击? tcp_keepalive_time就是预防此情形的.我个人在做nat服务的时候的修改值为1800秒)

    tcp_keepalive_probes:INTEGER
    默认值是9
    TCP发送keepalive探测以确定该连接已经断开的次数。(注意:保持连接仅在SO_KEEPALIVE套接字选项被打开是才发送.次数默认不需要修改,当然根据情形也可以适当地缩短此值.设置为5比较合适)

    tcp_keepalive_intvl:INTEGER
    默认值为75
    探测消息发送的频率,乘以tcp_keepalive_probes就得到对于从开始探测以来没有响应的连接杀除的时间。默认值为75秒,也就是没有活动的连接将在大约11分钟以后将被丢弃。(对于普通应用来说,这个值有一些偏大,可以根据需要改小.特别是web类服务器需要改小该值,15是个比较合适的值)

    tcp_retries1 :INTEGER
    默认值是3
    放弃回应一个TCP连接请求前﹐需要进行多少次重试。RFC 规定最低的数值是3﹐这也是默认值﹐根据RTO的值大约在3秒 - 8分钟之间。(注意:这个值同时还决定进入的syn连接)

    tcp_retries2 :INTEGER
    默认值为15
    在丢弃激活(已建立通讯状况)的TCP连接之前﹐需要进行多少次重试。默认值为15,根据RTO的值来决定,相当于13-30分钟(RFC1122规定,必须大于100秒).(这个值根据目前的网络设置,可以适当地改小,我的网络内修改为了5)

    tcp_orphan_retries :INTEGER
    默认值是7
    在近端丢弃TCP连接之前﹐要进行多少次重试。默认值是7个﹐相当于 50秒 - 16分钟﹐视 RTO 而定。如果您的系统是负载很大的web服务器﹐那么也许需要降低该值﹐这类 sockets 可能会耗费大量的资源。另外参的考 tcp_max_orphans 。(事实上做NAT的时候,降低该值也是好处显著的,我本人的网络环境中降低该值为3)

    tcp_fin_timeout :INTEGER
    默认值是 60
    对于本端断开的socket连接,TCP保持在FIN-WAIT-2状态的时间。对方可能会断开连接或一直不结束连接或不可预料的进程死亡。默认值为 60 秒。过去在2.2版本的内核中是 180 秒。您可以设置该值﹐但需要注意﹐如果您的机器为负载很重的web服务器﹐您可能要冒内存被大量无效数据报填满的风险﹐FIN-WAIT-2 sockets 的危险性低于 FIN-WAIT-1 ﹐因为它们最多只吃 1.5K 的内存﹐但是它们存在时间更长。另外参考 tcp_max_orphans。(事实上做NAT的时候,降低该值也是好处显著的,我本人的网络环境中降低该值为30)

    tcp_max_tw_buckets :INTEGER
    默认值是180000
    系 统在同时所处理的最大 timewait sockets 数目。如果超过此数的话﹐time-wait socket 会被立即砍除并且显示警告信息。之所以要设定这个限制﹐纯粹为了抵御那些简单的 DoS 攻击﹐千万不要人为的降低这个限制﹐不过﹐如果网络条件需要比默认值更多﹐则可以提高它(或许还要增加内存)。(事实上做NAT的时候最好可以适当地增加该值)

    tcp_tw_recycle :BOOLEAN
    默认值是0
    打开快速 TIME-WAIT sockets 回收。除非得到技术专家的建议或要求﹐请不要随意修改这个值。(做NAT的时候,建议打开它)

     

    tcp_tw_reuse:BOOLEAN
    默认值是0
    该文件表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连接(这个对快速重启动某些服务,而启动后提示端口已经被使用的情形非常有帮助)

    tcp_max_orphans :INTEGER
    缺省值是8192
    系统所能处理不属于任何进程的TCP sockets最大数量。假如超过这个数量﹐那么不属于任何进程的连接会被立即reset,并同时显示警告信息。之所以要设定这个限制﹐纯粹为了抵御那些简单的 DoS 攻击﹐千万不要依赖这个或是人为的降低这个限制(这个值Redhat AS版本中设置为32768,但是很多防火墙修改的时候,建议该值修改为2000)

    tcp_abort_on_overflow :BOOLEAN
    缺省值是0
    当守护进程太忙而不能接受新的连接,就象对方发送reset消息,默认值是false。这意味着当溢出的原因是因为一个偶然的猝发,那么连接将恢复状态。只有在你确信守护进程真的不能完成连接请求时才打开该选项,该选项会影响客户的使用。(对待已经满载的sendmail,apache这类服务的时候,这个可以很快让客户端终止连接,可以给予服务程序处理已有连接的缓冲机会,所以很多防火墙上推荐打开它)

    tcp_syncookies :BOOLEAN
    默认值是0
    只有在内核编译时选择了CONFIG_SYNCOOKIES时才会发生作用。当出现syn等候队列出现溢出时象对方发送syncookies。目的是为了防止syn flood攻击。
    注意:该选项千万不能用于那些没有收到攻击的高负载服务器,如果在日志中出现synflood消息,但是调查发现没有收到synflood攻击,而是合法用户的连接负载过高的原因,你应该调整其它参数来提高服务器性能。参考:
    tcp_max_syn_backlog
    tcp_synack_retries
    tcp_abort_on_overflow
    syncookie严重的违背TCP协议,不允许使用TCP扩展,可能对某些服务导致严重的性能影响(如SMTP转发)。(注意,该实现与BSD上面使用的tcp proxy一样,是违反了RFC中关于tcp连接的三次握手实现的,但是对于防御syn-flood的确很有用.)

    tcp_stdurg :BOOLEAN
    默认值为0
    使用 TCP urg pointer 字段中的主机请求解释功能。大部份的主机都使用老旧的 BSD解释,因此如果您在 Linux 打开它﹐或会导致不能和它们正确沟通。

     

    tcp_max_syn_backlog :INTEGER
    对于那些依然还未获得客户端确认的连接请求﹐需要保存在队列中最大数目。对于超过 128Mb 内存的系统﹐默认值是 1024 ﹐低于 128Mb 的则为 128。如果服务器经常出现过载﹐可以尝试增加这个数字。警告﹗假如您将此值设为大于 1024﹐最好修改 include/net/tcp.h 里面的 TCP_SYNQ_HSIZE ﹐以保持 TCP_SYNQ_HSIZE*16<=tcp_max_syn_backlog ﹐并且编进核心之内。(SYN Flood攻击利用TCP协议散布握手的缺陷,伪造虚假源IP地址发送大量TCP-SYN半打开连接到目标系统,最终导致目标系统Socket队列资源耗 尽而无法接受新的连接。为了应付这种攻击,现代Unix系统中普遍采用多连接队列处理的方式来缓冲(而不是解决)这种攻击,是用一个基本队列处理正常的完 全连接应用(Connect()和Accept() ),是用另一个队列单独存放半打开连接。这种双队列处理方式和其他一些系统内核措施(例如Syn-Cookies/Caches)联合应用时,能够比较有效的缓解小规模的SYN Flood攻击(事实证明<1000p/s)加大SYN队列长度可以容纳更多等待连接的网络连接数,所以对Server来说可以考虑增大该值.)

    tcp_window_scaling :INTEGER
    缺省值为1
    该 文件表示设置tcp/ip会话的滑动窗口大小是否可变。参数值为布尔值,为1时表示可变,为0时表示不可变。tcp/ip通常使用的窗口最大可达到 65535 字节,对于高速网络,该值可能太小,这时候如果启用了该功能,可以使tcp/ip滑动窗口大小增大数个数量级,从而提高数据传输的能力(RFC 1323)。(对普通地百M网络而言,关闭会降低开销,所以如果不是高速网络,可以考虑设置为0)

    tcp_timestamps :BOOLEAN
    缺省值为1
    Timestamps 用在其它一些东西中﹐可以防范那些伪造的 sequence 号码。一条1G的宽带线路或许会重遇到带 out-of-line数值的旧sequence 号码(假如它是由于上次产生的)。Timestamp 会让它知道这是个 '旧封包'。(该文件表示是否启用以一种比超时重发更精确的方法(RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项。)

    tcp_sack :BOOLEAN
    缺省值为1
    使 用 Selective ACK﹐它可以用来查找特定的遗失的数据报--- 因此有助于快速恢复状态。该文件表示是否启用有选择的应答(Selective Acknowledgment),这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段)。(对于广域网通信来说这个选项应该启用,但是这会增加对 CPU 的占用。)

    tcp_fack :BOOLEAN
    缺省值为1
    打开FACK拥塞避免和快速重传功能。(注意,当tcp_sack设置为0的时候,这个值即使设置为1也无效)

    tcp_dsack :BOOLEAN
    缺省值为1
    允许TCP发送"两个完全相同"的SACK。

    tcp_ecn :BOOLEAN
    缺省值为0
    打开TCP的直接拥塞通告功能。

    tcp_reordering :INTEGER
    默认值是3
    TCP流中重排序的数据报最大数量 。 (一般有看到推荐把这个数值略微调整大一些,比如5)

    tcp_retrans_collapse :BOOLEAN
    缺省值为1
    对于某些有bug的打印机提供针对其bug的兼容性。(一般不需要这个支持,可以关闭它)

    tcp_wmem(3个INTEGER变量): min, default, max
    min:为TCP socket预留用于发送缓冲的内存最小值。每个tcp socket都可以在建议以后都可以使用它。默认值为4096(4K)。

    default:为TCP socket预留用于发送缓冲的内存数量,默认情况下该值会影响其它协议使用的net.core.wmem_default 值,一般要低于net.core.wmem_default的值。默认值为16384(16K)。

    max: 用于TCP socket发送缓冲的内存最大值。该值不会影响net.core.wmem_max,"静态"选择参数SO_SNDBUF则不受该值影响。默认值为131072(128K)。(对于服务器而言,增加这个参数的值对于发送数据很有帮助,在我的网络环境中,修改为了51200 131072 204800)

    tcp_rmem (3个INTEGER变量): min, default, max
    min:为TCP socket预留用于接收缓冲的内存数量,即使在内存出现紧张情况下tcp socket都至少会有这么多数量的内存用于接收缓冲,默认值为8K。

    default:为TCP socket预留用于接收缓冲的内存数量,默认情况下该值影响其它协议使用的 net.core.wmem_default 值。该值决定了在tcp_adv_win_scale、tcp_app_win和tcp_app_win=0默认值情况下,TCP窗口大小为65535。默认值为87380

    max:用于TCP socket接收缓冲的内存最大值。该值不会影响 net.core.wmem_max,"静态"选择参数 SO_SNDBUF则不受该值影响。默认值为 128K。默认值为87380*2 bytes。(可以看出,.max的设置最好是default的两倍,对于NAT来说主要该增加它,我的网络里为 51200 131072 204800)

    tcp_mem(3个INTEGER变量):low, pressure, high
    low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。(理想情况下,这个值应与指定给 tcp_wmem 的第 2 个值相匹配 - 这第 2 个值表明,最大页面大小乘以最大并发请求数除以页大小 (131072 * 300 / 4096)。 )

    pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入pressure模式,当内存消耗低于low值时则退出pressure状态。(理想情况下这个值应该是 TCP 可以使用的总缓冲区大小的最大值 (204800 * 300 / 4096)。 )

    high:允许所有tcp sockets用于排队缓冲数据报的页面量。(如果超过这个值,TCP 连接将被拒绝,这就是为什么不要令其过于保守 (512000 * 300 / 4096) 的原因了。 在这种情况下,提供的价值很大,它能处理很多连接,是所预期的 2.5 倍;或者使现有连接能够传输 2.5 倍的数据。 我的网络里为192000 300000 732000)

    一般情况下这些值是在系统启动时根据系统内存数量计算得到的。

    tcp_app_win : INTEGER
    默认值是31
    保留max(window/2^tcp_app_win, mss)数量的窗口由于应用缓冲。当为0时表示不需要缓冲。

    tcp_adv_win_scale : INTEGER
    默认值为2
    计算缓冲开销bytes/2^tcp_adv_win_scale(如果tcp_adv_win_scale > 0)或者bytes-bytes/2^(-tcp_adv_win_scale)(如果tcp_adv_win_scale <= 0)。

     

    tcp_rfc1337 :BOOLEAN
    缺省值为0
    这个开关可以启动对于在RFC1337中描述的"tcp 的time-wait暗杀危机"问题的修复。启用后,内核将丢弃那些发往time-wait状态TCP套接字的RST 包.

     

    tcp_low_latency : BOOLEAN
    缺省值为0
    允许 TCP/IP 栈适应在高吞吐量情况下低延时的情况;这个选项一般情形是的禁用。(但在构建Beowulf 集群的时候,打开它很有帮助)

     

    tcp_westwood :BOOLEAN
    缺省值为0
    启用发送者端的拥塞控制算法,它可以维护对吞吐量的评估,并试图对带宽的整体利用情况进行优化;对于 WAN 通信来说应该启用这个选项。

     

    tcp_bic :BOOLEAN
    缺省值为0
    为快速长距离网络启用 Binary Increase Congestion;这样可以更好地利用以 GB 速度进行操作的链接;对于 WAN 通信应该启用这个选项。

     

     

     

    # 以下一段为抵抗syn flood攻击,平时建议关闭

    sysctl -w net.ipv4.tcp_syncookies=1              # tcp syncookie,默认关闭

    sysctl -w net.ipv4.tcp_max_syn_backlog=1280   # syn队列,默认1024,> 1280可能工作不稳定,需要修改内核源码参数

    sysctl -w net.ipv4.tcp_synack_retries=2             # syn-ack握手状态重试次数,默认5,遭受syn-flood攻击时改为1或2

    sysctl -w net.ipv4.tcp_syn_retries=2                  # 外向syn握手重试次数,默认4

     

     

    # 以下一段为应对tcp connect连接耗尽攻击,如果开启iptables connlimit模块可禁用

    # 有严重连接性能影响和不稳定因素,慎用

    sysctl -w tcp_tw_recycle=1                           # 默认0,tw快速回收

    sysctl -w tcp_tw_reuse=1                             # 默认0,tw重用

    sysctl -w tcp_keepalive_intvl=60                    # 默认75,tcp keeplive探测轮询时间

    sysctl -w tcp_keepalive_probes=3                  # 默认9,tcp keeplive探测轮询次数

    sysctl -w tcp_keepalive_time=1800                # 默认7200,tcp keeplive时间

    sysctl -w tcp_fin_timeout=30                        # 默认60,tcp fin状态超时时间

    #sysctl -w net.ipv4.tcp_retries1=2                     # tcp连接重传参数,慎用

    #sysctl -w net.ipv4.tcp_retries2=8

     

    sysctl -w net.ipv4.ip_conntrack_max=65535          # 增大iptables状态跟踪表

    展开全文
  • 下面我们介绍典型的TCP连接的建立与关闭过程(不包括任何数据传输) 一、TCP连接的建立(三次握手) TCP连接的建立分为3步: 1.主动开启者(通常称为客户端)发送一个SYN报文段(即一个在TCP头部的SYN位字段置位...

    • 一个TCP连接通常分为3个阶段:启动、数据传输(也称为“连接已建立”)、退出
    • 下面我们介绍典型的TCP连接的建立与关闭过程(不包括任何数据传输)

    一、TCP连接的建立(三次握手)

    TCP连接的建立分为3步:

    • 1.主动开启者(通常称为客户端)发送一个SYN报文段(即一个在TCP头部的SYN位字段置位的TCP/IP数据包),并指明自己想要连接的端口号和它的客户端初始序列号(记为ISN(c),本文下面介绍)。通常,客户端还会借此发送一个或多个选项。客户端发送的这个SYN报文段称作段1
    • 2.服务器也发送自已的SYN报文段作为响应,并包含了它的初始序列号(记作ISN(s))。该段称作段2。此外,为了确认客户端的SYN,服务器将其包含的ISN(c)数值加1后作为返回的ACK数值。因此,每发送一个SYN,序列号就会自动加1。这样如果出现丢失的情况,该SYN段将会重传
    • 3.为了确认服务器的SYN,客户端将ISN(s)的数值加1后作为返回的ACK数值。这称为段3

    • 简化之后如下图所示:(在显示相关状态的同时省略了选项与初始序列号等细节)

    • 通过发送上述3个报文段就能够完成一个TCP连接的建立。它们也常称作三次握手。 三次握手的目的不仅在于让通信双方了解一个连接正在建立,还在于利用数据包的选项来承载特殊的信息,交换初始序列号(Initial Sequence Number。ISN)
    • 发送首个SYN的一方被认为是主动地打开一个连接。如上文所述,它通常是一个客户端。连接的另一方会接收这个SYN,并发送下一个SYN,因此它是被动地打开一个连接。 通常,这一方称为服务器 (后面将会介绍一种客户端与服务器同时打开一个连接的情况,但非常少见)

    二、TCP连接的关闭(四次挥手)

    • 连接的任何一方都能够发起一个关闭操作。此外,该过程还支持双方同时关闭连接的操作,但这种情况非常少见
    • 在传统的情况下,负责发起关闭连接的通常是客户端。然而,一些服务器(例如web服务器)在对请求做出响应之后也会发起一个关闭操作。通常一个关闭操作是由应用程序提出关闭连接的请求而引发的(例如使用系统调用close)
    • TCP协议规定通过发送一个FIN段(即FIN位字段置位的TCP报文段)来发起关闭操作。只有当连接双方都完成关闭操作后,才构成一个完整关闭

    TCP连接的关闭分为4步:

    • 1.连接的主动关闭者发送一个FIN段指明接收者希望看到的自已当前的序列号(K)。FIN段还包含了一个ACK段用于确认对方最近一次发来的数据(标记为L)
    • 2.连接的被动关闭者将K的数值加1作为响应的ACK值,以表明它已经成功接收到主动关闭者发送的FIN。此时,上层的应用程序会被告知连接的另一端已经提出了关闭的请求。通常,这将导致应用程序发起自已的关闭操作
    • 3.接着,被动关闭者将身份转变为主动关闭者,并发送自已的FIN。该报文段的序列号为L
    • 4.为了完成连接的关闭,最后发送的报文段还包含一个ACK用于确认上一个FIN。值得注意的是,如果出现FIN丢失的情况,那么发送方将重新传输直到接收到一个ACK确认为止

    • 简化之后如下图所示:(在显示相关状态的同时省略了选项与初始序列号等细节)

    三、TCP半关闭

    • 如前所述,TCP支持半关闭操作。虽然一些应用需要此项功能,但它并不常见
    • 为了实现这一特性,API必须为应用程序提供一种基本的表达方式。例如,应用程序表明“我已经完成了数据的发送工作,并发送一个FIN给对方,但是我仍然希望接收来自对方的数据直到它发送一个FIN给我” 。伯克利套接字的API提供了半关闭操作。应用程序只需要调用shutdown()函数来代替基本的cIose()函数,就能实现上述操作。然而,绝大部分应用程序仍然会调用close()函数来同时关闭一条连接的两个传输方向
    • shutdown系统调用参阅:https://blog.csdn.net/qq_41453285/article/details/89647130
    • close系统调用参阅:https://blog.csdn.net/qq_41453285/article/details/89179532

    图示

    • 下图展示了一个正在使用的半关闭示例。图中左侧的客户端负责发起半关闭操作,然而在实际应用中,通信的任何一方都能完成这项工作
    • 首先发送的两个报文段与TCP正常关闭完全相同:初始者发送的FIN,接着是接收者回应该FIN的ACK
    • 由于接收到半关闭的一方仍能够发送数据,因此下图中的后续操作与四次挥手关闭连接的情况不同不同。虽然下图在ACK之后只描述了一个数据段的传输过程,但实际应用时可以传输任意数量的数据段(后面“TCP数据流与窗口管理”将会详细地讨论数据段的交换与确认细节)。当接收半关闭的一方完成数据发送后,它将会发送一个FIN来关闭本方的连接,同时向发起半关闭的应用程序发出一个文件尾指示。当第2个FIN被确认之后,整个连接完全关闭

    四、TCP的同时打开

    • 虽然两个应用程序同时主动打开连接看似不大可能,但是在特定安排的情况下是有可能实现的。通信双方在接收到来自对方的SYN之前必须先发送一个SYN;两个SYN必须经过网络送达对方。该场景还要求通信双方都拥有一个IP地址与端口号,并且将其告知对方。 上述情况十分少见(前面文章介绍的防火墙“打孔”技术除外),一旦发生,可称其为同时打开
    • 例如:
      • 主机A的一个应用程序通过本地的7777端口向主机B的8888端口发送一个主动打开请求,与此同时主机B的一个应用程序也通过本地的8888端口向主机A的7777端口提出一个主动打开请求,此时就会发生一个同时打开的情况
      • 这种情况不同于主机A的一个客户端连接主机B的一个服务器,而同时又有主机B的一个客户端连接主机A的一个服务器的情况。在这种情况下,服务器始终是连接的被动打开者而非主动打开者,而备自的客户端也会选择不同的端口号。因此,它们可以被区分为两个不同的TCP连接

    图例

    • 下图显示了在一个同时打开过程中报文段的交换情况
    • 一个同时打开过程需要交换4个报文段,比普通的三次握手增加了一个
    • 由于通信双方都扮演了客户端与服务器的角色,因此不能够将任何一方称作客户端或服务器

    五、TCP的同时关闭

    • 同时关闭与同时连接并没有太大区别
    • 前面的正常关闭过程,通信一方(通常是客户端,但不一定总是)提出主动关闭请求,并发送首个FIN。在同时关闭中,通信双方都会完成上述工作

    图例

    • 下图显示了在一个同时关闭中需要交换的报文段
    • 同时关闭需要交换与正常关闭相同数量的报文段。两者真正的区别在于:报文段序列是交叉的还是顺序的。下文将会介绍TCP实现中同时打开与同时关闭操作使用特殊状态这一不常见的方法

    六、初始化序列号(Seq、ISN)

    为什么要用序列号

    • 当一个连接打开时,任何拥有合适的IP地址、端口号、符合逻辑的序列号(即在窗口中)以及正确校验和的报文段都将被对方接收。然而,这也引人了另一个问题。在一个连接中,TCP报文段在经过网络路由后可能会存在延迟抵达与排序混乱的情况。为了解决这一问题,需要仔细选择初始序列号
    • 在发送用于建立连接的SYN之前,通信双方会选择一个初始序列号。初始序列号会随时间而改变,因此每一个连接都拥有不同的初始序列号
    • [RFCO793]指出初始序列号可被视为一个32位的计数器。该计数器的数值每4微秒加1。此举的目的在于为一个连接的报文段 安排序列号,以防止出现与其他连接的序列号重叠的情况。尤其对于同一连接的两个不同实例而言,新的序列号也不能出现重叠的情况

    序列号重叠问题

    • 由于一个TCP连接是被一对端点所唯一标识的,其中包括由2个IP地址与2个端口号构成的4元组,因此即便是同一个连接也会出现不同的实例。如果连接由于某个报文段的长时间延迟而被关闭,然后又以相同的4元组被重新打开,那么可以相信延迟的报文段又会被视为有效数据重新进入新连接的数据流中
    • 上述情况会令人十分烦恼。通过采取一些步骤来避免连接实例间的序列号重叠问题,能够将风险降至最低。即便如此,一个对数据完整性有较高要求的应用程序也可以在应用层利用CRC或校验和保证所需数据在传输过程中没有出现任何错误。在任何情况下这都是一种很好的方法,并已普遍用于大文件的传输

    序列号带来的攻击

    • 如前文所述,一个TCP报文段只有同时具备连接的4元组与当前活动窗口的序列号,才会在通信过程中被对方认为是正确的。然而,这也从另一个侧面反映了TCP的脆弱性:如果选择合适的序列号、 IP地址以及端口号,那么任何人都能伪造出一个TCP报文段,从而打断TCP的正常连接[RFC5961]
    • 一种抵御上述行为的方法是使初始序列号(或者临时端口号)变得相对难以被猜出,而另一种方法则是加密

    不同系统对序列号的实现

    • 现代系统通常采用半随机的方法选择初始序列号。证书报告CA-2001-09 [CERTISN]讨论了这一方法的具体实现细节
    • Linux系统采用一个相对复杂的过程来选择它的初始序列号。 它采用基于时钟的方案,并且针对每一个连接为时钟设置随机的偏移量。随机偏移量是在连接标识(即4元组)的基础上利用加密散列函数得到的。散列函数的输人每隔5分钟就会改变一次。在32位的初始序列号中,最高的8位是一个保密的序列号,而剩余的备位则由散列函数生成。上述方法所生成的序列号很难被猜出,但依然会随着时间而逐步增加
    • 据报告显示,Windows系统使用了一种基于RC4[S94]的类似方案

    七、TCP连接演示案例

    • 使用telnet工具连接服务器“10.0.0.2:80”

    • telnet命令是建立在TCP连接的基础上的。当Telnet应用程序连接23(Telnet协议的众所周知端口)以外的端口,它将不能用于应用协议。它仅仅将自已的字节输人拷贝至TCP连接中,反之亦然。当一个Web服务器接收到进人的连接请求时,它首先需要等待对web页面的请求。在这种情况下,我们不能提供这样的请求,因此服务器不会产生任何数据。这些均符合我们的期望,因为我们只对连接建立与终止过程中的数据包交换感兴趣

    最终结果

    • 客户端发送的SYN报文段所包含的初始序列号为685506836,广告窗口为65535,该报文段还包含了若干其他选项(选项在后面介绍)
    • 第二个报文段既包含了服务器的SYN还包含了对客户端请求的ACK确认。它的序列号(服务器的初始序列号)为1479690171,ACK号为685506837。ACK号仅比客户端的初始序列号大1,说明服务器已经成功接收到了客户端的初始序列号。该报文段同样也包含了一个广告窗口以表明服务器愿意接收64240个字节。第三个数据包将最终完成三次握手,它的ACK号为1479690172。ACK号是不断累积的,并且总是表明ACK发送者希望接收到的下一个序列号(而不是它上一个接收到的序列号)
    • 在4.4秒暂停之后,Telnet应用程序被要求关闭连接。这使得客户端发送第4个报文段FIN。FIN的序列号为685506837,并由第5个报文段确认(ACK号为685506838)。稍后,服务器会发送自已的FIN,对应的序列号为1479690172。该报文段对客户端的FIN进行了再次确认。值得注意的是,该报文段的PSH位被置位。虽然这样并不会对连接的关闭过程产生实质影响,但通常用于说明服务器将不会再发送任何数据。最后一个报文段用于对服务器的FIN进行确认,ACK号为1479690173

    • 从图中我们还会发现SYN报文段包含了一个或多个选项。这些选项需要占用TCP头部额外的空间。例如,第一个TCP头部的长度为44字节,比最小的长度长24字节。TCP也提供了若干选项,下文将详细介绍当一个连接无法建立时如何使用这些选项

    八、TCP连接建立超时演示案例

    • 本节的若干实例将会展示连接不能建立的情况。一种显而易见的情况是服务器关闭。为了模拟这种情况,我们将telnet命令发送给一个处于同一子网的不存在的主机。在不修改ARP表的情况下,上述做法会使客户端收到一个“无法到达主机”的错误消息后退出。由 于没有接收到针对之前发送的ARP请求而返回的ARP响应,因此会产生“无法到达主机”的消息。如果我们能事先在ARP表中为这个不存在的主机添加一条记录,那 么系统就不需要发送ARP请求,而会马上根据TCP/IP协议尝试与这个不存在的主机建立联系。相关的命令如下:

    • 上述例子选择的MAC地址00:00:1a:1b:1c:1d不能与局域网中其他主机的MAC冲突,除此之外并无特别。超时发生在发送初始命令后的3.2分钟。由于没有主机响应,例子中所 有的报文段都是由客户端产生的。下图显示了使用Wireshark软件在摘要模式下获得的输出结果

    • 有趣的是这些输出结果显示了客户端TCP为了建立连接频繁地发送SYN报文段。在首个报文段发送后仅3秒第二个报文段就被发送出去,第三个报文段则是这之后的6秒,而第四个报文段则在第三个报文段发送12秒以后被发送出去,以此类推。这一行为被称作指数 回退。在讨论以太网CSMA/CD介质访问控制协议时我们也曾见过这样的行为。然而,这两种指数回退也略有不同。此处的每一次回退数值都是前一次数值的两倍,而在以太网中最大的回退数值是上一次的两倍,实际的回退数值则需要随机选取
    • 一些系统可以配置发送初始SYN的次数,但通常选择一个相对较小的数值50。Linux系统中,系统配置变量net.ipv4.tcp_syn_retries表示了在一次主动打开申请中尝试重新发送SYN报文段的最大次数。相应地,变量net.ipv4.tcp_synack_retries表示在响应对方的一个主 动打开请求时尝试重新发送SYN + ACK报文段的最大次数。此外,它能够在设定Linux专有的TCP_SYNCNT套接字选项的基础上用于个人连接。正如上面所介绍的,默认的数值为重试5次。两次重新传输之间的指数回退时间是TCP拥塞管理响应的一部分。当我们讨论 kam算法时再仔细研究

    九、连接与转换器

    • 在前面NAT的文章中,我们已经讨论了一些协议(比如TCP和UDP)如何利用传统的NAT转换地址与端口号。我们还讨论了IP数据包如何在IPv6与IPv4两个版本间进行转换。当TCP使用NAT时,伪头部的校验和通常需要调整(使用校验和中立地址修改器的情况除外)。其他协议也使用伪头部校验和。因为计算包含了与传输层、网络层相关的信息。
    • 当一个TCP连接首次被建立时,NAT能够根据报文段的SYN位探明这一事实。同样,可以通过检查SYN + ACK报文段与ACK报文段所包含的序列号来判断一个连接是否已经完全建立。上述方法还适用于连接的终止。通过在NAT中实现一部分TCP状态机能够跟踪连接,包括当前状态、备方向的序列号以及相关的ACK号。这种状态跟踪是典型的NAT实现方法
    • 当NAT扮演编辑者的角色并且向传输协议的数据负载中写人内容时,就会涉及一些更复杂的问题。对于TCP而言,它将会包括在数据流中添加与删除数据,并由此影响序列号(与报文段)的长度。此举会影响到校验和,但也会影响数据的顺序。如果利用NAT在数据 流中插人或删除数据这些数值都要做出适当调整。如果NAT的状态与终端主机的状态不同步,连接就无法正确进行下去。因此,上述做法会带来一定的脆弱性
    展开全文
  • 可靠的关闭TCP连接 在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发 fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以 主动方要处于 TIME_WAIT 状态,而不能...

    一、查看哪些IP连接本机

    netstat -an

    二、查看TCP连接数

    1)统计80端口连接数

    netstat -nat|grep -i "80"|wc -l

    2)统计httpd协议连接数

    ps -ef|grep httpd|wc -l

    3)、统计已连接上的,状态为“established

    netstat -na|grep ESTABLISHED|wc -l

    4)、查出哪个IP地址连接最多,将其封了.

    netstat -na|grep ESTABLISHED|awk {print 5}|awk -F: {print5}|awk -F: {print1}|sort|uniq -c|sort -r +0n

    netstat -na|grep SYN|awk {print 5}|awk -F: {print5}|awk -F: {print1}|sort|uniq -c|sort -r +0n

    ---------------------------------------------------------------------------------------------

    1、查看apache当前并发访问数:

    netstat -an | grep ESTABLISHED | wc -l

    对比httpd.conf中MaxClients的数字差距多少。

    2、查看有多少个进程数:

    ps aux|grep httpd|wc -l

    3、可以使用如下参数查看数据

    server-status?auto

    #ps -ef|grep httpd|wc -l

    1388

    统计httpd进程数,连个请求会启动一个进程,使用于Apache服务器。

    表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整。

    #netstat -nat|grep -i "80"|wc -l

    4341

    netstat -an会打印系统当前网络链接状态,而grep -i "80"是用来提取与80端口有关的连接的,wc -l进行连接数统计。

    最终返回的数字就是当前所有80端口的请求总数。

    #netstat -na|grep ESTABLISHED|wc -l

    376

    netstat -an会打印系统当前网络链接状态,而grep ESTABLISHED 提取出已建立连接的信息。 然后wc -l统计。

    最终返回的数字就是当前所有80端口的已建立连接的总数。

    netstat -nat||grep ESTABLISHED|wc - 可查看所有建立连接的详细记录

    查看Apache的并发请求数及其TCP连接状态:

      Linux命令:

    netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

    netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

    TIME_WAIT 8947 等待足够的时间以确保远程TCP接收到连接中断请求的确认

    FIN_WAIT1 15 等待远程TCP连接中断请求,或先前的连接中断请求的确认

    FIN_WAIT2 1 从远程TCP等待连接中断请求

    ESTABLISHED 55 代表一个打开的连接

    SYN_RECV 21 再收到和发送一个连接请求后等待对方对连接请求的确认

    CLOSING 2 没有任何连接状态

    LAST_ACK 4 等待原来的发向远程TCP的连接中断请求的确认

    TCP连接状态详解 

    LISTEN: 侦听来自远方的TCP端口的连接请求

    SYN-SENT: 再发送连接请求后等待匹配的连接请求

    SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认

    ESTABLISHED: 代表一个打开的连接

    FIN-WAIT-1: 等待远程TCP连接中断请求,或先前的连接中断请求的确认

    FIN-WAIT-2: 从远程TCP等待连接中断请求

    CLOSE-WAIT: 等待从本地用户发来的连接中断请求

    CLOSING: 等待远程TCP对连接中断的确认

    LAST-ACK: 等待原来的发向远程TCP的连接中断请求的确认

    TIME-WAIT: 等待足够的时间以确保远程TCP接收到连接中断请求的确认

    CLOSED: 没有任何连接状态

      LAST_ACK 5

      SYN_RECV 30

      ESTABLISHED 1597

      FIN_WAIT1 51

      FIN_WAIT2 504

      TIME_WAIT 1057

      其中的

    SYN_RECV表示正在等待处理的请求数;

    ESTABLISHED表示正常数据传输状态;

    TIME_WAIT表示处理完毕,等待超时结束的请求数。

    ---------------------------------------------------------------------------------------------

    查看Apache并发请求数及其TCP连接状态

    查看httpd进程数(即prefork模式下Apache能够处理的并发请求数):

      Linux命令:

    ps -ef | grep httpd | wc -l

      返回结果示例:

      1388

      表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整,我这组服务器中每台的峰值曾达到过2002。

    查看Apache的并发请求数及其TCP连接状态:

      Linux命令:

    netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

    返回结果示例:

      LAST_ACK 5

      SYN_RECV 30

      ESTABLISHED 1597

      FIN_WAIT1 51

      FIN_WAIT2 504

      TIME_WAIT 1057

      其中的SYN_RECV表示正在等待处理的请求数;ESTABLISHED表示正常数据传输状态;TIME_WAIT表示处理完毕,等待超时结束的请求数。

      状态:描述

      CLOSED:无连接是活动 的或正在进行

      LISTEN:服务器在等待进入呼叫

      SYN_RECV:一个连接请求已经到达,等待确认

      SYN_SENT:应用已经开始,打开一个连接

      ESTABLISHED:正常数据传输状态

      FIN_WAIT1:应用说它已经完成

      FIN_WAIT2:另一边已同意释放

      ITMED_WAIT:等待所有分组死掉

      CLOSING:两边同时尝试关闭

      TIME_WAIT:另一边已初始化一个释放

      LAST_ACK:等待所有分组死掉

     

     

     

     

    如发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决,

    vim /etc/sysctl.conf

    编辑文件,加入以下内容:

    net.ipv4.tcp_syncookies = 1

    net.ipv4.tcp_tw_reuse = 1

    net.ipv4.tcp_tw_recycle = 1

    net.ipv4.tcp_fin_timeout = 30

    然后执行 /sbin/sysctl -p 让参数生效。

    net.ipv4.tcp_syncookies = 1 表示开启SYN cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

    net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

    net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

    net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

    下面附上TIME_WAIT状态的意义:

    客户端与服务器端建立TCP/IP连接后关闭SOCKET后,服务器端连接的端口

    状态为TIME_WAIT

    是不是所有执行主动关闭的socket都会进入TIME_WAIT状态呢?

    有没有什么情况使主动关闭的socket直接进入CLOSED状态呢?

    主动关闭的一方在发送最后一个 ack 后

    就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间

    这个是TCP/IP必不可少的,也就是“解决”不了的。

    也就是TCP/IP设计者本来是这么设计的

    主要有两个原因

    1。防止上一次连接中的包,迷路后重新出现,影响新连接

    (经过2MSL,上一次连接中所有的重复包都会消失)

    2。可靠的关闭TCP连接

    在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发

    fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以

    主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

    TIME_WAIT 并不会占用很大资源的,除非受到攻击。

    还有,如果一方 send 或 recv 超时,就会直接进入 CLOSED 状态

     

    如何合理设置apache httpd的最大连接数?

    手头有一个网站在线人数增多,访问时很慢。初步认为是服务器资源不足了,但经反复测试,一旦连接上,不断点击同一个页面上不同的链接,都能迅速打开,这种现象就是说明apache最大连接数已经满了,新的访客只能排队等待有空闲的链接,而如果一旦连接上,在keeyalive 的存活时间内(KeepAliveTimeout,默认5秒)都不用重新打开连接,因此解决的方法就是加大apache的最大连接数。

    1.在哪里设置?

    apache 2.24,使用默认配置(FreeBSD 默认不加载自定义MPM配置),默认最大连接数是250

    在/usr/local/etc/apache22/httpd.conf中加载MPM配置(去掉前面的注释):

    # Server-pool management (MPM specific)

    Include etc/apache22/extra/httpd-mpm.conf

    可见的MPM配置在/usr/local/etc/apache22/extra/httpd-mpm.conf,但里面根据httpd的工作模式分了很多块,哪一部才是当前httpd的工作模式呢?可通过执行 apachectl -l 来查看:

    Compiled in modules:

                  core.c

                  prefork.c

                  http_core.c

                  mod_so.c

    看到prefork 字眼,因此可见当前httpd应该是工作在prefork模式,prefork模式的默认配置是:

    <IfModule mpm_prefork_module>

                    StartServers                      5

                    MinSpareServers                  5

                    MaxSpareServers                  10

                    MaxClients                      150

                    MaxRequestsPerChild              0

    </IfModule>

    2.要加到多少?

    连接数理论上当然是支持越大越好,但要在服务器的能力范围内,这跟服务器的CPU、内存、带宽等都有关系。

    查看当前的连接数可以用:

    ps aux | grep httpd | wc -l

    或:

    pgrep httpd|wc -l

    计算httpd占用内存的平均数:

    ps aux|grep -v grep|awk '/httpd/{sum+=$6;n++};END{print sum/n}'

    由于基本都是静态页面,CPU消耗很低,每进程占用内存也不算多,大约200K。

    服务器内存有2G,除去常规启动的服务大约需要500M(保守估计),还剩1.5G可用,那么理论上可以支持1.5*1024*1024*1024/200000 = 8053.06368

    约8K个进程,支持2W人同时访问应该是没有问题的(能保证其中8K的人访问很快,其他的可能需要等待1、2秒才能连上,而一旦连上就会很流畅)

    控制最大连接数的MaxClients ,因此可以尝试配置为:

    <IfModule mpm_prefork_module>

                    StartServers                      5

                    MinSpareServers                  5

                    MaxSpareServers                  10

                    ServerLimit                    5500

                    MaxClients                    5000

                    MaxRequestsPerChild              100

    </IfModule>

    注意,MaxClients默认最大为250,若要超过这个值就要显式设置ServerLimit,且ServerLimit要放在MaxClients之前,值要不小于MaxClients,不然重启httpd时会有提示。

    重启httpd后,通过反复执行pgrep httpd|wc -l 来观察连接数,可以看到连接数在达到MaxClients的设值后不再增加,但此时访问网站也很流畅,那就不用贪心再设置更高的值了,不然以后如果网站访问突增不小心就会耗光服务器内存,可根据以后访问压力趋势及内存的占用变化再逐渐调整,直到找到一个最优的设置值。

    (MaxRequestsPerChild不能设置为0,可能会因内存泄露导致服务器崩溃)

    更佳最大值计算的公式:

    apache_max_process_with_good_perfermance < (total_hardware_memory / apache_memory_per_process ) * 2

    apache_max_process = apache_max_process_with_good_perfermance * 1.5

    附:

    实时检测HTTPD连接数:

    watch -n 1 -d "pgrep httpd|wc -l"

     



    作者:燕赵娱乐刘晓东
    链接:https://www.jianshu.com/p/863d4f3fddef
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    转载于:https://my.oschina.net/maliang9527/blog/3045583

    展开全文
  • Linux下进行TCP简单通信

    千次阅读 2018-05-09 23:03:34
    体会TCP与UDP编程的不同,UDP编程:http://blog.csdn.net/yueguanghaidao/article/details/7055985二、实验平台Linux操作系统三、实验内容编写LinuxTCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦...

    前面为转载

    一、实验目的

    学习和掌握Linux下的TCP服务器基本原理和基本编程方法,体会TCP与UDP编程的不同,UDP编程:http://blog.csdn.net/yueguanghaidao/article/details/7055985

    二、实验平台

    Linux操作系统

    三、实验内容

    编写Linux下TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址、端口号,并向客户端发送字符串。

    四、实验原理

    使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分,其主要实现过程如图1.1所示。

                                               


        图1.1 TCP客户/服务器的套接字函数


    1、socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。

    -----------------------------------------------------------------
     #include <sys/socket.h>
     int socket(int family,int type,int protocol);    
          返回:非负描述字---成功   -1---失败
     -----------------------------------------------------------------

      第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。

    2、connect函数:当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。

    -----------------------------------------------------------------
     #include <sys/socket.h>      
      int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);  
               返回:0---成功   -1---失败
     -----------------------------------------------------------------

      第一个参数是socket函数返回的套接口描述字;第二和第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。

    这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以IPv4套接口地址结构为例,它以“sockaddr_in”命名,定义在头文件<netinet/in.h>;以下是结构体的内容:

     

    ------------------------------------------------------------------
    struct in_addr {
     in_addr_t s_addr;     /* IPv4地址 */
    }; 
    struct sockaddr_in {
     uint8_t sin_len; /* 无符号的8位整数 */
     sa_family_t sin_family;
     /* 套接口地址结构的地址簇,这里为AF_INET */
     in_port_t sin_port; /* TCP或UDP端口 */
     struct in_addr sin_addr;
     char sin_zero[8];  

    };
         -------------------------------------------------------------------

    3、bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

    -------------------------------------------------------------------
    #include <sys/socket.h>  
     int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen);
     返回:0---成功   -1---失败 
     -------------------------------------------------------------------

      第一个参数是socket函数返回的套接口描述字;第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。

    4、listen函数:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。

    -------------------------------------------------------------------
    #include <sys/socket.h>
     int listen(int sockfd,int backlog);   
     返回:0---成功   -1---失败
     -------------------------------------------------------------------

      第一个参数是socket函数返回的套接口描述字;第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手为完成的连接,accept函数是从以连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。

    5、accept函数:accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。

    -------------------------------------------------------------------
    #include <sys/socket.h>         
     int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);  
      回:非负描述字---成功   -1---失败
     -------------------------------------------------------------------

    第一个参数是socket函数返回的套接口描述字;第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。

    6、write和read函数:当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函数write()和read()进行接收和发送操作。

    (1)write()函数用于数据的发送。

    -------------------------------------------------------------------
    #include <unistd.h>         
     int write(int sockfd, char *buf, int len); 
      回:非负---成功   -1---失败
     -------------------------------------------------------------------

    参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;参数buf是指向一个用于发送信息的数据缓冲区;len指明传送数据缓冲区的大小。

     

    (2)read()函数用于数据的接收。

    -------------------------------------------------------------------
    #include <unistd.h>         
     int read(int sockfd, char *buf, intlen);  
      回:非负---成功   -1---失败
     -------------------------------------------------------------------

    参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;参数buf是指向一个用于接收信息的数据缓冲区;len指明接收数据缓冲区的大小。

    7、send和recv函数:TCP套接字提供了send()和recv()函数,用来发送和接收操作。这两个函数与write()和read()函数很相似,只是多了一个附加的参数。

    (1)send()函数用于数据的发送。

    -------------------------------------------------------------------
    #include <sys/types.h>
    #include < sys/socket.h >         
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);  
      回:返回写出的字节数---成功   -1---失败
     -------------------------------------------------------------------

    前3个参数与write()相同,参数flags是传输控制标志。

    (2)recv()函数用于数据的发送。

    -------------------------------------------------------------------
    #include <sys/types.h>
    #include < sys/socket.h >         
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);  
      回:返回读入的字节数---成功   -1---失败
     -------------------------------------------------------------------

    前3个参数与read()相同,参数flags是传输控制标志。

     

    五、实验步骤

    1、登陆进入ubuntu操作系统,新建一个文件,命名为tcpserver.c(为了方便起见,可以进入“home”,再进入用户目录,在用户目录下新建tcpserver.c)。

    2、在tcpserver.c中编写服务器端程序代码并保存。

    3、在“终端”(“Applications”→“附件”→“终端”)中执行命令进入tcpserver.c所在目录。(pwd命令可以显示当前所在目录;ls命令可以显示当前目录下的文件和文件夹信息;cd..命令可以进入上一级目录;cd 目录名 命令可以进入当前所示的某个目录。)

    4、执行命令gcc –o tcpserver tcpserver.c生成可执行文件tcpserver。

    5、执行命令./ tcpserver,观察结果。

    6、认真分析源代码,体会如何编写一个TCP服务器端程序。

    原博代码:

    #include <stdio.h>  //server
           #include <stdlib.h>  
           #include <string.h>  
           #include <unistd.h>  
           #include <sys/types.h>  
           #include <sys/socket.h>  
           #include <netinet/in.h>  
           #include <arpa/inet.h>  
           
           #define  PORT 1234  
           #define  BACKLOG 1  
       
           int main()  
           {  
           int  listenfd, connectfd;  
           struct  sockaddr_in server;  
           struct  sockaddr_in client;  
           socklen_t  addrlen;  
           if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)  
           {  
           perror("Creating  socket failed.");  
           exit(1);  
           }  
           int opt =SO_REUSEADDR;  
           setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
           bzero(&server,sizeof(server));  
           server.sin_family=AF_INET;  
           server.sin_port=htons(PORT);  
           server.sin_addr.s_addr= htonl (INADDR_ANY);  
           if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {  
           perror("Binderror.");  
           exit(1);  
           }     
           if(listen(listenfd,BACKLOG)== -1){  /* calls listen() */  
           perror("listen()error\n");  
           exit(1);  
           }  
           addrlen =sizeof(client);  
           if((connectfd = accept(listenfd,(struct sockaddr*)&client,&addrlen))==-1) {  
           perror("accept()error\n");  
           exit(1);  
           }  
           printf("Yougot a connection from cient's ip is %s, prot is %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));  
           send(connectfd,"Welcometo my server.\n",22,0);  
           close(connectfd);  
           close(listenfd);  
    return 0;  
           }  
    #include<stdio.h>    //client
           #include <stdlib.h>  
           #include<unistd.h>  
           #include<string.h>  
           #include<sys/types.h>  
           #include<sys/socket.h>  
           #include<netinet/in.h>  
           #include<netdb.h>  
       
           #define  PORT 1234  
           #define  MAXDATASIZE 100  
       
           int main(int argc, char *argv[])  
           {  
           int  sockfd, num;  
           char  buf[MAXDATASIZE];  
           struct hostent *he;  
           struct sockaddr_in server;  
           if (argc!=2) {  
           printf("Usage:%s <IP Address>\n",argv[0]);  
           exit(1);  
           }  
           if((he=gethostbyname(argv[1]))==NULL){  
           printf("gethostbyname()error\n");  
           exit(1);  
           }  
           if((sockfd=socket(AF_INET, SOCK_STREAM, 0))==-1){  
           printf("socket()error\n");  
           exit(1);  
           }  
           bzero(&server,sizeof(server));  
           server.sin_family= AF_INET;  
           server.sin_port = htons(PORT);  
           server.sin_addr =*((struct in_addr *)he->h_addr);  
           if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){  
           printf("connect()error\n");  
           exit(1);  
           }  
           if((num=recv(sockfd,buf,MAXDATASIZE,0)) == -1){  
           printf("recv() error\n");  
           exit(1);  
           }  
           buf[num-1]='\0';  
           printf("Server Message: %s\n",buf);  
           close(sockfd);  
    return 0;  
    }  
    自己的代码
        #include <stdio.h>  
        #include <sys/types.h>  
        #include <sys/socket.h>  
        #include <netinet/in.h>  
        #include <arpa/inet.h>  
          
        int main(int argc, char *argv[])  
        {  
            int server_sockfd;//服务器端套接字  
            int client_sockfd;//客户端套接字  
            int len;  
            struct sockaddr_in my_addr;   //服务器网络地址结构体  
            struct sockaddr_in remote_addr; //客户端网络地址结构体  
            int sin_size;  
            char buf[BUFSIZ];  //数据传送的缓冲区  
            memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零  
            my_addr.sin_family=AF_INET; //设置为IP通信  
            my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上  
            my_addr.sin_port=htons(8000); //服务器端口号  
              
            /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/  
            if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  
            {    
                perror("socket");  
                return 1;  
            }  
           
                /*将套接字绑定到服务器的网络地址上*/  
            if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)  
            {  
                perror("bind");  
                return 1;  
            }  
              
            /*监听连接请求--监听队列长度为5*/  
            listen(server_sockfd,5);  
              
            sin_size=sizeof(struct sockaddr_in);  
              
            /*等待客户端连接请求到达*/  
            if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)  
            {  
                perror("accept");  
                return 1;  
            }  
            printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));  
            len=send(client_sockfd,"Welcome Client!\n",21,0);//发送欢迎信息  
              
            /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/  
            while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)  
            {  
                buf[len]='\0';  
                printf("Welcome Client!\nReceived:%s\n",buf);  
                if(send(client_sockfd,buf,len,0)<0)  
                {  
                    perror("write");  
                    return 1;  
                }  
            }  
            close(client_sockfd);  
            close(server_sockfd);  
                return 0;  
        }  
    

        #include <stdio.h>  
        #include <sys/types.h>  
        #include <sys/socket.h>  
        #include <netinet/in.h>  
        #include <arpa/inet.h>  
          
        int main(int argc, char *argv[])  
        {  
            int client_sockfd;  
            int len;  
            struct sockaddr_in remote_addr; //服务器端网络地址结构体  
            char buf[BUFSIZ];  //数据传送的缓冲区  
            memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零  
            remote_addr.sin_family=AF_INET; //设置为IP通信  
            remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址  
            remote_addr.sin_port=htons(8000); //服务器端口号  
              
            /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/  
            if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  
            {  
                perror("socket");  
                return 1;  
            }  
              
            /*将套接字绑定到服务器的网络地址上*/  
            if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)  
            {  
                perror("connect");  
                return 1;  
            }  
            printf("connected to server\n"); 
            len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息  
                 buf[len]='\0';  
            printf("%s",buf); //打印服务器端信息  
              
            /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/  
            while(1)  
            {  
                printf("Enter string to send:");  
                scanf("%s",buf);  //可以发送想发送的信息,如 hello,server!
                if(!strcmp(buf,"quit"))  
                    break;  
                len=send(client_sockfd,buf,strlen(buf),0);  
                len=recv(client_sockfd,buf,BUFSIZ,0);  
                buf[len]='\0';  
                printf("received:%s\n",buf);  
            }  
            close(client_sockfd);//关闭套接字  
                return 0;  
        }  
    效果图



    基于UDP的补充知识
    https://blog.csdn.net/three_bird/article/details/51302354?_biz=MjM5OTA1MDUyMA==&mid=407358558&idx=2&sn=b21877f23bf4063fa311185009c1f0b7&scene=0


    ----------------------------------------------------------------------------分割线

    转载阮一峰老师通俗易懂的TCP/IP网络通信过程   http://www.ruanyifeng.com/

    我们每天使用互联网,你是否想过,它是如何实现的?

      全世界几十亿台电脑,连接在一起,两两通信。上海的某一块网卡送出信号,洛杉矶的另一块网卡居然就收到了,两者实际上根本不知道对方的物理位置,你不觉得这是很神奇的事情吗?

      互联网的核心是一系列协议,总称为"互联网协议"(Internet Protocol Suite)。它们对电脑如何连接和组网,做出了详尽的规定。理解了这些协议,就理解了互联网的原理。

      下面就是我的学习笔记。因为这些协议实在太复杂、太庞大,我想整理一个简洁的框架,帮助自己从总体上把握它们。为了保证简单易懂,我做了大量的简化,有些地方并不全面和精确,但是应该能够说清楚互联网的原理。

      =================================================

      互联网协议入门

      作者:阮一峰

      一、概述

      1. 1 五层模型

      互联网的实现,分成好几层。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。

      用户接触到的,只是最上面的一层,根本没有感觉到下面的层。要理解互联网,必须从最下层开始,自下而上理解每一层的功能。

      如何分层有不同的模型,有的模型分七层,有的分四层。我觉得,把互联网分成五层,比较容易解释。

      如上图所示,最底下的一层叫做"实体层"(Physical Layer),最上面的一层叫做"应用层"(Application Layer),中间的三层(自下而上)分别是"链接层"(Link Layer)、"网络层"(Network Layer)和"传输层"(Transport Layer)。越下面的层,越靠近硬件;越上面的层,越靠近用户。

      它们叫什么名字,其实并不重要。只需要知道,互联网分成若干层就可以了。

      1. 2 层与协议

      每一层都是为了完成一种功能。为了实现这些功能,就需要大家都遵守共同的规则。

      大家都遵守的规则,就叫做"协议"(protocol)。

      互联网的每一层,都定义了很多协议。这些协议的总称,就叫做"互联网协议"(Internet Protocol Suite)。它们是互联网的核心,下面介绍每一层的功能,主要就是介绍每一层的主要协议。

      二、实体层

      我们从最底下的一层开始。

      电脑要组网,第一件事要干什么?当然是先把电脑连起来,可以用光缆、电缆、双绞线、无线电波等方式。

      这就叫做"实体层",它就是把电脑连接起来的物理手段。它主要规定了网络的一些电气特性,作用是负责传送 0 和 1 的电信号。

      三、链接层

      3. 1 定义

      单纯的 0 和 1 没有任何意义,必须规定解读方式:多少个电信号算一组?每个信号位有何意义?

      这就是"链接层"的功能,它在"实体层"的上方,确定了 0 和 1 的分组方式。

      3. 2 以太网协议

      早期的时候,每家公司都有自己的电信号分组方式。逐渐地,一种叫做"以太网"(Ethernet)的协议,占据了主导地位。

      以太网规定,一组电信号构成一个数据包,叫做"帧"(Frame)。每一帧分成两个部分:标头(Head)和数据(Data)。

      "标头"包含数据包的一些说明项,比如发送者、接受者、数据类型等等;"数据"则是数据包的具体内容。

      "标头"的长度,固定为 18 字节。"数据"的长度,最短为 46 字节,最长为 1500 字节。因此,整个"帧"最短为 64 字节,最长为 1518 字节。如果数据很长,就必须分割成多个帧进行发送。

      3. 3 MAC 地址

      上面提到,以太网数据包的"标头",包含了发送者和接受者的信息。那么,发送者和接受者是如何标识呢?

      以太网规定,连入网络的所有设备,都必须具有"网卡"接口。数据包必须是从一块网卡,传送到另一块网卡。网卡的地址,就是数据包的发送地址和接收地址,这叫做 MAC 地址。

      每块网卡出厂的时候,都有一个全世界独一无二的 MAC 地址,长度是 48 个二进制位,通常用 12 个十六进制数表示。

      前 6 个十六进制数是厂商编号,后 6 个是该厂商的网卡流水号。有了 MAC 地址,就可以定位网卡和数据包的路径了。

      3. 4 广播

      定义地址只是第一步,后面还有更多的步骤。

      首先,一块网卡怎么会知道另一块网卡的 MAC 地址?

      回答是有一种 ARP 协议,可以解决这个问题。这个留到后面介绍,这里只需要知道,以太网数据包必须知道接收方的 MAC 地址,然后才能发送。

      其次,就算有了 MAC 地址,系统怎样才能把数据包准确送到接收方?

      回答是以太网采用了一种很"原始"的方式,它不是把数据包准确送到接收方,而是向本网络内所有计算机发送,让每台计算机自己判断,是否为接收方。

      上图中,1号计算机向 2 号计算机发送一个数据包,同一个子网络的 3 号、4号、5号计算机都会收到这个包。它们读取这个包的"标头",找到接收方的 MAC 地址,然后与自身的 MAC 地址相比较,如果两者相同,就接受这个包,做进一步处理,否则就丢弃这个包。这种发送方式就叫做"广播"(broadcasting)。

      有了数据包的定义、网卡的 MAC 地址、广播的发送方式,"链接层"就可以在多台计算机之间传送数据了。

      四、网络层

      4. 1 网络层的由来

      以太网协议,依靠 MAC 地址发送数据。理论上,单单依靠 MAC 地址,上海的网卡就可以找到洛杉矶的网卡了,技术上是可以实现的。

      但是,这样做有一个重大的缺点。以太网采用广播方式发送数据包,所有成员人手一"包",不仅效率低,而且局限在发送者所在的子网络。也就是说,如果两台计算机不在同一个子网络,广播是传不过去的。这种设计是合理的,否则互联网上每一台计算机都会收到所有包,那会引起灾难。

      互联网是无数子网络共同组成的一个巨型网络,很像想象上海和洛杉矶的电脑会在同一个子网络,这几乎是不可能的。

      因此,必须找到一种方法,能够区分哪些 MAC 地址属于同一个子网络,哪些不是。如果是同一个子网络,就采用广播方式发送,否则就采用"路由"方式发送。("路由"的意思,就是指如何向不同的子网络分发数据包,这是一个很大的主题,本文不涉及。)遗憾的是,MAC 地址本身无法做到这一点。它只与厂商有关,与所处网络无关。

      这就导致了"网络层"的诞生。它的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做"网络地址",简称"网址"。

      于是,"网络层"出现以后,每台计算机有了两种地址,一种是 MAC 地址,另一种是网络地址。两种地址之间没有任何联系,MAC 地址是绑定在网卡上的,网络地址则是管理员分配的,它们只是随机组合在一起。

      网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理 MAC 地址。

      4. 2 IP 协议

      规定网络地址的协议,叫做 IP 协议。它所定义的地址,就被称为 IP 地址。

      目前,广泛采用的是 IP 协议第四版,简称 IPv4。这个版本规定,网络地址由 32 个二进制位组成。

      习惯上,我们用分成四段的十进制数表示 IP 地址,从0.0.0.0一直到 255.255.255.255。

      互联网上的每一台计算机,都会分配到一个 IP 地址。这个地址分成两个部分,前一部分代表网络,后一部分代表主机。比如,IP 地址 172.16.254.1,这是一个 32 位的地址,假定它的网络部分是前 24 位(172.16.254),那么主机部分就是后 8 位(最后的那个1)。处于同一个子网络的电脑,它们 IP 地址的网络部分必定是相同的,也就是说 172.16.254.2 应该与 172.16.254.1 处在同一个子网络。

      但是,问题在于单单从 IP 地址,我们无法判断网络部分。还是以 172.16.254.1 为例,它的网络部分,到底是前 24 位,还是前 16 位,甚至前 28 位,从 IP 地址上是看不出来的。

      那么,怎样才能从 IP 地址,判断两台计算机是否属于同一个子网络呢?这就要用到另一个参数"子网掩码"(subnet mask)。

      所谓"子网掩码",就是表示子网络特征的一个参数。它在形式上等同于 IP 地址,也是一个 32 位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP 地址 172.16.254.1,如果已知网络部分是前 24 位,主机部分是后 8 位,那么子网络掩码就是 11111111.11111111.11111111.00000000,写成十进制就是 255.255.255.0。

      知道"子网掩码",我们就能判断,任意两个 IP 地址是否处在同一个子网络。方法是将两个 IP 地址与子网掩码分别进行 AND 运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。

      比如,已知 IP 地址 172.16.254.1 和 172.16.254.233 的子网掩码都是 255.255.255.0,请问它们是否在同一个子网络?两者与子网掩码分别进行 AND 运算,结果都是 172.16.254.0,因此它们在同一个子网络。

      总结一下,IP 协议的作用主要有两个,一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一个子网络。

      4. 3 IP 数据包

      根据 IP 协议发送的数据,就叫做 IP 数据包。不难想象,其中必定包括 IP 地址信息。

      但是前面说过,以太网数据包只包含 MAC 地址,并没有 IP 地址的栏位。那么是否需要修改数据定义,再添加一个栏位呢?

      回答是不需要,我们可以把 IP 数据包直接放进以太网数据包的"数据"部分,因此完全不用修改以太网的规格。这就是互联网分层结构的好处:上层的变动完全不涉及下层的结构。

      具体来说,IP 数据包也分为"标头"和"数据"两个部分。

      "标头"部分主要包括版本、长度、IP 地址等信息,"数据"部分则是 IP 数据包的具体内容。它放进以太网数据包后,以太网数据包就变成了下面这样。

      IP 数据包的"标头"部分的长度为 20 到 60 字节,整个数据包的总长度最大为 65,535字节。因此,理论上,一个 IP 数据包的"数据"部分,最长为 65,515字节。前面说过,以太网数据包的"数据"部分,最长只有 1500 字节。因此,如果 IP 数据包超过了 1500 字节,它就需要分割成几个以太网数据包,分开发送了。

      4. 4 ARP 协议

      关于"网络层",还有最后一点需要说明。

      因为 IP 数据包是放在以太网数据包里发送的,所以我们必须同时知道两个地址,一个是对方的 MAC 地址,另一个是对方的 IP 地址。通常情况下,对方的 IP 地址是已知的(后文会解释),但是我们不知道它的 MAC 地址。

      所以,我们需要一种机制,能够从 IP 地址得到 MAC 地址。

      这里又可以分成两种情况。第一种情况,如果两台主机不在同一个子网络,那么事实上没有办法得到对方的 MAC 地址,只能把数据包传送到两个子网络连接处的"网关"(gateway),让网关去处理。

      第二种情况,如果两台主机在同一个子网络,那么我们可以用 ARP 协议,得到对方的 MAC 地址。ARP 协议也是发出一个数据包(包含在以太网数据包中),其中包含它所要查询主机的 IP 地址,在对方的 MAC 地址这一栏,填的是 FF:FF:FF:FF:FF:FF,表示这是一个"广播"地址。它所在子网络的每一台主机,都会收到这个数据包,从中取出 IP 地址,与自身的 IP 地址进行比较。如果两者相同,都做出回复,向对方报告自己的 MAC 地址,否则就丢弃这个包。

      总之,有了 ARP 协议之后,我们就可以得到同一个子网络内的主机 MAC 地址,可以把数据包发送到任意一台主机之上了。

      五、传输层

      5. 1 传输层的由来

      有了 MAC 地址和 IP 地址,我们已经可以在互联网上任意两台主机上建立通信。

      接下来的问题是,同一台主机上有许多程序都需要用到网络,比如,你一边浏览网页,一边与朋友在线聊天。当一个数据包从互联网上发来的时候,你怎么知道,它是表示网页的内容,还是表示在线聊天的内容?

      也就是说,我们还需要一个参数,表示这个数据包到底供哪个程序(进程)使用。这个参数就叫做"端口"(port),它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据。

      "端口"是 0 到 65535 之间的一个整数,正好 16 个二进制位。0到 1023 的端口被系统占用,用户只能选用大于 1023 的端口。不管是浏览网页还是在线聊天,应用程序会随机选用一个端口,然后与服务器的相应端口联系。

      "传输层"的功能,就是建立"端口到端口"的通信。相比之下,"网络层"的功能是建立"主机到主机"的通信。只要确定主机和端口,我们就能实现程序之间的交流。因此,Unix 系统就把主机+端口,叫做"套接字"(socket)。有了它,就可以进行网络应用程序开发了。

      5. 2 UDP 协议

      现在,我们必须在数据包中加入端口信息,这就需要新的协议。最简单的实现叫做 UDP 协议,它的格式几乎就是在数据前面,加上端口号。

      UDP 数据包,也是由"标头"和"数据"两部分组成。

      "标头"部分主要定义了发出端口和接收端口,"数据"部分就是具体的内容。然后,把整个 UDP 数据包放入 IP 数据包的"数据"部分,而前面说过,IP 数据包又是放在以太网数据包之中的,所以整个以太网数据包现在变成了下面这样:

      UDP 数据包非常简单,"标头"部分一共只有 8 个字节,总长度不超过 65,535字节,正好放进一个 IP 数据包。

      5. 3 TCP 协议

      UDP 协议的优点是比较简单,容易实现,但是缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。

      为了解决这个问题,提高网络可靠性,TCP 协议就诞生了。这个协议非常复杂,但可以近似认为,它就是有确认机制的 UDP 协议,每发出一个数据包都要求确认。如果有一个数据包遗失,就收不到确认,发出方就知道有必要重发这个数据包了。

      因此,TCP 协议能够确保数据不会遗失。它的缺点是过程复杂、实现困难、消耗较多的资源。

      TCP 数据包和 UDP 数据包一样,都是内嵌在 IP 数据包的"数据"部分。TCP 数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常 TCP 数据包的长度不会超过 IP 数据包的长度,以确保单个 TCP 数据包不必再分割。

      六、应用层

      应用程序收到"传输层"的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。

      "应用层"的作用,就是规定应用程序的数据格式。

      举例来说,TCP 协议可以为各种各样的程序传递数据,比如 Email、WWW、FTP 等等。那么,必须有不同协议规定电子邮件、网页、FTP 数据的格式,这些应用程序协议就构成了"应用层"。

      这是最高的一层,直接面对用户。它的数据就放在 TCP 数据包的"数据"部分。因此,现在的以太网的数据包就变成下面这样。

      至此,整个互联网的五层结构,自下而上全部讲完了。这是从系统的角度,解释互联网是如何构成的。下一次,我反过来,从用户的角度,自上而下看看这个结构是如何发挥作用,完成一次网络数据交换的。


    七、一个小结

      先对前面的内容,做一个小结。

      我们已经知道,网络通信就是交换数据包。电脑A向电脑B发送一个数据包,后者收到了,回复一个数据包,从而实现两台电脑之间的通信。数据包的结构,基本上是下面这样:

      发送这个包,需要知道两个地址:

    对方的 MAC 地址

    对方的 IP 地址

      有了这两个地址,数据包才能准确送到接收者手中。但是,前面说过,MAC 地址有局限性,如果两台电脑不在同一个子网络,就无法知道对方的 MAC 地址,必须通过网关(gateway)转发。

      上图中,1号电脑要向 4 号电脑发送一个数据包。它先判断 4 号电脑是否在同一个子网络,结果发现不是(后文介绍判断方法),于是就把这个数据包发到网关A。网关A通过路由协议,发现 4 号电脑位于子网络B,又把数据包发给网关B,网关B再转发到 4 号电脑。

      1号电脑把数据包发到网关A,必须知道网关A的 MAC 地址。所以,数据包的目标地址,实际上分成两种情况:

    场景数据包地址
    同一个子网络对方的 MAC 地址,对方的 IP 地址
    非同一个子网络网关的 MAC 地址,对方的 IP 地址

      发送数据包之前,电脑必须判断对方是否在同一个子网络,然后选择相应的 MAC 地址。接下来,我们就来看,实际使用中,这个过程是怎么完成的。

      八、用户的上网设置

      8. 1 静态 IP 地址

      你买了一台新电脑,插上网线,开机,这时电脑能够上网吗?

      通常你必须做一些设置。有时,管理员(或者 ISP)会告诉你下面四个参数,你把它们填入操作系统,计算机就能连上网了:

    本机的 IP 地址

    子网掩码

    网关的 IP 地址

    DNS 的 IP 地址

      下图是 Windows 系统的设置窗口。

      这四个参数缺一不可,后文会解释为什么需要知道它们才能上网。由于它们是给定的,计算机每次开机,都会分到同样的 IP 地址,所以这种情况被称作"静态 IP 地址上网"。

      但是,这样的设置很专业,普通用户望而生畏,而且如果一台电脑的 IP 地址保持不变,其他电脑就不能使用这个地址,不够灵活。出于这两个原因,大多数用户使用"动态 IP 地址上网"。

      8. 2 动态 IP 地址

      所谓"动态 IP 地址",指计算机开机后,会自动分配到一个 IP 地址,不用人为设定。它使用的协议叫做 DHCP 协议

      这个协议规定,每一个子网络中,有一台计算机负责管理本网络的所有 IP 地址,它叫做"DHCP 服务器"。新的计算机加入网络,必须向"DHCP 服务器"发送一个"DHCP 请求"数据包,申请 IP 地址和相关的网络参数。

      前面说过,如果两台计算机在同一个子网络,必须知道对方的 MAC 地址和 IP 地址,才能发送数据包。但是,新加入的计算机不知道这两个地址,怎么发送数据包呢?

      DHCP 协议做了一些巧妙的规定。

      8. 3 DHCP 协议

      首先,它是一种应用层协议,建立在 UDP 协议之上,所以整个数据包是这样的:

      (1)最前面的"以太网标头",设置发出方(本机)的 MAC 地址和接收方(DHCP 服务器)的 MAC 地址。前者就是本机网卡的 MAC 地址,后者这时不知道,就填入一个广播地址:FF-FF-FF-FF-FF-FF。

      (2)后面的"IP 标头",设置发出方的 IP 地址和接收方的 IP 地址。这时,对于这两者,本机都不知道。于是,发出方的 IP 地址就设为0.0.0.0,接收方的 IP 地址设为 255.255.255.255。

      (3)最后的"UDP 标头",设置发出方的端口和接收方的端口。这一部分是 DHCP 协议规定好的,发出方是 68 端口,接收方是 67 端口。

      这个数据包构造完成后,就可以发出了。以太网是广播发送,同一个子网络的每台计算机都收到了这个包。因为接收方的 MAC 地址是 FF-FF-FF-FF-FF-FF,看不出是发给谁的,所以每台收到这个包的计算机,还必须分析这个包的 IP 地址,才能确定是不是发过自己的。当看到发出方 IP 地址是0.0.0.0,接收方是 255.255.255.255,于是 DHCP 服务器知道"这个包是发过我的",而其他计算机就可以丢弃这个包。

      接下来,DHCP 服务器读出这个包的数据内容,分配好 IP 地址,发送回去一个"DHCP 响应"数据包。这个响应包的结构也是类似的,以太网标头的 MAC 地址是双方的网卡地址,IP 标头的 IP 地址是 DHCP 服务器的 IP 地址(发出方)和 255.255.255.255(接收方),UDP 标头的端口是 67(发出方)和 68(接收方),分配给请求端的 IP 地址和本网络的具体参数则包含在 Data 部分。

      新加入的计算机收到这个响应包,于是就知道了自己的 IP 地址、子网掩码、网关地址、DNS 服务器等等参数。

      8. 4 上网设置:小结

      这个部分,需要记住的就是一点:不管是"静态 IP 地址"还是"动态 IP 地址",电脑上网的首要步骤,是确定四个参数。这四个值很重要,值得重复一遍:

    本机的 IP 地址

    子网掩码

    网关的 IP 地址

    DNS 的 IP 地址

      有了这几个数值,电脑就可以上网"冲浪"了。接下来,我们来看一个实例,当用户访问网页的时候,互联网协议是怎么运作的。

      九、一个实例:访问网页

      9. 1 本机参数

      我们假定,经过上一节的步骤,用户设置好了自己的网络参数:

    本机的 IP 地址:192.168.1.100

    子网掩码:255.255.255.0

    网关的 IP 地址:192.168.1.1

    DNS 的 IP 地址:8.8.8.8

      然后他打开浏览器,想要访问 Google,在地址栏输入了网址:www.google.com。

      这意味着,浏览器要向 Google 发送一个网页请求的数据包。

      9. 2 DNS 协议

      我们知道,发送数据包,必须要知道对方的 IP 地址。但是,现在,我们只知道网址 www.google.com,不知道它的 IP 地址。

      DNS 协议可以帮助我们,将这个网址转换成 IP 地址。已知 DNS 服务器为8.8.8.8,于是我们向这个地址发送一个 DNS 数据包(53端口)。

      然后,DNS 服务器做出响应,告诉我们 Google 的 IP 地址是 172.194.72.105。于是,我们知道了对方的 IP 地址。

      9. 3 子网掩码

      接下来,我们要判断,这个 IP 地址是不是在同一个子网络,这就要用到子网掩码。

      已知子网掩码是 255.255.255.0,本机用它对自己的 IP 地址 192.168.1.100,做一个二进制的 AND 运算(两个数位相同,结果为1,否则为0),计算结果为 192.168.1.0;然后对 Google 的 IP 地址 172.194.72.105 也做一个 AND 运算,计算结果为 172.194.72.0。这两个结果不相等,所以结论是,Google 与本机不在同一个子网络。

      因此,我们要向 Google 发送数据包,必须通过网关 192.168.1.1 转发,也就是说,接收方的 MAC 地址将是网关的 MAC 地址。

      9. 4 应用层协议

      浏览网页用的是 HTTP 协议,它的整个数据包构造是这样的:

      HTTP 部分的内容,类似于下面这样:

    GET / HTTP/1.1

    Host: www.google.com

    Connection: keep-alive

    User-Agent: Mozilla/5.0 (Windows NT 6.1) ......

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

    Accept-Encoding: gzip,deflate,sdch

    Accept-Language: zh-CN,zh;q=0.8

    Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3

    Cookie: ... ...

      我们假定这个部分的长度为 4960 字节,它会被嵌在 TCP 数据包之中。

      9. 5 TCP 协议

      TCP 数据包需要设置端口,接收方(Google)的 HTTP 端口默认是 80,发送方(本机)的端口是一个随机生成的 1024-65535之间的整数,假定为 51775。

      TCP 数据包的标头长度为 20 字节,加上嵌入 HTTP 的数据包,总长度变为 4980 字节。

      9. 6 IP 协议

      然后,TCP 数据包再嵌入 IP 数据包。IP 数据包需要设置双方的 IP 地址,这是已知的,发送方是 192.168.1.100(本机),接收方是 172.194.72.105(Google)。

      IP 数据包的标头长度为 20 字节,加上嵌入的 TCP 数据包,总长度变为 5000 字节。

      9. 7 以太网协议

      最后,IP 数据包嵌入以太网数据包。以太网数据包需要设置双方的 MAC 地址,发送方为本机的网卡 MAC 地址,接收方为网关 192.168.1.1 的 MAC 地址(通过 ARP 协议得到)。

      以太网数据包的数据部分,最大长度为 1500 字节,而现在的 IP 数据包长度为 5000 字节。因此,IP 数据包必须分割成四个包。因为每个包都有自己的 IP 标头(20字节),所以四个包的 IP 数据包的长度分别为 1500、1500、1500、560。

      9. 8 服务器端响应

      经过多个网关的转发,Google 的服务器 172.194.72.105,收到了这四个以太网数据包。

      根据 IP 标头的序号,Google 将四个包拼起来,取出完整的 TCP 数据包,然后读出里面的"HTTP 请求",接着做出"HTTP 响应",再用 TCP 协议发回来。

      本机收到 HTTP 响应以后,就可以将网页显示出来,完成一次网络通信。

      这个例子就到此为止,虽然经过了简化,但它大致上反映了互联网协议的整个通信过程。



    展开全文
  • 基于Linux下的TCP编程

    万次阅读 多人点赞 2011-09-14 18:27:31
    基于LinuxTCP网络编程一.LinuxTCP编程框架TCP网络编程的流程包含服务器和客户端两种模式。服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;客户端模式则...
  • 断开连接其实从我的角度看不区分客户端和服务器端,任何一方都可以调用close(or closesocket)之类 的函数开始主动终止一个连接。这里先暂时说正常情况。当调用close函数断开一个连接时,主动断开的 一方发送FIN...
  • Linux TCP服务器

    千次阅读 2015-12-01 13:39:48
    一、TCP编程框架  TCP网络包括服务器(server)和...二、LinuxTCP编程流程  根据系统给出API接口,根据上面流程,很容易写出一个简单的TCP应用程序。  TCP服务模式API使用流程:  创建socket()套接字  绑定bind
  • TCP连接问题

    千次阅读 2010-03-17 10:54:00
    TCP 连接的保持并不需要任何额外的操作,但在实际应用中,要长时间保持一个 TCP 连接则会受到诸多因素的影响。本文介绍了几种常见的导致 TCP 连接断连的原因,并在此基础上,以 AIX 系统上 TCP 连接的异常断连为例,...
  • 首先介绍一下TCP连接建立与关闭过程中的状态。TCP连接过程是状态的转换,促使状态发生转换的因素包括用户调用、特定数据包以及超时等,具体状态如下所示:CLOSED:初始状态,表示没有任何连接。LISTEN:Server端的...
  • Linux 网络编程——TCP编程

    万次阅读 多人点赞 2015-05-12 12:33:55
    概述TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 具有以下特点:1)电话系统服务模式的抽象2)每一次完整的数据传输都要经过建立连接、使用连接...
  • Tcptrack跟踪 TCP连接(centos6.5)

    千次阅读 2015-06-10 12:22:23
    摘要:Tcptrack是一个能够显示特定端口上有关TCP连接的嗅探器,它会监视正在发生的所有的连接,并且以一种友好的界面显示相关信息。 Tcptrack是一个能够显示特定端口上有关TCP连接的嗅探器,它会监视正在发生的所有...
  • TCP 连接断连问题剖析

    千次阅读 2018-05-14 10:11:22
    TCP 连接的保持并不需要任何额外的操作,但在实际应用中,要长时间保持一个 TCP 连接则会受到诸多因素的影响。本文介绍了几种常见的导致 TCP 连接断连的原因,并在此基础上,以 AIX 系统上 TCP 连接的异常断连为例,...
  • 基于LinuxTCP网络编程

    千次阅读 2013-11-12 13:04:29
    基于LinuxTCP网络编程 一.LinuxTCP编程框架 TCP网络编程的流程包含服务器和客户端两种模式。服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求...
  • 在任意位置Reset掉任意的TCP连接

    千次阅读 2018-11-07 22:10:24
    漫漫长夜又要降临…黑夜里,我不敢点灯,复明日,阳光下,我不敢睁眼。...其实我是不怎么懂Python的,折腾了大半天之后,竟然对Python产生了兴趣,正好旁边有人碰到了TCP连接被莫名Reset掉的案例,借这个...
  • TCP连接过程解析

    千次阅读 2017-04-07 18:23:15
    讲解传统Socket在建立连接时发生了什么,connectionTimeout/soTimeout的区别,为什么先建立连接后传输数据,关闭连接时发生了什么,time_wait及解决办法,及一些其他的socket参数和选项
  • linux TCP 参数设置

    千次阅读 2013-08-26 10:39:45
    此文为网络转载,对理解linux内核tcp参数设置有一定帮助,设置tcp参数一定要小心谨慎,轻易不要更改线上环境,我贴一下我们线上环境中,sysctl.conf的内容,见文章底部 net.ipv4.tcp_tw_reuse = 1 ...
  • linux下配置TCP/IP的祥解

    千次阅读 2006-04-29 15:27:00
    Linux下配置TCP/IP 本教程首先回顾 TCP/IP 的起源,然后阐述 TCP/IP 发展所依据的模型,最后还讨论 TCP/IP 是如何工作的 — 包括 IP 地址、子网以及路由。打好这些理论基础之后,我们将讨论 Linux 所...
  • Linux-C TCP简单例子

    千次阅读 多人点赞 2019-03-13 20:07:44
    Linux-CTCP简单例子 一、简述 记-使用TCP协议通信的简单例子。 例子1:一个客户端,一个服务端,客户端发送信息,服务端就收信息。 例子2:使用多线程实现服务器与客户端的双向通信。 例子3:使用多路复用...
  • TCP 连接状态

    千次阅读 2017-04-14 14:07:37
    TCP十一种状态 全部11种状态 1. 客户端独有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT 。 2. 服务器独有的:(1)LISTEN (2)SYN_RCVD (3)CLOSE_WAIT (4)LAST_ACK 。...
  • TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不 会被释放。网络服务器程序要同时管理大量连接,所以很有必要...
  • 上一篇文章介绍了 TCP/IP相关协议,socket通信流程和涉及到的各种函数:Socket简单理解本篇将具体解释tcp客户端服务器编程模型相关的代码文章分为4个部分:1. TCP客户端服务器编程模型流程图2. 网络字节序与主机字节序3...
  • linux网络编程之TCP接口详解

    千次阅读 2016-06-26 17:52:22
    对于linux网络编程基于TCP的API做了详细的描述
  • 基于linuxTCP网络聊天室设计与实现

    万次阅读 多人点赞 2016-06-12 09:12:54
    利用Linux实现基于TCP模式的网络聊天程序 主要完成的两大组成部分为:服务器和客户端。 服务器程序主要负责监听客户端发来的消息。 客户端需要登录到服务器端才可以实现正常的聊天功能。该程序是利用进程以及共享...
  • TCP 连接断连问题剖析

    千次阅读 2010-12-07 18:10:00
    TCP 连接的保持并不需要任何额外的操作,但在实际应用中,要长时间保持一个 TCP 连接则会受到诸多因素的影响。本文介绍了几种常见的导致 TCP 连接断连的原因,并在此基础上,以 AIX 系统上 TCP 连接的异常断连为例,...
  • TCP连接中的异常断开情况处理

    千次阅读 2014-07-08 10:25:28
    1. TCP连接中可能出现的异常断开情况 假设存在这样一种情况:在两个不同的主机Machine1、Machine2系统上分别运行两个应用程序Application1、Application2,在Application1与Application2的进程中存在一个TCP链接...
  • tcp_syn_retries :INTEGER 默认值是5 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃...这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1 决定的) tcp_synack_retries :INTEGER 默认
  • TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示。  (1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。  (2) 第二次...
  • LinuxTCP基础编程

    千次阅读 2015-01-14 23:03:00
    关闭套接字fd,结束当前的TCP连接 代码示例 服务器端 // // main.cpp // TCPServer // // Created by God Lin on 15/1/14. // Copyright (c) 2015年 arbboter. All rights reserved. // #...
  • linux TCP 和 socket 参数设置

    千次阅读 2016-04-30 00:37:41
    tcp_syn_retries :INTEGER 默认值是5 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。...这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1 决定的) tcp_synack_retries :INTEGER 默认值是

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,939
精华内容 18,775
关键字:

linux关闭特定tcp连接

linux 订阅