Python——网络编程

Python基础之socket编程

客户端、服务器架构

C/S:C = Client,S = Server。C/S 架构即“客户端-服务器”架构。

B/S:B = Browser,S =Server。B/S架构其实是C/S架构一种特殊的实现形式

OSI七层协议 & TCP/IP 五层模型

TCP/IP 五层模型讲解

物理层
  • 功能:以二进制数据形式在物理媒体上传输数据。
数据链路层
以太网协议(ethernet):
  • 一组电信号构成一个数据包,叫做 ‘以太帧’
  • 以太帧(IEEE802.3结构)的组成:

    • Preamble: 前导码,7个字节,用于数据传输过程中的双方发送、接收的速率的同步
    • SFD: 帧开始符,1个字节,表明下一个字节开始是真实数据
    • dst MAC: 目标MAC地址,6个字节,指明这个以太帧的接受者
    • src MAC: 源MAC地址,6个字节,指明这个以太帧的发送者
    • Length: 长度,2个字节,指明这个以太帧数据字段的长度,
    • Type: 类型,2个字节,指明这个以太帧中数据的协议类型,比如 0x0800 表明该以太帧用的是IPv4的ip协议
    • Data and Pad: 数据与填充,46~1500个字节,包含了上层协议传递下来的数据,如果加入数据字段后,这个以太帧长度不够64个字节,会在数据字段加入‘填充’使它达到64字节
    • FCS: 帧校验序列,4个字节,对接收网卡,主要是检测Date and Pad字段,提供判断是否传输错误的一种方法,如果发现错误,丢弃此帧。目前最为流行的用于FCS的算法是循环冗余校验(cyclic redundancy check - CRC
  • MAC地址(Media Access Control Address): 每块网卡出厂时,由网卡生产厂家少如网卡的EPROM,它储存的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址,形象的解释,MAC地址就如同身份证上的身份证号码,具有全球唯一性。

  • 广播:有了MAC地址,局域网内的两台主机就可以通信,一台主机通过ARP协议获取另外一台主机的MAC地址。在同一局域网内的主机都会收到一个包,其他主机会收到这个包,并进行拆解,发现目标MAC地址不是自己,就丢弃,如果是自己就响应。
网络层

试想,我们现在有了ethernet、MAC地址、广播的发送方式,世界上的计算机都采用以太网的广播方式,的确彼此之间就可以通信le ,但是会出现一个问题,一台主机发送的包就会被全世界收到,这就会造成很多问题,比如效率低下。

那么实际上世界的网络是由一个个小的彼此隔离的局域网组成的,以太网的广播包只能在一个局域网内发送,一个局域网就是一个广播域,以太网的广播包只能在一个广播域内发送,跨广播域通信只能通过路由转发,那么网络层的功能就是找一种办法来区分不同的广播域/子网,这种办法叫做IP协议。

IP协议
  • 规定网络地址的协议叫做IP协议,它定义的地址称之为IP地址,广泛采用的版本为IPv4,它规定网络地址由32位2进制表示。
  • 范围0.0.0.0 - 255.255.255.255
  • 一个IP地址通常携程四段十进制数,例如:127.10.12.1
  • IP地址分为两部分
    • 网络部分:标识子网
    • 主机部分:标识主机
    • 单纯的IP地址,只是标识了IP地址的种类,从网络部分或主机部分都无法辨识一个IP所处的子网,即:192.168.10.1与192.168.10.2 不能确定二者处于同一个子网内。
  • 子网掩码:就是标识子网特征的一个参数,我们知道了‘子网掩码’后,我们就能判断,任意两个IP地址是否处在同一个子网络内,方法就是将两个IP地址与子网掩码进行AND运算(两个对应数位为1,运算结果就为1,否则为0),然后比较结果是否相同,如果是的话,就表明这两个IP地址,在同一个子网络中,否则就不是。
  • IP协议,最主要的功能由两个:一个是为每一台计算及分配IP地址,另一个是确定哪些地址在同一个子网络。
  • IP数据报
  • ARP协议
传输层

以我们的电脑为例,你会在电脑上运行QQ,微信,浏览器等应用软件,网络层的IP+子网掩码帮我们区分了子网,以太网的MAC地址帮我们找到了局域网内的主机,那么如何该分辨你的这个数据是发送给你主机上的哪个应用程序的呢?答案是端口port(端口范围0-65535,0-1023为系统占用端口尽量别用)

tcp协议

暂时看一下这个超链接大佬写的吧,好像举例更生动点。

应用层

应用层是系统的最高层,是直接为应用进程提供服务的,直接和应用程序接口并提供常见的网络应用服务。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。

socket

Socket我们通常称之为套接字,是在应用层和TCP/IP协议族通信的一个抽象层。它把运输层和网络层复杂的复杂的操作抽象为几个简单的接口提供给应用层调用,以此来实现网络中的通信。

套接字发展史及分类

  • 基于文件类型的套接字家族:AF_UNIX(UNIX中一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信)
  • 基于网络类型的套接字家族:AF_INET(python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

套接字工作流程

socket工作流程

TCP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# TCP服务器.py
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server.bind(('127.0.0.1',8080))# 1024-65535
server.listen(5)

while True:
conn,client_addr = server.accept()
print(conn)
while True:
try:
data = conn.recv(1024) # 接受数据的最大限制
if not data:break # 针对linux系统。
conn.send(data.upper()) # 注意收发都是以bytes为单位
except ConnectionResetError:
break
conn.close()
server.close()
1
2
3
4
5
6
7
8
9
10
11
12
# TCP客户端.py
import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
msg = input('>>>: ').strip()
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
client.close()
UDP
1
2
3
4
5
6
7
8
9
10
# UDP服务器.py
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
client_data,client_addr = server.recvfrom(1024)
msg = input('回复%s:%s>>>: '%(client_addr[0],client_addr[1]))
server.sendto(msg.decode('utf-8'),client_addr)
1
2
3
4
5
6
7
8
9
10
# UDP客户端.py
import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
res,server_addr = client.recvfrom(1024)
print(res.decode('utf-8'))

TCP粘包问题

很通俗的理解一下TCP粘包问题,第一种情况就是发送的内容过少,TCP协议为了提高传输效率,通常会收集短时间内的少量数据作为一个包发送出去;第二种情况就是发送的内容过多,导致接收端不能完整的接受,残留数据在管道内,再次接收时,会先接受残留数据然后再接受新的数据。所以我们也可以看出,TCP协议不会丢失数据,数据没收完会接着上次继续收,但是也就造成了粘包问题,而UDP根本不会粘包,但是当发送的数据超过512个字节时,极其容易丢失数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# _*_ coding: utf-8 _*_
# Time : 2018/11/18 2:27 PM
# Author : oOC
# Email : david.yfwei@gmail.com
# File : cmd解决粘包服务器.py

import socket,subprocess,struct,json

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server.bind(('127.0.0.1',8080))
server.listen(5)

while True:

print('正在等待连接'.center(30,'='))
# 同意连接请求
client,client_addr = server.accept()

# 重复收发数据
while True:
try:
# 接收命令,返回值一定是个bytes类型
cmd = client.recv(1024).decode('utf-8')
# 在linux中,客户端异常关闭,服务器会收空
if not cmd:continue

info = subprocess.Popen(cmd,shell = True,stdout = subprocess.PIPE,stderr = subprocess.PIPE)
res = info.stdout.read()+info.stderr.read()
print('字节数:%s',len(res))

# 1.组装一个报头
head_dic = {
'name':'CMD套接字通信',
'total_size':len(res),
}
# 2.报头转JSON格式
head_str = json.dumps(head_dic)
# 3.JSON转字节
head_bytes = head_str.encode('utf-8')
# 4.发送报头长度
bytes_len = struct.pack('i',len(head_bytes))
client.send(bytes_len)
# 5.发送报头
client.send(head_bytes)
# 6.发送真实数据
client.send(res)


except ConnectionResetError:
print('客户端异常关闭!')
client.close()
break

client.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# _*_ coding: utf-8 _*_
# Time : 2018/11/18 2:27 PM
# Author : oOC
# Email : david.yfwei@gmail.com
# File : cmd解决粘包客户端.py

import socket,struct,json

client = socket.socket()

while True:
# 发送数据
msg = input('>>>: ')
if not msg:continue
client.send(msg.encode('utf-8'))

# 1.获取报头长度
bytes_len = client.recv(4) # 服务器格式为i,固定4个字节
# 2.转回整型
head_len = struct.unpack('i',bytes_len)
# 3.接收报头数据
head_bytes = client.recv(head_len)
# 4.转为JSON字符串
head_dic = json.loads(head_bytes.decode('utf-8'))

recv_len = 0
finally_data = b''

while recv_len < head_dic['total_size']:
data = client.recv(1024)
recv_len += len(data)
finally_data += data

print(finally_data.decode('utf-8'))

client.close()