Bootstrap

Python网络编程

网络编程

基本概念

IP地址

IP是Internet Protocol Address,即“互联网协议地址”。

用来标识网络中的一个通信实体的地址。通信实体可以是一个计算机、路由器等。如互联网的每个服务器都要有自己的IP地址,而每个局域网的计算机要通信地址也要配IP地址。

路由器是连接两个或多个网络的网络设备。

IP地址就像是我们的家庭住址一样,如果你要写信给一个人,你就要知道他(她)的地址,这样邮递员才能把信送到。计算机发送信息就好比是邮递员,它必须知道唯一的“家庭地址”才能不至于把信送错人家。只不过我们的地址是用文字来表示的,计算机的地址用二进制数字表示。 IP地址被用来给Internet上的电脑一个编号。大家日常见到的情况是每台联网的PC上都需要有IP地址,才能正常通信。我们可以把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”,而Internet中的路由器,就相当于电信局的“程控式交换机”。

类别最大网络数IP地址范围单个网段最大主机数私有IP地址范围
A126(2^7-2)1.0.0.1-127.255.255.2541677721410.0.0.0-1.0.255.255.255
B16384(2^14)128.0.0.1-191.255.255.25465534127.16.0.0-172.31.255.255
C2097152(2^21)192.0.0.1-223.255.255.254254192.168.0.0-192.168.255.255

IPV4,采用32位地址长度,只有大约43亿个地址,它只有4段数字,每一段最大不超过255。随着互联网的发展,IP地址不够用了,在2019年11月25日IPv4位地址分配完毕。

IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。

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

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

  1. windows下,我们可以通过命令ipconfig获取网卡信息。(Linux和Mac,是ifconfig

  2. 通过ping查看网络连接:

    1. ping www.baidu.com 查看是否能上公网

    2. ping 192.168.1.100 查看是否和该计算机在同一个局域网

    3. ping 127.0.0.1 查看本机网卡是否可用

公有地址

公有地址(Public address)由Inter NIC(Internet Network Information Center互联网信息中心)负责。这些IP地址分配给注册并向Inter NIC提出申请的组织机构。通过它直接访问互联网。

私有地址

私有地址(Private address)属于非注册地址,专门为组织机构内部使用。

以下列出留用的内部私有地址

A类 10.0.0.0--10.255.255.255

B类 172.16.0.0--172.31.255.255

C类 192.168.0.0--192.168.255.255

端口

端口号用来识别计算机中进行通信的应用程序。因此,它也被称为程序地址

一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地进行数据传输。

端口分配

端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。 端口的表示是一个16位的二进制整数,对应十进制的0-65535。

操作系统中一共提供了0~65535可用端口范围。

按端口号分类:

公认端口(Well Known Ports)

从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。

注册端口(Registered Ports)

从1024到65535。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。

网络通信协议

国际标准化组织(ISO,即International Organization for Standardization)定义了网络通信协议的基本框架,被称为OSI(Open System Interconnect,即开放系统互联)模型。要制定通讯规则,内容会很多,比如要考虑A电脑如何找到B电脑,A电脑在发送信息给B电脑时是否需要B电脑进行反馈,A电脑传送给B电脑的数据格式又是怎样的?内容太多太杂,所以OSI模型将这些通讯标准进行层次划分,每一层次解决一个类别的问题,这样就使得标准的制定没那么复杂。OSI模型制定的七层标准模型,分别是:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层。

网络协议的分层

虽然国际标准化组织制定了这样一个网络通信协议的模型,但是实际上互联网通讯使用最多的网络通信协议是TCP/IP网络通信协议。

TCP/IP 是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,网络接口层(物理+数据链路层)

把用户应用程序作为最高层,把物理通信线路作为最低层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。

TCP和UDP传输数据的区别

TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

TCP

TCP(Transmission Control Protocol,传输控制协议)。TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。

uploading.4e448015.gif

转存失败重新上传取消

UDP

UDP(User Data Protocol,用户数据报协议)

UDP是一个非连接的协议,传输数据之前源端和终端不建立连接, 当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、 计算机的能力和传输带宽的限制; 在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。

UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP

uploading.4e448015.gif

转存失败重新上传取消

TCP和UDP区别

这两种传输方式都在实际的网络编程中使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则可以通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。

由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。

UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

TCP的三次握手

TCP是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。 一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂, 只简单的描述下这三次对话的简单过程:

1.主机A向主机B发出连接请求:“我想给你发数据,可以吗?”,这是第一次对话;

2.主机B向主机A发送同意连接和要求同步 (同步就是两台主机一个在发送,一个在接收,协调工作)的数据包 :“可以,你什么时候发?”,这是第二次对话;

3.主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”, 这是第三次握手。

三次“对话”的目的是使数据包的发送和接收同步, 经过三次“对话”之后,主机A才向主机B正式发送数据。

1.第一步,客户端发送一个包含SYN即同步(Synchronize)标志的TCP报文,SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。

2.第二步,服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)

3.第三步,客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。然后才开始通信的第二步:数据处理。

TCP断开连接的四次挥手

第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;

第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;

第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;

第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。

由TCP的三次握手和四次断开可以看出,TCP使用面向连接的通信方式, 大大提高了数据通信的可靠性,使发送数据端和接收端在数据正式传输前就有了交互, 为数据正式传输打下了可靠的基础。

数据包与处理流程

