038_Python网络编程

1. 网络通信概述

网络的目的:
用网络能够把多方链接在一起,以进行数据传递
网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信

2. TCP/IP 协议

2.1 认识 TCP/IP

不同种类的计算机间到底是怎么进行数据传递的呢?
就像说不同语言的人沟通一样,只要有一种大家都认可都遵守的协议即可, 那么这个计算机都遵守的网络通信协议叫做 TCP/IP协议

TCP/IP协议(族):互联网协议包含了上百种协议标准,但是最重要的两个协议是 TCP 和 IP 协议,所以,大家把互联网的协议简称TCP/IP协议
TCP/IP协议的体系架构如下图所示:
在这里插入图片描述

2.2 IP 地址

ip 地址:用来在网络中标记一台电脑的一串数字。

  1. ip 地址的分类
    每一个IP地址包括两部分:网络地址主机地址
    在这里插入图片描述

  2. 私有 ip
    网络IP中,国际规定有一部分IP地址是属于局域网使用的,也就是属于私有IP,不在公网中使用的,它们的范围是:
    10.0.0.0~10.255.255.255
    172.16.0.0~172.31.255.255
    192.168.0.0~192.168.255.255

  3. 回环地址 ip
    IP地址127.0.0.1 代表本机IP地址,等价于localhost, 用 http://127.0.0.1 就可以测试本机中配置的Web服务器。

  4. 子网掩码
    子网掩码不能单独存在,它必须结合IP地址一起使用。
    子网掩码的作用: 将某个IP地址划分成网络地址和主机地址两部分。子网掩码的设定必须遵循一定的规则, 用来判断两个IP是否在同一个网络。
    示例:
    A: 172.25.254.18/24
    B: 172.25.0.10/24
    ===> 此时,A, B不在同一网络
    A: 172.25.254.18/16
    B: 172.25.0.10/16
    ===> 此时,A, B在同一网络

  5. 端口
    端口号只有整数,范围是从0到65535;

3. socket 编程

3.1 网络进程间的通信 (socket)

问题:本地通过进程 PID 来唯一标识一个进程,在网络中如何唯一标识一个进程?又是如何通信的?
网络层的 “IP地址” 可以唯一标识网络中的主机,而传输层的 “协议+端口” 可以唯一标识主机中的应用程序(进程)。因此 利用IP地址协议端口就可以标识网络的进程
socket(简称套接字) 是进程间通信的一种方式, 能实现不同主机间的进程间通信,网络上各种各样的服务大多都是基于 Socket 来完成通信的。

在 Python 中 使用 socket 模块的函数 socket 实现套接字的创建:
socket.socket(AddressFamily, Type)
1. Address Family
AF_INET:IPV4用于 Internet 进程间通信
AF_INET6:IPV6用于 Internet 进程间通信
2. Type:套接字类型
SOCK_STREAM:流式套接字,主要用于 TCP 协议 (默认)
SOCK_DGRAM:数据报套接字,主要用于 UDP 协议

3.2 UDP

UDP (用户数据报协议),是一个无连接的简单的面向数据报运输层协议。UDP不提供可靠性,它只是把应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。

UDP的特点:

  • UDP是面向无连接的通讯协议
  • UDP是不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
  • UDP数据报包括目的端口号和源端口号信息
  • UDP传输数据时有大小限制,每个被传输的数据报必须限定在64KB之内。
  • 可实现广播 在这里插入图片描述

UDP 应用场景:
UDP是面向消息的协议,通信时不需要建立连接,数据的传输自然是不可靠的,UDP一般用于多点通信和实时的数据业务,比如:

  • 语音广播
  • 视频
  • QQ
  • TFTP (简单文件传送)
  • SNMP (简单网络管理协议)
  • DNS (域名解释)

UDP 网络程序编写的主要流程:
在这里插入图片描述
在这里插入图片描述

UDP 编程——模拟QQ聊天,代码示例:

  1. 用户B (客户端) 代码:
import socket

# 1. 创建 UDP 套接字
udpclient = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

print("QQ用户B上线.........")
while True:
	# 2. 对需要发送的数据进行编码预处理,str --> byte
    send_data = input('B:>> ').encode('utf-8')
    if not send_data:
        continue
    # 3. 与服务端建立连接,并发送数据
    udpclient.sendto(send_data, ('127.0.0.1', 9999))
    if send_data == b'quit':
        print("聊天结束.....")
        break
    # 4. 阻塞状态,直到接收到服务端回传的数据和地址信息(address=(ip, port))
    # 	 才会解阻塞
    recv_data, address = udpclient.recvfrom(1024)
    print("A:>> ", recv_data.decode('utf-8'))
