python高级编程 ---- 网络编程_TCP/IP协议 & socket编程 & 并发服务器

python高级编程 网络编程

一、网络通信概述

1、什么是网络

网络是一种辅助双方或者多方能够连接在一起的工具
如果没有网络,单机的世界是多么的孤独啊

2、使用网络的目的

联通多方然后进行通信用的,即把数据从一方传递给另一方
用网络能够把多方连接在一起,然后可以进行数据传递
网络编程就是:让不同的电脑上的软件能够进行数据传递,即进程之间的通信
在这里插入图片描述

二、TCP/IP协议

1、协议

如何实现网络通信:通过协议
协议:大家彼此都认可的一个条款
比如,有的人说英语,有的人说中文,有的人说德语,说同一种语言的人可以交流,不同的语言之间就不行了。为了让解决不同种族人之间的语言沟通障碍,就规定国际通用语言是英语,这就是一个规定,就是协议

那么不同种类之间的计算机(windows、linu、mac)是怎么进行数据传递的呢,网络通信的协议是什么呢?
就像说不同语言的人沟通一样,只要有一种大家都认可都遵守的协议即可,那就是计算机遵守的网络通信协议:TCP/IP协议

2、TCP/IP协议(族)

(1)TCP/IP协议

互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以大家把互联网协议简称为TCP/IP协议
在这里插入图片描述

(2)IP地址

地址就是用来标记地点的
IP地址是用来在网络中标记一台电脑的一串数字
比如:192.168.1.1
在本地局域网中,IP地址是唯一的

IP地址的分类:
每一个IP地址包括两部分:网络地址,主机地址
对于一个IP地址:172.25.254.1/24
IPV4是点分十进制,一共有32位,分为4段,每段8位,每段的范围是0~255
在172.25.254.1/24中,/24代表网络地址有24位,也就是说前三段 172.25.254是网络地址,剩下的一段 1是主机地址

IP地址可以分为5类:
平时不考虑网络地址还是主机地址
在这里插入图片描述

3、一些概念

(1)私网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

(2)回环地址IP

IP地址127.0.0.1代表本机IP地址,等价于localhost,
用http://127.0.0.1就可以测试本纪中配置的Web服务器
在这里插入图片描述

(3)子网掩码

子网掩码不能单独存在,它必须结合IP地址一起使用
子网掩码的作用:将某个IP地址划分成网络地址和主机地址两部分
子网掩码的设定必须遵循一定的规则,用来判断两个IP是否在同意个网络
子网掩码可以表示为两种形式:
比如:255.255.255.0,或者是24
255.255.255.0它转换成二进制是11111111 11111111 111111110就等价于24,也就是说IP地址的前24位是网络地址

如果两个IP地址的网络地址相同,那么这两台主机就可以进行通信
例如:
A主机:172.25.254.18/24
B主机:172.25.0.10/24
它俩的网络地址是前24位,明显不同,所以不能通信
如果是
A主机:172.25.254.18/16
B主机:172.25.0.10/16
网络地址是前16位,相同,则可以进行通信

(4)端口:

我们常见的的一些服务和它的端口号
sshd(22)、mysql(3306)、redis(6379)、http(80)、https(443)、ftp(21)

端口就好比一个房子的门,是进入这间房子的必经之路
端口号只有整数,范围是0 ~ 65535

端口是唯一标识计算机上的进程,便于不同计算机进程之间进行交流,
为什么不用PID(也是唯一标识)来表示呢,因为当我关闭一个进程再打开的时候,PID是会改变的

查看规定好的服务的端口号:
cat /etc/services | less

三、socket编程

1、网络进程间通信

本地进程间通信:
相同主机之间进行进程间通信的方式有:管道、消息队列、同步(互斥锁、条件变量等)

网络进程间通信:

本地通过进程的PID来唯一标识一个进程,在网络中如何唯一标识一个进程呢?
网络层的 “IP地址” 可以唯一标识网络中的主机,而传输层的 “协议+端口”可以唯一标识主机中的 应用程序(进程)。
因此利用IP地址,协议,端口 就能标识网络的进程。

