Python之网络编程(socket模块)
-
什么是socket?
- Socket是应用层与TCP / IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个“门面模式”,它把复杂的TCP / IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
- 基于文件类型的套接字家族:
- 套接字家族的名字:AF_UNIX
Unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
- 套接字家族的名字:AF_UNIX
- 基于网络类型的套接字家族
- 套接字家族的名字:AF_INET
还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,Python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET。
- 套接字家族的名字:AF_INET
-
为什么要用socket?
- 使用socket,我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规则去编程,写出的程序自然就是遵循TCP/UDP标准的。
-
基于TCP协议的socket(无并发)
-
服务端
# tcp是基于可靠链接的,必须先启动服务端,然后再启动客户端去链接服务端 import socket # 由于 socket 模块中有太多的属性。因此可以使用'from module import *'语句。也就是 'from socket import *',这样我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短代码。 # from socket import * # socket_server = socket(AF_INET,SOCK_STREAM) socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建服务器套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。 socket_server.bind(('127.0.0.1', 8080)) # 把地址绑定到套接字,只有服务端需要绑定,IP地址填写服务器IP,端口是数字类型,1025-65530任选,端口0-1024系统占用 socket_server.listen(5) # 监听链接,backlog = 5 表示同一时间能接受5个请求,并不是最大连接数 # 等待连接 conn, client_address = socket_server.accept() # 程序阻塞,等待连接,有两个参数,一个连接对象conn,一个客户端地址client_address(包含IP和端口),对象conn是tcp三次握手的产物,用来收发消息,而socket_server对象是专门用来建立连接的 # 收发消息 msg = conn.recv(1024) # 收消息,有个返回值给msg,1024是一个最大的限制,表示最多能收 1024 bytes conn.send('Hello!!!'.encode('utf-8')) # 发消息,网络中只能传输bytes类型 conn.close() # 断开(关闭)客户端套接字,回收系统资源。完成TCP四次挥手。 socket_server.close() # 断开(关闭)服务器套接字,回收系统资源
-
客户端
import socket socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建客户端套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。 socket_client.connect(('127.0.0.1', 8080)) # 与服务器建立连接,地址为服务器的IP地址和端口号 socket_client.send('Hello!'.encode('utf-8')) # 发消息,注意字符串不能直接直接发,需要转换成二进制 msg = socket_client.recv(1024) # 收消息 socket_client.close()
-
-
加上通信循环和连接循环的socket(无并发)(解决服务端不可以循环接收客户端信息的问题)
-
服务端
import socket socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 如果什么参数都不传,默认就是这个 socket_server.bind(('127.0.0.1', 8080)) socket_server.listen(5) while True: # 加连接循环 conn, client_address = socket_server.accept() while True: # 加通信循环 try: msg = conn.recv(1024) if not msg: break # 针对Linux操作系统 print('客户端:', client_address) conn.send(msg + b'_SB') except ConnectionResetError: break conn.close() socket_server.close()
-
客户端
import socket socke_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socke_client.connect(('127.0.0.1', 8080)) while True: msg = input('>>>:') socke_client.send(msg.encode('utf-8')) msg = socke_client.recv(1024) print(msg) socke_client.close()
-
-
远程执行命令的小程序(无并发)
-
服务端
from socket import * import subprocess import struct # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串 socket_server = socket(AF_INET, SOCK_STREAM) socket_server.bind(('127.0.0.1', 8080)) socket_server.listen(5) while True: conn, client_address = socket_server.accept() print('正在监听……') while True: try: cmd = conn.recv(1024) if not cmd: break print('开始接收文件……') obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_stdout = obj.stdout.read() res_stderr = obj.stderr.read() except ConnectionResetError: break # -*- 解决TCP粘包问题 -*-# # 制作固定长度的报头 total_size = len(res_stdout) + len(res_stderr) header = struct.pack('i', total_size) # 制作固定长度的报头 # 发送报头 conn.send(header) # conn.send(res_stdout+res_stderr) # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。 conn.send(res_stdout) conn.send(res_stderr) conn.close() socket_server.close()
-
客户端
from socket import * import struct socket_client = socket(AF_INET, SOCK_STREAM) socket_client.connect(('127.0.0.1', 8080)) while True: cmd = input('>>>:') if not cmd: continue # 判断cmd不为空,if x is not None:continue 是最好的写法 socket_client.send(cmd.encode('utf-8')) # -*- 解决TCP粘包问题 -*-# # 先收固定长度的报头 header = socket_client.recv(4) # 解析报头 total_size = struct.unpack('i', header)[0] # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取 recv_size = 0 res = b'' while recv_size < total_size: recv_date = socket_client.recv(1024) res += recv_date recv_size += len(recv_date) # info = socket_client.recv(1024) print(res.decode('gbk')) socket_client.close()
-
-
远程执行命令的小程序(自定义报头,无并发)
-
服务端
from socket import * import subprocess import struct # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串 import json socket_server = socket(AF_INET, SOCK_STREAM) socket_server.bind(('127.0.0.1', 8081)) socket_server.listen(5) while True: conn, client_address = socket_server.accept() print('正在监听……') while True: try: cmd = conn.recv(1024) if not cmd: break print('开始接收文件……') obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_stdout = obj.stdout.read() res_stderr = obj.stderr.read() except ConnectionResetError: break # 制作报头 header_dic = {'total_size': len(res_stdout) + len(res_stderr), 'md5': 'xxxxxxxxxxxx', 'filename': 'xxx.py'} header_json = json.dumps(header_dic) # 将字典转换成字符串 header_bytes = header_json.encode('utf-8') # 将字符串转换成 # 获取报头长度 header_size = len(header_bytes) # 发送报头 conn.send(struct.pack('i', header_size)) # conn.send(res_stdout+res_stderr) # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。 # 发送数据 conn.send(res_stdout) conn.send(res_stderr) conn.close() socket_server.close()
-
客户端
from socket import * import struct import json socket_client = socket(AF_INET, SOCK_STREAM) socket_client.connect(('127.0.0.1', 8081)) while True: cmd = input('>>>:') if not cmd: continue # 判断cmd不为空,if x is not None:continue 是最好的写法 socket_client.send(cmd.encode('utf-8')) # 先收报头的长度 header_size = struct.unpack('i', socket_client.recv(4))[0] # 接收报头 header_bytes = socke_client.recv(header_size) # 解析报头 header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) total_size = header_dic['total_size'] # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取 recv_size = 0 res = b'' while recv_size < total_size: recv_date = socke_client.recv(1024) res += recv_date recv_size += len(recv_date) # info=socket_client.recv(1024) print(res.decode('gbk')) socket_client.close()
-
-
基于UDP的socket
数据报协议(UDP)没有粘包问题,UDP协议面向无连接,发送数据,无需对方确认,发送效率高,但UDP协议有效传输数据大小为 512 bytes,超过这个大小就非常容易丢包,DNS服务使用UDP协议,由于这个限制,导致全球根服务器数量限制在13台。
-
服务端
import socket socket_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) socket_server.bind(('127.0.0.1', 8080)) while True: client_msg, client_address = socket_server.recvfrom(1024) socket_server.sendto(client_msg.upper(), client_address)
-
客户端
import socket socket_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: msg = input('>>>:').strip() socket_client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) server_msg, server_address = socket_client.recvfrom(1024) print(server_msg.decode('utf-8'))