# 5. 关闭 socket 对象
udpclient.close()

  1. 用户A (服务端) 代码:
import socket

# 1. 创建 UDP 套接字
udpserver = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 2. 限定连接的ip,绑定端口
udpserver.bind(('0.0.0.0', 9999))
print('QQ用户A上线.........')

while True:
    # 3. 阻塞状态,直到接收到客户端回传的数据才解阻塞
    # 返回的是元组, 一个元素是客户端发送的信息, 第二个元素是客户端和服务端交互的地址(IP, port)
    recv_data, address = udpserver.recvfrom(1024)
    print("B:>> ", recv_data.decode('utf-8'))
    if recv_data == b'quit':
        print("聊天结束.......")
        break
    # 4. 数据预处理
    # 发送的消息必须是bytes类型
    #   bytes -->  str    bytesObj.decode('utf-8')
    #   str   --> bytes   strObj.encode('utf-8')
    send_data = input('A:>> ').encode('utf-8')
    if not send_data:
        continue
    # 5. 与客户端建立连接,并发送数据
    udpserver.sendto(send_data, address)
# 6. 关闭 socket 对象
udpserver.close()

执行结果:
在这里插入图片描述

3.3 TCP
3.3.1 认识 TCP

TCP:传输控制协议 (英语:Transmission Control Protocol,缩写为TCP) 是一种面向连接的可靠的基于字节流传输层通信协议。

TCP 与 UDP对比:
在这里插入图片描述

3.3.2 TCP 网络编程代码实现

TCP 网络程序编写的主要流程:
在这里插入图片描述
代码示例:

  1. 客户端代码
	
import socket

# 1. 创建 TCP 套接字
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 2. 连接服务端
client.connect(('127.0.0.1', 9998))

# 3.给服务端发送消息
client.send(b'hello server')

# 4. 接收服务端发送的消息
recv_data = client.recv(1024).decode('utf-8')
print("接收服务端发送的消息:", recv_data)

# 5. 关闭socket对象
client.close()
  1. 服务端代码
import socket
# 1. 创建服务端的 TCP 套接字
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 2. 绑定地址和端口(IP:port)
server.bind(('0.0.0.0', 9998))

# 3. 监听是否有客户端连接?listen
server.listen(5)
print('server start .........')

# 4.接收客户端的连接accept
clientSocketObj, clientAddress = server.accept()

# 5. 接收客户端发送的消息
recv_data = clientSocketObj.recv(1024).decode('utf-8')
print("接收到客户端发送的消息:", recv_data)

# 6. 给客户端发送消息
send_data = b'hello client'
clientSocketObj.send(send_data)

# 7. 关闭socket对象
clientSocketObj.close()
server.close()
3.3.3 TCP 详解

在这里插入图片描述
三次握手:TCP连接是通过三次握手来连接的。

  • 两个包: 同步序列标号 SYN;确认包 ACK
  • 四种状态: SYN_SENT, LISTEN, SYN_RECV, ESTABLISHED
  • 第一次握手
    当客户端向服务器发起连接请求时,客户端会发送同步序列标号SYN到服务器,在这里我们设SYN为x,等待服务器确认,这时客户端的状态为SYN_SENT。

  • 第二次握手
    当服务器收到客户端发送的SYN后,服务器要做的是确认客户端发送过来的SYN,在这里服务器发送确认包ACK,这里的ACK为x+1,意思是说“我收到了你发送的SYN了”,同时,服务器也会向客户端发送一个SYN包,这里我们设SYN为y。这时服务器的状态为SYN_RECV。(一句话,服务器端发送SYN和ACK两个包)

  • 第三次握手
    客户端收到服务器发送的SYN和ACK包后,需向服务器发送确认包ACK,“我也收到你发送的SYN了,我这就给你发个确认过去,然后我们即能合体了”,这里的ACK为y+1,发送完毕后,客户端和服务器的状态为ESTABLISH,即TCP连接成功。

在三次握手中,客户端和服务器端都发送两个包SYN和ACK,只不过服务器端的两个包是一次性发过来的,客户端的两个包是分两次发送的。

- 四次分手
四次挥手:当A端和B端要断开连接时,需要四次握手,这里称为四次挥手。