因此通过IP确定沟通的主机,通过TCP/IP协议沟通,根据端口号唯一标识不同主机之间的进程。

2、什么是socket

socket,简称套接字,是进程间通信的一种方式,能实现不同主机之间的进程的通信。我们网络上各种各样的服务大多是基于socket来完成通信的。比如: pymysql、qq等应用的底层都是用Sccket来封装的

3、创建socket

在python中使用socket模块中的socket()函数就可以完成:
socket.socket(AddressFamily , type)

AddressFamily:地址协议,有两种:
AF_INET:IPV4(点分十进制)用于internet进程间通信
AF_INET6:IPV6(冒分十六进制)用于internet进程间通信
是网络层协议

Type:套接字类型,有两种:
SOCK_STREAM:流式套接字,主要用于TCP协议
SOCK_DGRAM:数据报套接字,主要用于UDP协议
是传输层协议

有一些默认值
在这里插入图片描述

import socket

# 1. 创建socket对象
#       family: AF_INET(IPv4)   AF_INET6(IPv6)      ========= 网络层协议
#       type: # SOCK_STREAM(TCP)   SOCK_DGRAM(UDP) ========== 传输层协议
#       Linux: socket可以认为是一个文件;
socketObj = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
print(socketObj.getsockname())

#2.  关闭socket对象
socketObj.close()

# 2. socket基本使用
import os
os.system('hostname') #linux命令获取主机名

hostname = socket.gethostname() #python中用socket模块获取主机名
print("主机名:", hostname)  #LAPTOP-KUD6IFIG
print(socket.gethostbyname('localhost')) #127.0.0.1

4、UDP介绍

(1)什么是UDP

UDP ⽤户数据报文协议,是⼀个⽆连接的简单的⾯向数据报的传输层协议。UDP不提供可靠性,它只是把应⽤程序传给IP层的数据报发送出去,但 是并不能保证它们能到达⽬的地。由于UDP在传输数据报前不⽤在客户和服务器之间建⽴⼀个连接,且没有超时重发等机制,故⽽传输速度很快。
UDP是⼀种⾯向⽆连接的协议,基于数据报文传输的,每个数据报都是⼀个独⽴的信息,包括完整的源地址或⽬的地址,它在⽹络上以任何可能的路径传往⽬的地,因此能否到达⽬的地,到达⽬的地的时间以及内容的正确性都是不能被保证的。

(2)UDP的特点

UDP是⾯向⽆连接的通讯协议,UDP数据包括⽬的端⼝号和源端⼝号信息, 由于通讯不需要连接,所以可以实现⼴播发送。
UDP传输数据时有⼤⼩限制,每个被传输的数据报必须限定在64KB之内。
UDP是⼀个不可靠的协 议,发送⽅所发送的数据报并不⼀定以相同的次序到达接收⽅。
优点:传输速度快
缺点:数据有可能丢失,不可靠

UDP结构:
在这里插入图片描述
源端口:源端口号。在需要对方回信时选用。不需要时可用全0。
目的端口:目的端口号。这在终点交付报文时必须要使用到。
长度: UDP用户数据报的长度,其最小值是8(仅有首部),最多2^32-1 = 64KB。
校验和:检测UDP用户数据报在传输中是否有错。有错就丢弃

(3)UDP应用场景

UDP是⾯向消息的协议,通信时不需要建⽴连接,数据的传输⾃然是不可靠的,UDP⼀般⽤于多点通信和实时的数据业务,⽐如:
语⾳⼴播 、
视频、QQ、(丢了没关系,在连接一次)
TFTP(简单⽂件传送)、
SNMP(简单⽹络管理协议) 、
DNS(域名解释)

5、UDP网络程序

在这里插入图片描述
在这里插入图片描述

服务端(UDP Server):提供服务
创建socket对象 ---- 绑定IP和端口 ---- 接收信息 ---- 发送消息(一个响应) ---- 关闭

客户端(UDP Client):访问提供的服务
创建socket对象 ---- 发送消息 ---- 接收消息 ---- 关闭

