网络编程

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各层详解

一个会写博客的程序员
原文地址:https://www.cnblogs.com/znn041622/p/11000018.html