通信传输中的数据单位,一般也称“数据包”。在数据包中包括:包、帧、数据包、段、消息。

网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。包首部就像协议的脸。

socket套接字编程底层原理

Socket编程封装了常见的TCP、UDP操作,可以实现非常方便的网络编程。

socket()函数介绍

在Python语言标准库中,通过使用socket模块提供的socket对象,可以在计算机网络中建立可以互相通信的服务器与客户端。在服务器端需要建立一个socket对象,并等待客户端的连接。客户端使用socket对象与服务器端进行连接,一旦连接成功,客户端和服务器端就可以进行通信了。

⚠️上图中,我们可以看出socket通讯中,发送和接收数据,都是通过操作系统控制网卡来进行。因此,我们在使用之后,必须关闭socket。

在Python 中,通常用一个Socket表示“打开了一个网络连接”,语法格式如下:

socket.socket([family[, type[, proto]]])

UDP编程介绍

UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据。recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。

 from socket import *
 ​
 # 服务端代码
 s = socket(AF_INET,SOCK_DGRAM) # 创建UDP类型套接字
 # 绑定端口
 # bind((ip,port))传入一个元组,ip可不写
 s.bind(("127.0.0.1",8888))
 print("wait")
 recv_data = s.recvfrom(1024)    # 1024本次接受的最大字节数
 print(f"收到远程信息{recv_data[0]},from{recv_data[1]}")
 # 关闭连接
 s.close()
 from socket import *
 ​
 # UDP客户端发送消息
 s = socket(AF_INET,SOCK_DGRAM)
 # 与服务端的ip和端口一致
 addr = ("127.0.0.1",8888)
 data = input("请输入:")
 # sendto(发送的数据,接受者)
 s.sendto(data.encode("utf-8"),addr)
 s.close()

持续通信

 from socket import *
 ​
 # 服务端代码
 s = socket(AF_INET,SOCK_DGRAM) # 创建UDP类型套接字
 # 绑定端口
 # bind((ip,port))传入一个元组,ip可不写
 s.bind(("127.0.0.1",8888))
 print("wait")
 while True:
     recv_data = s.recvfrom(1024)    # 1024本次接受的最大字节数
     recv_content = recv_data[0].decode("gbk")
     print(f"收到远程信息{recv_data[0]},from{recv_data[1]}")
     if recv_content == "88":
         print("Break")
         break
     # 关闭连接
 s.close()
 from socket import *
 ​
 # UDP客户端发送消息
 s = socket(AF_INET,SOCK_DGRAM)
 # 与服务端的ip和端口一致
 addr = ("127.0.0.1",8888)
 while True:
 ​
     data = input("请输入:")
     # sendto(发送的数据,接受者)
     s.sendto(data.encode("utf-8"),addr)
     if data == "88":
         print("Break")
         break
 s.close()

多线程结合自由通信

 """多线程服务端"""
 from socket import *
 from threading import Thread
 ​
 ​
 def recv_data():
     while True:
         recv_data = s.recvfrom(1024)
         recv_content = recv_data[0].decode("gbk")
         print(f"收到远程消息{recv_content},from{recv_data[1]}")
         if recv_content == "88":
             print("Break")
             break
 ​
 def send_data():
     # 模拟不同主机
     addr = ("127.0.0.1",8899)
     while True:
         data = input("请输入:")
         s.sendto(data.encode("gbk"),addr)
         if data == "88":
             print("Break")
             break
 if __name__ == '__main__':
     s = socket(AF_INET,SOCK_DGRAM)
     s.bind(("127.0.0.1",8888))
 ​
     # 创建两个线程
     t1 = Thread(target=recv_data)
     t2 = Thread(target=send_data)
     t1.start()
     t2.start()
     t1.join()
     t2.join()
 """多线程客户端"""
 ​
 from socket import *
 from threading import Thread
 ​
 ​
 def recv_data():
     while True:
         recv_data = s.recvfrom(1024)
         recv_content = recv_data[0].decode("gbk")
         print(f"收到远程消息{recv_content},from{recv_data[1]}")
         if recv_content == "88":
             print("Break")
             break
 ​
 def send_data():
     # 模拟不同主机
     addr = ("127.0.0.1",8888)
     while True:
         data = input("请输入:")
         s.sendto(data.encode("gbk"),addr)
         if data == "88":
             print("Break")
             break
 if __name__ == '__main__':
     s = socket(AF_INET,SOCK_DGRAM)
     s.bind(("127.0.0.1",8899))
 ​
     # 创建两个线程
     t1 = Thread(target=recv_data)
     t2 = Thread(target=send_data)
     t1.start()
     t2.start()
     t1.join()
     t2.join()

TCP编程

 # 服务端
 from socket import *
 ​
 server_socket = socket(AF_INET,SOCK_STREAM)
 server_socket.bind(("127.0.0.1",8899))
 server_socket.listen(5) # 最大监听数
 print("等待接收连接")
 client_socket,client_info = server_socket.accept()
 recv_data =  client_socket.recv(1024)    # 最大接收1024字节
 print(f"收到信息{recv_data.decode('gbk')},来自{client_info}")
 ​
 client_socket.close()
 server_socket.close()
 # 客户端
 from socket import *
 ​
 client_socket = socket(AF_INET,SOCK_STREAM)
 client_socket.connect(("127.0.0.1",8899))
 ​
 client_socket.send("Hello".encode("gbk"))
 client_socket.close()
;