同一个目录下创建两个.py文件
服务端.py ; 客户端.py

"""服务端"""

import socket

# 1.实例化socket对象
udpServer = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
# 2.绑定IP和端口,是一个元组
#    121.0.0.1代表本地IP
udpServer.bind(('0.0.0.0', 9001))  #0.0.0.0代表开放所有的IP地址
print('等待客户端UDP的连接。。。')
# 3.接收客户端的消息
recvdata, address = udpServer.recvfrom(1024)  # 会有两个返回值:发送的消息;哪个地址发来的(IP地址,端口地址)。是一个元组。最多1024个字节
print('接收到客户端的数据为:',recvdata.decode('utf-8'))   # socket传输的数据是一个bytes类型 :b'hello' ,将它转为字符串‘hello’
# 4.给客户端回复消息,发送的消息必须是bytes类型
udpServer.sendto(b'hello client',address)
# 5.关闭socket对象
udpServer.close()
"""客户端"""

import socket

# 1.创建socket对象
udpClient = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
# 2.客户端发送消息,发送的消息必须是bytes类型
udpClient.sendto(b'hello server',('192.168.1.109',9001)) #发给哪台主机的哪个端口
# 3.接收服务端的消息
recvdata,address = udpClient.recvfrom(1024)
print('接收到服务端的数据为:',recvdata.decode('utf-8'))
# 4.关闭socket对象
udpClient.close()

先运行服务端
结果:等大客户端UDP的连接。。。
在运行客户端
结果:
在这里插入图片描述
在这里插入图片描述

案例:基于UDP模拟QQ聊天

python2中可以之间传字符串
python3中只能传输bytes类型的数据

bytes类型与字符串类型的相互转换:
bytes —> str:bytesobj.decode(‘utf-8’)
str —> bytes:str.encode(‘utf-8’)

"""服务端"""
import socket

udpserver = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
udpserver.bind(('0.0.0.0',9999))
print('QQ用户A上线')
while True:
    recv_data,address = udpserver.recvfrom(1024)
    print('B:>>',recv_data.decode('utf-8'))
    if recv_data == b'quit':
        print('聊天结束。。。')
        break
    #数据传输必须是bytes类型
    # bytes -> str:bytesobj.decode('utf-8')
    # str -> bytes:str.encode('utf-8')
    send_data = input('A:>>').encode('utf-8')
    if not send_data:
        continue
    udpserver.sendto(send_data,address)
udpserver.close()
import socket

udpclient = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
print('QQ用户B上线')
while True:
    send_data = input('B:>>').encode('utf-8')
    #如果没有输入任何消息,跳出本次循环重写输入
    if not send_data:
        continue
    udpclient.sendto(send_data,('192.168.1.104',9999))
    if send_data == b'quit':
        print('聊天结束。。。。')
        break
    recv_data,address = udpclient.recvfrom(1024)
    print('A:>>',recv_data.decode('utf-8'))
udpclient.close()

线运行服务端
在运行客户端
结果:
在这里插入图片描述
在这里插入图片描述

6、TCP介绍

(1)什么是TCP

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

(2)TCP和UDP的区别

在这里插入图片描述
全双工:A给B发消息,不管B有没有给A回复,A都可以继续传输

7、TCP网络程序

在这里插入图片描述
服务端:
创建socket对象 ---- 绑定IP和端口 ---- 监听(有没有客户端来连接) ---- 建立连接(监听到有客户端连接) ---- 接收消息 ---- 发送消息 ---- 关闭
客户端:
创建socket对象 ---- 发起连接 ---- 发送消息 ---- 接收消息 ---- 关闭

"""服务端"""
import socket

