socket 相关
解决粘包问题:
服务端:先发送报头的长度,再发送报头的内容,最后才是内容
客户端:先先接收报头的长度,再接收报头内容(有必要的时候报头的内容都需要循环接收),然后解析报头的内容获取结果内容的信息再收取结果内容。
服务端代码:
# -*- coding: utf-8 -*- import json import socket import struct import subprocess ''' Socket套接字方法 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family(socket家族) socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成 socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) socket type类型 socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET #废弃了 (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.) proto=0 请忽略,特殊用途 fileno=None 请忽略,特殊用途 ''' phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 如果端口已经被使用,则回收再用 phone.bind(('localhost', 7001)) # 0-65535:0-1024给操作系统使用 phone.listen(5) print('服务器已经启动。。。。。') while 1: # 循环连接到服务端,不过得等前一个关掉后一个才能上 conn, addr = phone.accept() print('有客户端连接上来了!') print(conn, addr) if_conn = 1 while 1: # 循环通信 try: # try except是一个客户端端断开的时候,就断开此次的服务,准备开始等待下一个 # windows 是try的方式,linux判断if not data:break即可! # 1.收命令 data = conn.recv(1024) # 要注意的是,客户端服务端的收发命令都是字节类型 # print(data.decode('utf-8')) # 2.执行命令,拿执行结果 # stdout是正确执行的结果,stderr是报错的内容 res = subprocess.Popen(data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 分别读取 stdout = res.stdout.read() stderr = res.stderr.read() # 3、把命令的结果返回给客户端 # 第一步:制作固定长度的报头 header_dic = { 'filename': 'a.txt', 'md5': 'xxdxxx', 'total_size': len(stdout) + len(stderr) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8') # 第二步:先发送报头的长度 conn.send(struct.pack('i', len(header_bytes))) # 第三步:再发报头 conn.send(header_bytes) # 第四步:再发送真实的数据 conn.send(stdout) conn.send(stderr) except ConnectionResetError: # 适用于windows操作系统 break conn.close() phone.close()
客户端
# -*- coding: utf-8 -*- import json import socket import struct ''' Socket套接字方法 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family(socket家族) socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成 socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) socket type类型 socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET #废弃了 (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.) proto=0 请忽略,特殊用途 fileno=None 请忽略,特殊用途 ''' phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 7001)) while True: # 1、发命令 cmd = input('>>: ').strip() # ls /etc if cmd: phone.send(cmd.encode('utf-8')) # 2、拿命令的结果,并打印 # 第一步:先收报头的长度 obj = phone.recv(4) header_size = struct.unpack('i', obj)[0] # 第二步:再收报头 header_bytes = phone.recv(header_size) # 第三步:从报头中解析出对真实数据的描述信息 header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['total_size'] # 第四步:接收真实的数据 recv_size = 0 recv_data = b'' while recv_size < total_size: res = phone.recv(1024) # 1024是一个坑,但这里已经解决啦 recv_data += res recv_size += len(res) print(recv_data.decode('gbk')) # 这里是gbk,因为是这里是在windows上运行的,windows的默认编码是gbk,其他操作系统的要注意 phone.close()
简单多次通信
server ,并且当客户端断开的时候,服务端自动等待下一次链接,而不是报错(windows下的情况,windows是try except 来捕获异常,linux下判断收到的是否为空即可,这里是windows的情况)(windows是报错,linux是死循环)。
# -*- coding: utf-8 -*- import socket ''' Socket套接字方法 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family(socket家族) socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成 socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) socket type类型 socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET #废弃了 (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.) proto=0 请忽略,特殊用途 fileno=None 请忽略,特殊用途 ''' phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 如果端口已经被使用,则回收再用 phone.bind(('localhost', 7001)) # 0-65535:0-1024给操作系统使用 phone.listen(5) print('start') while 1: conn, addr = phone.accept() print('有客户端连接上来了!') print(conn, addr) if_conn = 1 while 1: try: data = conn.recv(1024) print(data.decode('utf-8')) conn.send(data.decode('utf-8').upper().encode('utf-8')) except ConnectionResetError: break conn.close() phone.close()
client
# -*- coding: utf-8 -*- import socket ''' Socket套接字方法 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family(socket家族) socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成 socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) socket type类型 socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET #废弃了 (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.) proto=0 请忽略,特殊用途 fileno=None 请忽略,特殊用途 ''' phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('localhost', 7001)) while 1: cmd = input('>>:').strip() if cmd: phone.send(cmd.encode('utf-8')) get = phone.recv(1024) print(get.decode('utf-8')) phone.close()
老男孩视频整理根据文件大小判断接收文件
#client同时进行了md5加密
import socket import hashlib client = socket.socket() client.connect(('localhost',9998)) while True: cmd = input("请输入:").strip() if len(cmd)==0:continue if cmd.startswith('get'): client.send(cmd.encode()) server_response = client.recv(1024) print("server response:",server_response) client.send(b'ready to recv file') file_total_size = int(server_response.decode()) received_size = 0 filename = cmd.split()[1] f = open(filename+'new','wb') m = hashlib.md5() while received_size < file_total_size: if file_total_size - received_size >1024:#收不止一次 size = 1024 else:#最后一次收了,剩多少收多少,不能多收 size = file_total_size - received_size print('last receive:',size) data = client.recv(size) received_size += len(data) m.update(data) f.write(data) else: new_file_md5 = m.hexdigest() print('file receive done ',received_size,file_total_size) f.close() server_file_md5 = client.recv(1024) print('server file md5:',server_file_md5) print('client file md5',new_file_md5) client.close()
#server
import socket,hashlib,os,time server = socket.socket() server.bind(('localhost',9998)) server.listen() while True: conn,addr = server.accept() print('等待新指令:') data = conn.recv(1024) if not data: print('客户端已断开') break cmd,filename = data.decode().split() print(filename) if os.path.isfile(filename): f = open(filename,'rb') m = hashlib.md5() file_size = os.stat(filename).st_size conn.send(str(file_size).encode()) conn.recv(1024) for line in f : m.update(line) conn.send(line) print('file md5',m.hexdigest()) f.close() conn.send(m.hexdigest().encode()) #send md5 print('send done') server.close()