python的网络编程
一、系统和网络
1、系统
操作系统: (Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。
2、osi七层协议
osi七层:
物理层
数据链路层
网络层
传输层
会话层
表示层
应用层
tcp/ip五层:
物理层
数据链路层
网络层
传输层
应用层
tcp/ip四层:
网络接口层
网络层
传输层
应用层
3、数据链路层
以太网协议:
# 一组电信号构成一个数据包,叫做‘帧’
# 每一数据帧分成:报头head和数据data两部分
head包含:(固定18个字节)
发送者/源地址,6个字节
接收者/目标地址,6个字节
数据类型,6个字节
data包含:(最短46字节,最长1500字节)
数据包的具体内容:
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
4、网络层
ip数据包也分为head和data部分
head:长度为20到60字节
data:最长为65,515字节
而以太网数据包的”数据”部分,最长只有1500字节。因此,如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。
5、传输层
二、socket
1、介绍
我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
2、 套接字工作流程
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
3、套接字函数
#1、服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
#2、客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
#3、公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
#4、面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
#5、面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
4、实现基于TCP的套接字(先启动服务端)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#服务端: import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
phone.bind(( '127.0.0.1' , 8081 )) #绑定ip和端口,让客户端连接
phone.listen( 5 ) #半连接池大小
print ( 'starting...' )
conn,client_addr = phone.accept() #等待客户端连接
print (conn,client_addr)
data = conn.recv( 1024 ) #基于建立好的conn链接对象收发消息
conn.send(data.upper()) conn.close() #断开链接
phone.close() #终止服务
#客户端: import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
phone.connect(( '127.0.0.1' , 8081 )) #连接服务器的ip和端口
phone.send( 'hello' .encode( 'utf-8' ))
data = phone.recv( 1024 )
print (data)
phone.close() |
5、最终版基于TCP的套接字
上面的值实现发送一条消息和连接一个客户端,所以要对程序进行修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#服务端: import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
phone.bind(( '127.0.0.1' , 8081 )) #绑定ip和端口,让客户端连接
phone.listen( 5 ) #半连接池大小
while True :
conn,client_addr = phone.accept() #等待客户端连接
print (conn,client_addr)
while True :
data = conn.recv( 1024 ) #基于建立好的conn链接对象收发消息
conn.send(data.upper())
conn.close() #断开链接
phone.close() #终止服务
#客户端: import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
phone.connect(( '127.0.0.1' , 8081 )) #连接服务器的ip和端口
while True :
msg = input ( '>>: ' ).strip()
if len (msg) = = 0 : continue
phone.send(msg.encode( 'utf-8' ))
data = phone.recv( 1024 )
print (data)
phone.close() |
6、粘包
应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。
若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
7、解决粘包的处理方法
程序流程:客户端发送命令,服务端在本地执行后,返回得到的结果给客户端
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
|
# 服务端: from socket import *
import subprocess
import struct
server = socket(AF_INET,SOCK_STREAM)
server.bind(( '127.0.0.1' , 8088 ))
server.listen( 5 )
while True :
conn,client_addr = server.accept()
print (client_addr)
while True :
try :
cmd = conn.recv( 8096 )
if not cmd: break
obj = subprocess.Popen(cmd.decode( 'utf-8' ),shell = True ,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
total_size = len (stdout) + len (stderr) #制作固定长度的报头
headers = struct.pack( 'i' ,total_size)
conn.send(headers)
conn.send(stdout) #发送命令的执行结果
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close() # 客户端: from socket import *
import struct
client = socket(AF_INET,SOCK_STREAM)
client.connect(( '127.0.0.1' , 8088 ))
while True :
cmd = input ( '>>: ' ).strip()
if not cmd: continue
client.send(cmd.encode( 'utf-8' )) #发送命令
headers = client.recv( 4 ) #先接收命令长度,struct模块生成一个4个字节的结果
total_size = struct.unpack( 'i' , headers)[ 0 ]
recv_size = 0 #再收命令的结果
data = b''
while recv_size < total_size:
recv_data = client.recv( 1024 )
data + = recv_data
recv_size + = len (recv_data)
print (data.decode( 'gbk' ))
client.close() |
8、解决粘包的处理方法加强版
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
56
57
58
59
60
|
#服务端: from socket import *
import subprocess
import struct
import json
server = socket(AF_INET,SOCK_STREAM)
server.bind(( '127.0.0.1' , 8093 ))
server.listen( 5 )
while True :
conn,client_addr = server.accept()
print (client_addr)
while True :
try :
cmd = conn.recv( 8096 )
if not cmd: break
obj = subprocess.Popen(cmd.decode( 'utf-8' ),shell = True ,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
headers = { #制作报头
'filepath' : 'a.txt' ,
'md5' : '123sxd123x123' ,
'total_size' : len (stdout) + len (stderr)
}
headers_json = json.dumps(headers) #把headers转为json格式
headers_bytes = headers_json.encode( 'utf-8' ) #前面的json结果得到字节形式
conn.send(struct.pack( 'i' , len (headers_bytes))) #先发报头的长度
conn.send(headers_bytes) #发送报头
conn.send(stdout) #发送真实数据,正确的stdout,错误的stderr
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close() #客户端: from socket import *
import struct
import json
client = socket(AF_INET,SOCK_STREAM)
client.connect(( '127.0.0.1' , 8093 ))
while True :
cmd = input ( '>>: ' ).strip()
if not cmd: continue
client.send(cmd.encode( 'utf-8' ))
headers_size = struct.unpack( 'i' ,client.recv( 4 ))[ 0 ]
headers_bytes = client.recv(headers_size)
headers_json = headers_bytes.decode( 'utf-8' )
headers_dic = json.loads(headers_json)
print ( '========>' ,headers_dic)
total_size = headers_dic[ 'total_size' ]
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = client.recv( 1024 )
data + = recv_data
recv_size + = len (recv_data)
print (data.decode( 'gbk' ))
client.close() |
9、文件下载
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
#服务端 import socket
import os
import json
import struct
SHARE_DIR = r 'F:\SHARE' #目标文件路径
class FtpServer:
def __init__( self ,host,port):
self .host = host
self .port = port
self .server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self .server.bind(( self .host, self .port))
self .server.listen( 5 )
def serve_forever( self ):
print ( 'server starting...' )
while True :
self .conn, self .client_addr = self .server.accept()
print ( self .client_addr)
while True :
try :
data = self .conn.recv( 1024 ) #params_json.encode('utf-8')
if not data: break
params = json.loads(data.decode( 'utf-8' )) #params=['get','a.txt']
cmd = params[ 0 ]
if hasattr ( self ,cmd):
func = getattr ( self ,cmd)
func(params)
else :
print ( '\033[45mcmd not exists\033[0m' )
except ConnectionResetError:
break
self .conn.close()
self .server.close()
def get( self ,params): #params=['get','a.txt']
filename = params[ 1 ] #filename='a.txt'
filepath = os.path.join(SHARE_DIR,filename)
if os.path.exists(filepath):
headers = { #制作报头
'filename' : filename,
'md5' : '123sxd123x123' ,
'filesize' : os.path.getsize(filepath)
}
headers_json = json.dumps(headers)
headers_bytes = headers_json.encode( 'utf-8' )
self .conn.send(struct.pack( 'i' , len (headers_bytes))) #先发报头的长度
self .conn.send(headers_bytes) #发送报头
with open (filepath, 'rb' ) as f: #发送真实的数据
for line in f:
self .conn.send(line)
def put( self ):
pass
if __name__ = = '__main__' :
server = FtpServer( '127.0.0.1' , 8081 )
server.serve_forever()
##客户端 import socket
import struct
import json
import os
DOWNLOAD_DIR = r 'F:\DOWNLOAD' #下载路径
class FtpClient:
def __init__( self ,host,port):
self .host = host
self .port = port
self .client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self .client.connect(( self .host, self .port))
def interactive( self ):
while True :
data = input ( '>>: ' ).strip() #get a.txt
if not data: continue
params = data.split() #parmas=['get','a.txt']
cmd = params[ 0 ] #cmd='get'
if hasattr ( self ,cmd):
func = getattr ( self ,cmd)
func(params) #func(['get','a.txt'])
def get( self ,params):
params_json = json.dumps(params)
self .client.send(params_json.encode( 'utf-8' ))
headers_size = struct.unpack( 'i' , self .client.recv( 4 ))[ 0 ] #接收报头长度
headers_bytes = self .client.recv(headers_size) #接收报头
headers_json = headers_bytes.decode( 'utf-8' )
headers_dic = json.loads(headers_json)
# print('========>', headers_dic)
filename = headers_dic[ 'filename' ]
filesize = headers_dic[ 'filesize' ]
filepath = os.path.join(DOWNLOAD_DIR, filename)
with open (filepath, 'wb' ) as f: #接收真实数据
recv_size = 0
while recv_size < filesize:
line = self .client.recv( 1024 )
recv_size + = len (line)
f.write(line)
print ( '===>下载成功' )
if __name__ = = '__main__' :
client = FtpClient( '127.0.0.1' , 8081 )
client.interactive()
|
本文转自 宋鹏超 51CTO博客,原文链接:http://blog.51cto.com/qidian510/2066654,如需转载请自行联系原作者