zoukankan      html  css  js  c++  java
  • 网络编程

    0.概述

    一些相互连接的、以共享资源为目的的计算机的集合。使用网络能够把多方连接在一起,然后可以进行数据传递。

    (1)tcp/ip协议详解

    计算机诞生之后,产为了通信,产生了各种各样的通信协议,形成了不同的计算机网络,这些计算机网络虽然可以进行内部通信,但是不同的计算机网络之间却无法进行通信。为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇(Internet Protocol Suite)就是通用协议标准。因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。

    (1)ip地址

    IP地址是互联网上每台计算机的唯一标识,IP协议负责把数据从一台计算机通过网络发送到另一台计算机。

    IPV4地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示(点分10进制),目的是便于阅读。

    IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,(采用点分16进制)以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334

    IP分类:

    32比特的ip地址被分为两个部分:
    
             ————网络号(NetWork ID , NID)
    
             ————主机号(Host ID,HID)
    
    IPv4定义了5类ip地址,分别为A,B,C,D,E类地址。

    子网掩码:子网掩码是一个32位的2进制数,其对应网络地址的所有位置都为1,对应于主机地址的所有位置都为0,主要用于识别ip地址中的网络地址和主机地址。

    告知路由器,IP地址的前多少位是网络地址,后多少位(剩余位)是主机地址,使路由器正确判断任意IP地址是否是本网段的,从而正确地进行路由。

    (2)端口

    要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识,为了方便称呼这些数字,则将这些数字称为端口。

    知名端口如下(为了通方便,一些端口被固定下来使用)形成了知名端口:

    (3)传输协议

            tcp协议(电子邮件,web应用)

    简介:
    TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
    TCP通信需要经过创建连接、数据传送、终止连接三个步骤。
    特点
    1. 面向连接
      通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。
      双方间的数据传输都可以通过这一个连接进行。
      完成数据交换后,双方必须断开此连接,以释放系统资源。
      这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。
    2.可靠传输
        1)TCP采用发送应答机制
        TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功
        2)超时重传
        发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。
        TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
        3)错误校验
        TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
        4) 流量控制和阻塞管理
        流量控制用来避免主机发送得过快而使接收方来不及完全收下


    3.三次握手:
      
    (1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
      (2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,
        并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

       (3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,

         ACK是否为  1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

     4.四次挥手:   

       (1)第一次挥手:主机1(可以是客户端,也可以是服务器端),设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;

              这表示主机1没有数据要发送给主机2了;

       (2)第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment NumberSequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,

              我也没有数据要发送了,可以进行关闭连接了;

       (3)第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;

       (4)第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,

              主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

    5.补充:

      (1)为什么需要三次握手,两次不可以吗?或者四次、五次可以吗?

      我们来分析一种特殊情况,假设客户端请求建立连接,发给服务器SYN包等待服务器确认,服务器收到确认后,如果是两次握手,假设服务器给客户端在第二次握手时发送数据,数据从服务器发出,服务器认为连接已经建立,但在发送数据的过程中数据丢失,客户端认为连接没有建立,会进行重传。假设每次发送的数据一直在丢失,客户端一直SYN,服务器就会产生多个无效连接,占用资源,这个时候服务器可能会挂掉。这个现象就是我们听过的“SYN的洪水攻击”。

      总结:第三次握手是为了防止:如果客户端迟迟没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,但问题是:服务器不知道客户端没有收到,所以他会收到两个连接,浪费连接开销。如果每次都是这样,就会浪费多个连接开销。

      (2)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

      首先,MSL即Maximum Segment Lifetime,就是最大报文生存时间,是任何报文在网络上的存在的最长时间,超过这个时间报文将被丢弃。《TCP/IP详解》中是这样描述的:MSL是任何报文段被丢弃前在网络内的最长时间。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒、1分钟、2分钟等。

      TCP的TIME_WAIT需要等待2MSL,当TCP的一端发起主动关闭,三次挥手完成后发送第四次挥手的ACK包后就进入这个状态,等待2MSL时间主要目的是:防止最后一个ACK包对方没有收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可以继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。    

    (3)client发送完最后一个ack之后,进入time_wait状态,但是他怎么知道server有没有收到这个ack呢?莫非sever也要等待一段时间,如果收到了这个ack就close,   
    如果没有收到就再发一个fin给client?这么说server最后也有一个time_wait哦?求解答!

        因为网络原因,主动关闭的一方发送的这个ACK包很可能延迟,从而触发被动连接一方重传FIN包。极端情况下,这一去一回,就是两倍的MSL时长。
        如果主动关闭的一方跳过TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动关闭的一方早先发出的延迟包到达后,
        就可能出现类似下面的问题:1.旧的TCP连接已经不存在了,系统此时只能返回RST包2.新的TCP连接被建立起来了,延迟包可能干扰新的连接,
        
    这就是为什么time_wait需要等待2MSL时长的原因。
      (4)为什么连接的时候是三次握手,关闭的时候却是四次握手?
          因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,
        很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,
        因此不能一起发送。

        udp协议(实时在线聊天,视频协议等等)

    特点:
    面向无连接:传输数据之前源端和目的端不需要建立连接   
    每个数据报的大小都限制在64k(8个字节)以内   面向报文的不可靠协议(即发出去的数据不一定会接收到)   传输速率快,效率高

    TCP和UDP对比

    1.TCP面向连接的可靠协议,而UDP不可靠的协议
    2.TCP连接过程可以传输大量数据,而UDP每个报文不超过64
    3.TCP传输速度慢,效率低,而UDP传输速度快,效率高

    (4)总结

    有了ip+端口+通信协议,计算机通信的基础就有了,网络编程就是让指的是让在不同的电脑上的软件能够进行数据传递,其中ip+端口做通信对象的标识,而通信协议则提供了如何进行通信的具体规定。

    1.互联网通信模型

    1.网络模型的示意图

    2.网络模型的各层的作用:

    面向通信的低层:

    物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0,它利用传输介质为数据链路层提供物理连接
    数据链路层:定义了电信号的分组方式,为网络层提供服务的,解决两个相邻结点之间的通信问题。
    网络层:是为传输层提供服务的,传送的协议数据单元称为数据包或分组。该层的主要作用是解决如何使数据包通过各结点传送的问题,即通过路径选择算法(路由)将数据包送到目的地。
        引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址。(ip协议传输层:作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,
        使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。(TCP
    /UDP协议
    面向信息处理的高层:
    会话层:主要功能是管理和协调不同主机上各种进程之间的通信(对话),即负责建立、管理和终止应用程序之间的会话。会话层得名的原因是它很类似于两个实体间的会话概念。
        例如,一个交互的用户会话以登录到计算机开始,以注销结束。 表示层:处理流经结点的数据编码的表示方式问题,以保证一个系统应用层发出的信息可被另一系统的应用层读出。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据
        表示格式转换成网络通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。 应用层:OSI参考模型的最高层,是用户与网络的接口。该层通过应用程序来完成网络用户的应用需求,如文件传输、收发电子邮件等。

    2.网络编程(SCOKET编程)

    (1)SOCKET定义

      socket(简称 套接字) ,是支持TCP/IP网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的例如我们每天浏览网页、QQ 聊天、收发 email 等等。

     

    (2)SCOKET的创建
      

    利用python的内置模块就可以完成该功能:
    import socket
    socket.socket(AddressFamily, Type)
    
    类 socket.socket 创建一个 socket,该类实例化时需要两个参数,返回socket 对象:
    
    参数一:AddressFamily(地址簇)
      socket.AF_INET IPv4(默认)   socket.AF_INET6 IPv6
      socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
    
    参数二:Type(类型)
      socket.SOCK_STREAM  流式socket , for TCP (默认)   
      socket.SOCK_DGRAM   数据报式socket , for UDP 实际应用: (1创建TCP连接: import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
    2创建UDP连接: import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()

    (3)SOCKET内置方法

    函数                            描述
    服务器端套接字 s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
    客户端套接字 s.connect()     主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 s.connect_ex()   connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
    公共用途的套接字函数 s.recv()     接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 s.send()      发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 s.sendall()   完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 s.recvfrom()     接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 s.sendto()     发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 s.close()       关闭套接字 s.getpeername()   返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 s.getsockname()   返回套接字自己的地址。通常是一个元组(ipaddr,port) s.setsockopt(level,optname,value) 设置给定套接字选项的值。 s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。 s.settimeout(timeout)   设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) s.gettimeout()       返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 s.fileno()           返回套接字的文件描述符。 s.setblocking(flag)      如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 s.makefile()          创建一个与该套接字相关连的文件

    (2)SOCKET的应用:

    聊天室实现
    
    import socket
    # 聊天器主要功能
    # 1、发送消息
    # 2、接收消息
    # 3、退出消息
    
    def send_msg(udp_socket):
        """发送消息的方法"""
        # 1、输入接收方的ip地址
        ipaddr = input("请输入接收方地址:
    ")
        if len(ipaddr) == 0:
            ipaddr = "192.168.31.163"
            print("默认设置为:%s" % ipaddr)
        # 2、输入接收方的端口
        port = input("请输入接收方端口号:
    ")
        if len(port) == 0:
            port = "6666"
            print("默认设置为:%s" % port)
        # 3、要求输入要发送的内容
        content = input("请输入要发送的内容:
    ")
        # 4、发送数据
        udp_socket.sendto(content.encode(), (ipaddr, int(port)))
    
    
    def recv_msg(udp_socket):
        # 1、接收数据
        recv_data = udp_socket.recvfrom(1024)
        # 2、把接收到的数据解码并且显示出来
        re_text = recv_data[0].decode()
        print("接收到消息为:%s" % re_text)
        ip_port = recv_data[1]
        # 3、打印显示发送方的IP和端口信息
        print(ip_port)
    
    
    if __name__ == '__main__':
    
        # 创建套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
        # 绑定端口为 6666
        udp_socket.bind(("", 6666))
    
        while True:
            # 给出提示,显示聊天器的主要功能
            print("*************************")
            print("****   1.发送消息  ****")
            print("****   2.接收消息  ****")
            print("****   3.退出系统  ****")
            print("*************************")
    
            # 1、提示用户选择功能
            num = int(input("请选择功能:
    "))
            # 2、判断用户选择
            # 如果选择1,调用发送消息的函数
            if num == 1:
                # print("您选择了发送消息")
                send_msg(udp_socket)
            # 如果选择2,调用接收消息的函数
            elif num == 2:
                # print("您
                recv_msg(udp_socket)
    
            # 如果选择3,退出程序执行
            else:
                print("程序正在退出...")
                break
                print("程序已退出!~")
    
        # 关闭套接字
        udp_socket.close()

     参考博客:TCP/IP各层详解

    一个会写博客的程序员
  • 相关阅读:
    骨灰级程序员20条编程经验
    js常用正则表达式
    css样式大全(整理版)
    ASP.NET中常用的26个优化性能方法
    C# 中的委托和事件
    Page的ResolveClientUrl与ResolveUrl读取路径
    层设定固定高度,内容超过高度,自动延伸
    Catalan数(卡塔兰数)
    称球问题
    zabbix分布式监控系统安装配置
  • 原文地址:https://www.cnblogs.com/znn041622/p/11000018.html
Copyright © 2011-2022 走看看