# 1.创建socket对象
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.绑定地址和端口
server.bind(('0.0.0.0',9998))
# 3.监听是否有客户端连接 ,python2可以不传值,python3要传值
server.listen(5)   # 最多监听5个客户端
# 4.接收客户端的连接 accept返回两个值:跟服务端交流的socket对象;跟服务端交流的地址(端口号和IP)
client_socket_obj,client_addres = server.accept()
# 5.接收客户端发送的消息
recv_data = client_socket_obj.recv(1024)
print('接收到客户端发送的消息为:',recv_data.decode('utf-8')) # 发来的是bytes类型,解码成字符串
# 6.给客户端发送消息
send_data = b'hello client'
client_socket_obj.send(send_data)
# 7.关闭socket对象
client_socket_obj.close()
server.close()
"""客户端"""
import socket

# 1.创建socket对象
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.连接服务端
client.connect(('192.168.1.109',9998))
# 3. 给服务端发送消息
client.send(b'hello server')
# 4.接收服务端发送的消息
recv_data = client.recv(1024)
print('接收到服务端发送的消息为:',recv_data.decode('utf-8'))
# 5.关闭socket对象
client.close()

案例:基于TCP模拟QQ聊天

import socket

# 1.创建socket对象
serverA = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.绑定地址和端口
serverA.bind(('0.0.0.0',9998))
print('QQ用户A上线.....')
serverA.listen(5)
# 4.接收客户端的连接 accept返回两个值:跟服务端交流的socket对象;跟服务端交流的地址
clientB_socket_obj,client_addres = serverA.accept()
while True:
    # 5.接收客户端发送的消息
    recv_data = clientB_socket_obj.recv(1024)
    print('B:',recv_data.decode('utf-8')) # 发来的是bytes类型,解码成字符串
    if recv_data == b'quit':
        print('聊天结束....')
        break
    # 6.给客户端发送消息
    send_data = input('A:').encode('utf-8')
    if not send_data:
        continue
    clientB_socket_obj.send(send_data)
# 7.关闭socket对象
clientB_socket_obj.close()
serverA.close()
import socket

# 1.创建socket对象
clientB = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
print('QQ用户B上线....')
# 2.连接服务端
clientB.connect(('192.168.1.109',9998))
while True:
    # 3. 给服务端发送消息
    send_data = input('B:').encode('utf-8')
    if not send_data:
        continue
    clientB.send(send_data)
    if send_data == b'quit':
        print('聊天结束....')
        break
    # 4.接收服务端发送的消息
    recv_data = clientB.recv(1024)
    print('A:',recv_data.decode('utf-8'))
# 5.关闭socket对象
clientB.close()

8、TCP的三次握手和四次挥手(敲黑板*)

(1)TCP三次握手

A:我爱你
B:哦,我也爱你
A:好的,我知道了
在这里插入图片描述
两个包: 同步序列标号 SYN ; 确认包 ACK
四种状态:
SYN_SENT(发送SYN报文) 、LISTEN(监听) 、 SYN_RECV(接收SYN报文) 、 ESTABLISHED(建立连接)

TCP连接是通过三次握手来连接的。
第一次握手
当客户端向服务器发起连接请求时,客户端会发送同步序列标号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,只不过服务器端的两个包是一次性发过来的,客户端的两个包是分两次发送的。

(2)TCP四次挥手

当A端和B端要断开连接时,需要四次挥手

A:我要跟你分手
B:行你让我考虑几天
B:我考虑好了,分就分
A:好的,再见
在这里插入图片描述
两个包: FIN:Finish 、 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是报文段发送和接收的最长时间

四、并发服务器

(1)长连接和短连接

并发服务器是socket应用编程中最常见的应用模型。根据连接方式分为长连接和短连接.
在这里插入图片描述
并发服务器模型根据处理方式可分为同步方式和异步方式
在这里插入图片描述

(2)还可分为单进程服务器和多进程服务器

单进程服务器:
同⼀时刻只能为⼀个客户进⾏服务,不能同时为多个客户服务
类似于找⼀个“明星”签字⼀样,客户需要耐⼼等待才可以获取到服务

多进程服务器:
优点:通过为每个客户端创建⼀个进程的⽅式,能够同时为多个客户端进⾏服务
缺点: 当客户端不是特别多的时候,使用多进程还⾏,如果有⼏百上千个,就不可取了,因为每次创建进程等过程需要好较⼤的资源。可以使用多线程。