断开连接请求可以由客户端发出,也可以由服务器端发出,在这里我们称A端向B端请求断开连接。

  • 第一次挥手
    A端向B端请求断开连接时会向B端发送一个带有FIN标记的报文段,这里的FIN是Finish的意思。

  • 第二次挥手
    B端收到A发送的FIN后,B段现在可能现在还有数据没有传完,所以B端并不会马上向A端发送FIN,而是先发送一个确认序号ACK,意思是说“你发的断开连接请求我收到了,但是我现在还有数据没有发完,请稍等一下呗”。

  • 第三次挥手
    当B端的事情忙完了,那么此时B端就可以断开连接了,此时B端向A端发送FIN序号,意思是这次可以断开连接了。

  • 第四次挥手
    A端收到B端发送的FIN后,会向B端发送确认ACK,然后经过两个MSL时长后断开连接。

MSL是 Maximum Segment Lifetime,最大报文段生存时间,2个MSL是报文段发送和接收的最长时间

4. 并发服务器

4.1 认识并发服务器

并发服务器是 socket 应用编程中最常见的应用模型。

  • 并发服务器模型根据连接方式分为长连接短连接
通信方式具体通信过程
长连接建立 socket 连接后不管是否使用都保持连接
短连接书暗访有数据交互时,建立 socket 连接,数据发送完成后断开连接
  • 并发服务器模型根据处理方式可分为同步方式和异步方式。
    同步:指客户端发送请求给服务器等待服务器返回处理结果
    异步:指客户端发送请求给服务器,不等待服务器返回处理结果,而是直接去完成其他的流程,对于处理结果客户端可以事后查询或让服务器进行主动通知。
    在这里插入图片描述
4.2 多进程服务器 (处理并发)

单进程服务器:同一时刻只能为一个客户进行服务,不能同时为多个客户服务
多进程服务器优点在于通过为每个客户端创建一个进程的方式,能够同时为多个客户端进行服务;缺点则是当客户端不是特别多的时候,这种方式还行,如果有成百上千个,就不可取了,因为每次创建进程等过程需要好较多的资源
在这里插入图片描述
代码示例

  1. 客户端代码
import socket

# 1. 创建服务端 TCP 套接字
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 2. 连接服务端
client.connect(('127.0.0.1', 9997))

while True:
    # 3.给服务端发送消息
    send_data = input('client: >> ').encode('utf-8')
    if not send_data:
        continue
    client.send(send_data)
    if send_data == 'quit':
        break

# 5. 关闭socket对象
client.close()
  1. 服务端代码
# 实现多进程的方式:
#       1. 实例化对象
#       2. 继承子类
#       注意: 一定要确定多进程要处理的任务
import socket
from multiprocessing import Process

from threading import Thread

import gevent
from gevent import monkey
monkey.path_all()

def dealWithClient(clientSocketObj, clientAddress):
	'''任务: 处理客户端请求并为其服务'''
    while True:
        # 5. 接收客户端发送的消息
        recv_data = clientSocketObj.recv(1024).decode('utf-8')
        print(clientAddress[0] + str(clientAddress[1]) + ':> ' + recv_data)
        if recv_data == 'quit':
            break
    clientSocketObj.close()


# 1. 创建服务端 TCP 套接字
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
#    重复使用绑定的信息,即解决端口已占用的问题
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 2. 绑定地址和端口(IP:port)
server.bind(('0.0.0.0', 9997))

# 3. 监听是否有客户端连接
server.listen(5)
print('server start .........')

while True:
    # 4.接收客户端的连接accept
    clientSocketObj, clientAddress = server.accept()
    # dealWithClient(clientSocketObj)
    
    # 4.1 多进程服务器 (处理并发)
    p = Process(target=dealWithClient, args=(clientSocketObj, clientAddress))
    p.start()
    
    # 4.2 多线程服务器 (处理并发)
    # t = Thread(target=dealWithClient, args=(clientSocketObj, clientAddress))
    # t.start()

	# 4.3 协程——gevent版并发服务器(TCP)
	# gevent.spawn(target=dealWithClient, args=(clientSocketObj, clientAddress))


# server.close()
4.2 多线程服务器 (处理并发)

多线程服务器:每一个连接催生一个线程
在这里插入图片描述
代码示例如 4.2 服务端代码所示

4.3 协程——gevent版并发服务器(TCP)

代码示例如 4.2 服务端代码所示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值