Socket网络编程:
确保数据发送全部接收方法:先将待发送的数据长度发送给对方,对方根据数据长度写一个while循环接收数据,直到接收的数据长度与发送的数据长度相等。
socket 粘包:同一个客户端或服务器端两次发送的命令靠的很近,容易一起全进入缓冲区,接收到的数据整到一起。避免粘包,交替使用send()和recv()。
FTP文件传输步骤:
server端:
1.读取文件名
2.检测文件是否存在
3.打来文件
4.检测文件大小
5.发送文件大小
6.等待客户端确认
7.开始边读边发数据
8.发送MD5确认
client端:
1.发送命令
2.接收文件大小
3.发送等待接收
4.接收文件并保存
5.接收MD5值并校验
socket内置方法:
Socket 参数介绍
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
socket.socketpair([family[, type[, proto]]])
socket.create_connection(address[, timeout[, source_address]])
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #获取要连接的对端主机地址 必会
sk.setblocking(bool) 必会
是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
sk.accept() 必会
接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
接收TCP 客户的连接(阻塞式)等待连接的到来
sk.connect(address) 必会
连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
sk.connect_ex(address)
同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
sk.close() 必会
关闭套接字
sk.recv(bufsize[,flag]) 必会
接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略
sk.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
sk.send(string[,flag]) 必会
将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送.
sk.sendall(string[,flag]) 必会
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout) 必会
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.getpeername() 必会
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
套接字的文件描述符
socket.sendfile(file, offset=0, count=None)
发送文件 ,但目前多数情况下并无什么卵用。
socket_ssh_client示例:
#socket_ssh_client # -*- coding:utf-8 -*- import socket client=socket.socket() client.connect(('127.0.0.1',8003)) while True: msg=input('>>:').strip() if msg=='quit': break if len(msg)==0:#不能发送空信息,会造成死机 continue client.send(msg.encode('utf-8')) receive_size=client.recv(1024)#接收总数据长度 client.send('避免粘包'.encode('utf-8'))#目的为避免粘包,将服务器端两次send分开。 print(receive_size) receive_data=b'' while int(receive_size.decode('utf-8'))!=len(receive_data):#循环接收所有缓冲区数据 data=client.recv(1024)#接受服务器过来的数据,当数据太多超过1024时,剩余的留在缓冲区,等待下次接收 receive_data+=data#数据存储 print(len(receive_data)) else: print(receive_data.decode())#打印数据 print('receive finish') client.close()
socket_ssh_server示例:
#socket_ssh_server # -*- coding:utf-8 -*- #SSH 为 Secure Shell 的缩写,;SSH 为建立在应用层和传输层基础上的安全协议,专为远程登录会话和其他网络服务提供安全性的协议。 import socket , os,time server=socket.socket() #server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)允许地址重用 server.bind(('localhost',8003)) server.listen(5) while True: conn,addr=server.accept() print(conn,addr) while True: try: data=conn.recv(1024) if len(data)==0: print('The client disconnect automaticlly ') break print(data.decode()) cmd_res=os.popen(data.decode()).read()#执行cmd命令data,执行结果赋给cmd_res。 if len(cmd_res)==0: cmd_res='cmd_res has no output...' conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))#字符串才能编码成二进制。先发送需要传输的数据长度。 #time.sleep(0.5)会卡顿0.5秒的写法 client_ack=conn.recv(1024) conn.send(cmd_res.encode('utf-8')) except ConnectionResetError as e: print('Client abnormal end open',e) break conn.close() server.close()
socket文件传输client:
import socket,hashlib client=socket.socket() client.connect(('localhost',8004)) while True: msg=input('>>:') if msg=='quit':break if msg.startswith('get'): client.send(msg.encode()) FileSize=client.recv(1024) if FileSize!='这不是一个文件'.encode('utf-8'): client.send('可以开始传输文件'.encode('utf-8')) receive_data = b'' f=open('test.txt','wb') m=hashlib.md5() while int(FileSize.decode('utf-8')) > len(receive_data): if int(FileSize.decode('utf-8')) - len(receive_data)>1024:#确保文件不会粘包,刚好接受完 size=1024 else: size=int(FileSize.decode('utf-8')) - len(receive_data) data = client.recv(size) m.update(data) receive_data += data f.write(data) else: print('单次接收完成') f.close() raw_md5=client.recv(1024).decode('utf-8') new_md5=m.hexdigest() print(raw_md5,new_md5) if raw_md5==new_md5: print('文件准确传输') else: print('传输失败') client.close()
socket文件传输server:
import socket,os,hashlib server=socket.socket() server.bind(('127.0.0.1',8004)) server.listen(5) while True: print('等待客户端连接') conn,addr=server.accept() while True: try: news=conn.recv(1024) if not news: print('客户端正常断开') break filename=news.split()[1] if os.path.isfile(filename): f=open(filename,'rb') m=hashlib.md5() FileSize=os.stat(filename).st_size#读取文件大小 conn.send(str(FileSize).encode('utf-8')) conn.recv(1024)#防止粘包 for line in f: conn.send(line) m.update(line) f.close() conn.send(m.hexdigest().encode('utf-8')) else: conn.send('这不是一个文件'.encode('utf-8')) except ConnectionResetError as e: print('客户端异常断开',e) break conn.close() server.close()
socketserver模块及功能:
socketserver支持多用户并发处理,是对socket的封装,模块名socketserver。
模块中包含以下几个类:
1.class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
2.class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)用法同上
3.class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)比较不常见
4.class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)比较不常见
以上四个称为server classes
之间的继承关系:
BaseServer-->TCPServer-->UDPServer-->UnixDatagramServer
BaseServer-->TCPServer-->UnixStreamServer
创建socketserver的步骤:
1.创建一个请求处理类,并且这个类要继承BaseRequstHandle,而且还要重写父类的handle()
2.必须实例化一个server class(例如TCPServer),并且传递IP和你上面创建的请求处理类给这个server class(TCPServer)
3.调用实例的handle_request()或server_forever()方法来处理一个或多个请求
实例.handle_request()#只处理一个请求
实例.server_forever()#可处理多个请求,永远执行,处理一个等待下一个。
4.调用server.close()关闭服务器
多线程并发服务器示例:
# -*- coding:utf-8 -*- import socketserver class MyTCPHandle(socketserver.BaseRequestHandler):#每一个请求过来,都会实例化MyTCPHandle() '''1.自定义一个请求处理类,继承BaseRequestHandle,重写Handle方法''' def handle(self):#和客户端的所有交互在Handle里写 'self.request is the TCP socket connected to client' while True: try: self.data=self.request.recv(1024).strip()#self.requesrt类似于conn,但是此处必须是self.request if not self.data: print('客户端主动断开') break print('{} wrote:'.format(self.client_address[0]))#打印客户端IP地址 print(self.data.decode('utf-8')) self.request.sendall(self.data.upper())#sendall等于重复调用send except ConnectionResetError as e: print('客户端异常断开',e) break addr,port='127.0.0.1',8005 #实例化一个server class(例如TCPServer),并且传递IP和你上面创建的请求处理类给这个server class(TCPServer) # server=socketserver.TCPServer((addr,port),MyTCPHandle)#此时不支持多并发 server=socketserver.ThreadingTCPServer((addr,port),MyTCPHandle)#支持多并发,多线程 #每来一个请求,开启一个新的线程。 # class ThreadingTCPServer(ThreadingMixIn, TCPServer):pass #server=socketserver.ForkingTCPServer((addr,port),MyTCPHandle)#多进程,没进来一个请求,开启一个新的进程。 # 在linix系统上,forkingTCPServer()效果与TheradingTCPServer()相同。 server.serve_forever() server.server_close()
对应服务器客户端示例:
# -*- coding:utf-8 -*- import socket client=socket.socket() client.connect(('localhost',8005)) while True: msg=input('>>:') if msg=='quit':break if len(msg)==0:continue client.send(msg.encode('utf-8')) data=client.recv(1024) print(data.decode('utf-8')) client.close()
BaseServer的内置方法:
# socketserver.BaseServer.
# fileno()返回文件描述符,不常用
#handle_request()#处理单个请求
#server_forever(poll_interval=0.5)#处理多个请求
#allow_reuse_address允许地址重用
#timeout超时时间
#setup()在handle()之前调用
#handle()请求内容
#finish()zaihandle()之后调用
#get_request()获得请求的实例和IP地址