网络编程基础
一. 简介
1.1 简单的socket通信例子(TCP)
服务端:
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8083)) #0-65535:0-1024给操作系统使用 phone.listen(5) print('starting...') while True: # 链接循环 conn,client_addr=phone.accept() print(client_addr) while True: #通信循环 try: data=conn.recv(1024) if not data:break #适用于linux操作系统 print('客户端的数据',data) conn.send(data.upper()) except ConnectionResetError: #适用于windows操作系统 break conn.close() phone.close()
客户端:
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8083)) while True: msg=input('>>: ').strip() #msg='' if not msg:continue phone.send(msg.encode('utf-8')) #phone.send(b'') # print('has send') data=phone.recv(1024) # print('has recv') print(data.decode('utf-8')) phone.close()
1.2 简单的socket通信(UDP)
服务端:
import socket import subprocess ip_port = ('127.0.0.1', 9003) bufsize = 1024 udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_server.bind(ip_port) while True: # 收消息 cmd, addr = udp_server.recvfrom(bufsize) print('用户命令----->', cmd,addr) # 逻辑处理 res = subprocess.Popen(cmd.decode('gbk'), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE) stderr = res.stderr.read() stdout = res.stdout.read() # 发消息 print(len(stdout)+len(stderr)) udp_server.sendto(stdout + stderr, addr) udp_server.close()
客户端:
from socket import * import time ip_port = ('127.0.0.1', 9003) bufsize = 8192 udp_client = socket(AF_INET, SOCK_DGRAM) while True: msg = input('>>: ').strip() if len(msg) == 0: continue udp_client.sendto(msg.encode('gbk'), ip_port) data, addr = udp_client.recvfrom(bufsize) print(data.decode('gbk'), end='')
1.3 粘包现象
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
只有TCP有粘包现象,UDP永远不会粘包
1.4 解决粘包
通过struct模块为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
服务端:
import socket import subprocess import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',9909)) #0-65535:0-1024给操作系统使用 phone.listen(5) print('starting...') while True: # 链接循环 conn,client_addr=phone.accept() print(client_addr) while True: #通信循环 try: #1、收命令 cmd=conn.recv(8096) if not cmd:break #适用于linux操作系统 #2、执行命令,拿到结果 obj = subprocess.Popen(cmd.decode('gbk'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=obj.stdout.read() stderr=obj.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()
客户端:
import socket import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',9909)) while True: #1、发命令 cmd=input('>>: ').strip() #ls /etc if not cmd:continue phone.send(cmd.encode('gbk')) #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')) phone.close()
1.5 简单的FTP
服务端:
import socket import subprocess import struct import json import os share_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/server/share' def get(conn,cmds): filename = cmds[1] # 3、以读的方式打开文件,读取文件内容发送给客户端 # 第一步:制作固定长度的报头 header_dic = { 'filename': filename, # 'filename':'1.mp4' 'md5': 'xxdxxx', 'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename)) # os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4') } 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) # 第四步:再发送真实的数据 with open('%s/%s' % (share_dir, filename), 'rb') as f: # conn.send(f.read()) for line in f: conn.send(line) def put(conn,cmds): pass def run(): phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用 phone.listen(5) print('starting...') while True: # 链接循环 conn,client_addr=phone.accept() print(client_addr) while True: #通信循环 try: #1、收命令 res=conn.recv(8096) # b'put 1.mp4' if not res:break #适用于linux操作系统 #2、解析命令,提取相应命令参数 cmds=res.decode('utf-8').split() #['put','1.mp4'] if cmds[0] == 'get': get(conn,cmds) elif cmds[0] == 'put': input(conn,cmds) except ConnectionResetError: #适用于windows操作系统 break conn.close() phone.close() if __name__ == '__main__': run()
客户端:
import socket import struct import json download_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/client/download' def get(phone,cmds): # 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) ''' header_dic={ 'filename': filename, #'filename':'1.mp4' 'md5':'xxdxxx', 'file_size': os.path.getsize(filename) } ''' print(header_dic) total_size = header_dic['file_size'] filename = header_dic['filename'] # 第四步:接收真实的数据 with open('%s/%s' % (download_dir, filename), 'wb') as f: recv_size = 0 while recv_size < total_size: line = phone.recv(1024) # 1024是一个坑 f.write(line) recv_size += len(line) print('总大小:%s 已下载大小:%s' % (total_size, recv_size)) def put(phone,cmds): pass def run(): phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8912)) while True: #1、发命令 inp=input('>>: ').strip() #get a.txt if not inp:continue phone.send(inp.encode('utf-8')) cmds=inp.split() #['get','a.txt'] if cmds[0] == 'get': get(phone,cmds) elif cmds[0] == 'put': put(phone,cmds) phone.close() if __name__ == '__main__': run()
1.6 初识socketserver
基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)
源码分析总结:
基于tcp的socketserver我们自己定义的类中的
- self.server即套接字对象
- self.request即一个链接
- self.client_address即客户端地址
基于udp的socketserver我们自己定义的类中的
- self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
- self.client_address即客户端地址
服务端:
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): # 创建一个链接,继承于socketserver中的BaseRequestHandler类 conn = self.request # 发送登录提示 print("Client connect by:", self.client_address) while True: try: print("Waitting for recving message...") # 接收消息 message = conn.recv(1024) if len(message) == 0: break print(message.decode('utf-8')) # 收到exit就退出 # 回复消息 # 发送消息 conn.sendall(message.upper()) except ConnectionResetError: print('connection is lost by:', self.client_address) break if __name__ == "__main__": server = socketserver.ThreadingTCPServer(('127.0.0.1', 8089, ), MyServer) # 调用serve_forever方法 server.serve_forever()
客户端:
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8089)) while True: msg=input('>>: ').strip() #msg='' if not msg:continue phone.send(msg.encode('utf-8')) #phone.send(b'') # print('has send') data=phone.recv(1024) # print('has recv') print(data.decode('utf-8')) phone.close()