Python - 网络编程
网络通信三要素:
A: IP地址 (1) 用来标识网络上一台独立的主机
(2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别该网络中的主机)
(3) 特殊的IP地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。
B: 端口号: (1) 用于标识进程的逻辑地址。不同的进程都有不同的端口标识。
(2) 端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)
C: 传输协议:通讯的规则。例如:TCP、UDP协议(好比两个人得用同一种语言进行交流)
①、UDP:User Datagram Protocol用户数据报协议
特点:
- 面向无连接:传输数据之前源端和目的端不需要建立连接。
- 每个数据报的大小都限制在64K(8个字节)以内。
- 面向报文的不可靠协议。(即:发送出去的数据不一定会接收得到)
- 传输速率快,效率高。
- 现实生活实例:邮局寄件、实时在线聊天、视频会议…等。
②、TCP:Transmission Control Protocol传输控制协议
特点:
- 面向连接:传输数据之前需要建立连接。
- 在连接过程中进行大量数据传输。
- 通过“三次握手”的方式完成连接,是安全可靠协议。
- 传输速度慢,效率低
网络通讯步骤:
确定对端IP地址→ 确定应用程序端口 → 确定通讯协议
SOCKET 编程
可以利用ip地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,socket翻译为套接字,socket是在应用层和传输层(TCP/IP协议族通信)之间的一个抽象层,是一组接口,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
应用程序两端通过“套接字”向网络发出请求或者应答网络请求。可以把socket理解为通信的把手(hand)
socket通信流程
相关方法及参数介绍
import socket sk = socket.socket()
socket()的参数
socket.socket(socket_family, socket_type, protocal=0)
family=AF_INET(服务器之间的通信)
family=AF_INET6(ipv6 服务器之间的通信)
family=AF_UNIX (Unix不同进程间的通信)
type=SOCK_STREAM(流式socket): TCP
type=SOCK_DGRAM(数据socket): UDP
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
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() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
最简单单次通信
import socket sk = socket.socket() #print(sk) #服务器端的socket对象 #<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)> address = ('127.0.0.1', 8000) sk.bind(address) #参数address必须为元组 sk.listen(3) #决定服务器最大可连接人数(最大排队数, 超过最大排队数就报错) print('waiting....') conn,addr = sk.accept() #print(conn) #客户端的socket对象 #(<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 59977)>, ('127.0.0.1', 59977)) #59977为客户端端口号(随机生成) #注意: sk和conn没有任何关系 inp = input('>>>') conn.send(bytes(inp, 'utf8')) conn.close()
import socket sk = socket.socket() print(sk) address=('127.0.0.1',8000) sk.connect(address) data = sk.recv(1024) #阻塞 print(str(data,'utf8')) sk.close()
不间断通信且可重复连接和退出处理
import socket sk = socket.socket() address = ('127.0.0.1', 8888) sk.bind(address) sk.listen(3) print('waiting...') while True: conn,addr = sk.accept() print(addr) while True: try: data = conn.recv(1024) #阻塞, 在windows下, 若是对方强制退出, 则会报错. 在linux下不会报错, 但是data为空, 所以用 if not data: break 就可以了 except Exception: break if not data: break print(str(data,'utf8')) inp = input('>>>') conn.send(bytes(inp, 'utf8')) sk.close()
import socket sk = socket.socket() address=('127.0.0.1',8888) sk.connect(address) while True: inp = input('>>>') if inp == 'exit': break sk.send(bytes(inp, 'utf8')) data = sk.recv(1024) print(str(data,'utf8')) sk.close()
命令传送和粘包
粘包: 发送数据时间间隔很短,数据了很小,会合到一起,产生粘包
#远程执行命令 import subprocess import socket sk = socket.socket() address = ('127.0.0.1', 8888) sk.bind(address) sk.listen(3) print('waiting connection...') while True: conn,addr = sk.accept() print(addr) while True: try: data = conn.recv(1024) except Exception: break if not data: break print(str(data,'utf8')) #解码 obj = subprocess.Popen(str(data,'utf8'), shell=True, stdout=subprocess.PIPE) # stdout=subprocess.PIPE这句将子进程移到主进程 cmd_result = obj.stdout.read() result_len = bytes(str(len(cmd_result)),'utf8') conn.sendall(result_len) #粘包现象, 由于两次sendadd执行时间的间隔太短, 变成一次过send过去 # import time #处理粘包(low) # time.sleep(0.5) conn.recv(1024) #粘包处理方法 recv阻塞 conn.sendall(cmd_result) conn.close()
import socket sk = socket.socket() address=('127.0.0.1',8888) sk.connect(address) print("客户端启动....") while True: inp = input('>>>').strip() if inp == 'e': break sk.send(bytes(inp, 'utf8')) #编码 - 通道只接收bytes类型数据 result_len = int(str(sk.recv(1024), 'utf8')) sk.sendall(bytes(' ','utf8')) #对应粘包的处理 print(result_len) data = bytes() while len(data) != result_len: recv = sk.recv(1024) data = recv print(str(data,'utf8')) sk.close()
sendall会把数据直接全部发送到客户端,客户端将所有的数据都放到缓冲区,每次recv多少字节取决于recv内的参数,理论不应该超过8k.
所以,并不能一次recv()无限大数据,所以这里应该通过循环去接收.
文件上传
import socket import os sk = socket.socket() address = ('127.0.0.1', 8888) sk.bind(address) sk.listen(3) print('waiting connection...') BASE_DIR = os.path.dirname(os.path.abspath(__file__)) while True: conn,addr = sk.accept() print(addr) while True: data = conn.recv(1024) cmd,filename,file_size = str(data,'utf8').split('|') path = os.path.join(BASE_DIR,'xx',filename) file_size = int(file_size) has_received = 0 f = open(path, "wb") while has_received < file_size: data = conn.recv(1024) f.write(data) has_received += len(data) print("ending") f.close() conn.close()
import socket import os sk = socket.socket() address=('127.0.0.1',8888) sk.connect(address) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) print("客户端启动....") while True: inp = input('>>>').strip() #post|11.png if inp == 'exit': break cmd, path = inp.split('|') path = os.path.join(BASE_DIR,path) filename = os.path.basename(path) file_size = os.stat(path).st_size file_info = 'post|%s|%s'%(filename,file_size) sk.sendall(bytes(file_info,'utf8')) has_sent = 0 file_obj = open(path, "rb") while has_sent < file_size: data = file_obj.read(1024) sk.sendall(data) has_sent += len(data) file_obj.close() print('上传成功') sk.close()
注意:
- 一收一发
- client_data=conn.recv(1024)
- 如果那边send一个空数据 这边recv为空,则recv继续阻塞,等待其他的数据, 所以别发空数据
socketserver
虽然Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好. SocketServer模块简化了编写网络服务程序的任务, 同时SocketServer模块也是Python标准库中很多服务器框架的基础.
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类:
一个是Server类,用于处理连接相关的网络操作
另一个则是RequestHandler类,用于处理数据相关的操作
并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程
Server类
它包含了种五种server类,BaseServer(不直接对外服务). TCPServer使用TCP协议,UDPServer使用UDP协议,还有两个不常使用的,即UnixStreamServer和UnixDatagramServer,这两个类仅仅在unix环境下有用(AF_unix).
RequestHandler类
所有requestHandler都继承BaseRequestHandler基类
创建一个socketserver 至少分以下几步
- First, you must create a request handler class by subclassing the
BaseRequestHandler
class and overriding itshandle()
method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()
orserve_forever()
method of the server object to process one or many requests. - Finally, call
server_close()
to close the socket.
为了让socketserver并发, 必须选择使用以下一个多并发的类
- socketserver.ForkingTCPServer
- socketserver.ForkingUDPServer
- socketserver.ThreadingTCPServer
- socketserver.ThreadingUDPServer
聊天并发实例
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print ("服务端启动...") while True: conn = self.request print (self.client_address) while True: client_data=conn.recv(1024) print (str(client_data,"utf8")) print ("waiting...") server_response=input(">>>") conn.sendall(bytes(server_response,"utf8")) # conn.sendall(client_data) conn.close() # print(self.request,self.client_address,self.server) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer) #创建socket连接 ThreadingTCPServer使其可以多线程, 否则单用TCPServer也行, 但无并发效果, ForkingTCPServer则是多进程 server.serve_forever()
import socket ip_port = ('127.0.0.1',8098) sk = socket.socket() sk.connect(ip_port) print ("客户端启动:") while True: inp = input('>>>') sk.sendall(bytes(inp,"utf8")) if inp == 'exit': break server_response=sk.recv(1024) print (str(server_response,"utf8")) sk.close()