基于TCP多进程服务器

# 实现多进程的方式:
#       1. 实例化对象
#       2. 继承子类
#       注意: 一定要确定多进程要处理的任务


# 任务: 处理客户端请求并为其服务
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()

import socket
from multiprocessing import Process

# 1. 创建服务端socket对象
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#假如端口被socket使用过,并且利用socket.close()来关闭连接,但此时端口还没有释放,要经过一个TIME_WAIT的过程之后才能使用,为了实现端口的马上复用,可以选择setsockopt()函数来达到目的。

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

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

while True:
    # 4.接收客户端的连接accept
    clientSocketObj, clientAddress = server.accept()
    # dealWithClient(clientSocketObj)
    p = Process(target=dealWithClient, args=(clientSocketObj, clientAddress))
    p.start()

# server.close()
##客户端
import socket

# 1.创建socket对象
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
print('QQ用户B上线....')
# 2.连接服务端
client.connect(('192.168.1.109',9996))
while True:
    # 3. 给服务端发送消息
    send_data = input('client:>>').encode('utf-8')
    if not send_data:
        continue
    client.send(send_data)
     # 4.接收服务端发送的消息
    recv_data = client.recv(1024)
    print('接受服务端发送的消息:',recv_data.decode('utf-8'))
    if send_data == b'quit':
        print('聊天结束....')
        break
   
# 5.关闭socket对象
client.close()

多线程服务器

在这里插入图片描述
协程-TCP服务器
在这里插入图片描述
在这里插入图片描述

五、两个案例

1、基于socket模拟浏览器获取页面内容

模拟底层的request模块,实现网页内容获取
先看一下百度页面的源代码,可以看到一些信息
在这里插入图片描述

# 实现http客户端的程序,获取百度的页面信息
import socket

# HTTP:应用层协议, TCP:传输层协议
# 1.创建socket对象
client = socket.socket()  # 默认IPV4,TCP传输
# 2. 连接服务端
# http://www.baidu.com 80
# https://www.baidu.com 443
client.connect(('www.baidu.com',80))
# 3.给服务器发送消息,内容:获取百度主页页面
# 这是固定格式 GET:获取,/:主页内容,HTTP:用HTTP通信,1.1:HTTP版本,\r\n:换行,Host:请求的主机名
client.send(b'GET / HTTP/1.1\r\nHost:www.baidu.com\r\nConnection:close\r\n\r\n')
# 4.接收服务端返回的内容
recv_data = client.recv(1024*100 )   # 百度的页面不止1024个字节,多写一点
#print(recv_data)
# 5.检测是否为百度页面,写到文件里查看
with open('baidu.html','wb') as f:  # wb: 接收的是bytes类型,‘wb’直接将bytes类型写到文件中
    f.write(recv_data)
# 6.关闭socket对象
client.close()

则可以获取到如下文件内容:
在这里插入图片描述

2、基于socket实现简易的Web服务器(多线程)

别人通过浏览器可以访问到我

import socket

def handler(clientSocketObj):
	# 5. 接收客户端传递的消息
	recv_data = clientSocketObj.recv(1024)
	print("*"*10)
	print(recv_data)
	# 6. 恢复消息
	clientSocketObj.send(b'HTTP/1.1 200 OK\r\n\r\n')
	clientSocketObj.send(b'<h1 style="color:green">index</h1>')

def webServer():
	# 1. 创建socket对象
	server = socket.socket()
	# 2.  绑定IP和端口
	server.bind(('0.0.0.0', 8082))
	# 3. 监听
	server.listen(5)
	print("自定义的HTTP服务8082开启.........")

	while True:
		# 4. 接收客户端连接
		clientSocketObj, clientAddress = server.accept()
		import threading
		t = threading.Thread(target=handler, args=(clientSocketObj, ))
		t.start()

if __name__ == '__main__':
	webServer()

浏览器输入:http://主机IP:8882 访问:
在这里插入图片描述
在这里插入图片描述


版权声明:本文为weixin_44568633原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。