标签:通信模型 cvs 代码 mys str clean 监视 .com 抽象
1.硬件C/S架构(打印机)
2.软件C/S架构
互联网中处处是C/S架构
如淘宝网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)
腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)
C/S架构与socket的关系:我们学习socket就是为了完成C/S架构的开发
1、引子
须知一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了(打个单机游戏,玩个扫雷啥的)
如果你要跟别人一起玩,那你就需要上网了,什么是互联网?
互联网的核心就是由一堆协议组成,协议就是标准,比如全世界人通信的标准是英语
如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。
人们按照分工不同把互联网协议从逻辑上划分了层级,
详见网络通信原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html
为何学习socket一定要先学习互联网协议:
首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件
其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的
然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。
2、TCP/IP协议
1、socket概述
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket和file的区别:
2、套接字工作流程
3、基本应用
import socket ‘‘‘客户端:发--收‘‘‘ ip_port = (‘127.0.0.1‘,9999) #封装一个对象 s = socket.socket() #链接服务端,如果已经有一个链接,就挂起 s.connect(ip_port) while True: # 发送数据 send_data = input(‘>>:‘).strip() #如果发送字符串长度为0,就重新输入 if len(send_data) == 0:continue #如果输入exit,客户端退出,不发送数据 if send_data == ‘exit‘: break s.send(bytes(send_data,encoding=‘utf-8‘)) #解决粘包问题,先接收带有数据长度的字节 ready_tag = s.recv(1024) ready_tag = str(ready_tag,encoding=‘utf-8‘) if ready_tag.startswith(‘Ready‘): #获取待接收数据长度 data_size = int(ready_tag.split(‘|‘)[-1]) #发送信号 start_tag = ‘Start‘ s.send(bytes(start_tag, encoding=‘utf-8‘)) # 基于数据长度循环接收数据 recv_msg = b‘‘ #空字节 recv_size = 0 while recv_size < data_size: recv_data = s.recv(1024) recv_msg += recv_data recv_size += len(recv_data) print(‘data size %s ,recv size %s ‘%(data_size,recv_size)) print(str(recv_msg,encoding=‘utf-8‘)) #关闭 s.close()
import socket import subprocess#导入执行命令模块 ‘‘‘服务端:收--发‘‘‘ #前1024个是系统端口 ip_port = (‘127.0.0.1‘,9999)#定义元组 #封装一个对象 s = socket.socket() #绑定ip+协议+端口,用来唯一标识一个进程,ip_port必须是元组格式 s.bind(ip_port) #定义最大可以挂起链接数,Python3默认最大值为128 s.listen(5) #循环待机状态,随时接收新的客户端连接 while True: #接收客户链接请求,conn相当于一个特定链接,addr是客户端ip+port conn,addr = s.accept() while True:#用来基于一个链接重复收发消息 try: #用来捕捉客户端关闭异常 # 接收数据 recv_data = conn.recv(1024) #如果客户端断开,接收不到数据,就断开当前链接 if len(recv_data) == 0:break #将接收到的命令输出到Windows终端,并执行命令 p = subprocess.Popen(str(recv_data,encoding=‘utf-8‘),shell=True,stdout=subprocess.PIPE) #接收gbk编码格式的返回值 ret = p.stdout.read() if len(ret) == 0: #如果命令错误,返回值为空 send_data = ‘cmd error‘ else: #如果命令成功 send_data = str(ret,encoding=‘gbk‘) #发送数据长度 send_data = bytes(send_data,encoding=‘utf-8‘) ready_tag = ‘Ready|%s‘ % len(send_data) conn.send(bytes(ready_tag,encoding=‘utf-8‘)) #接收发送信号 back_data = conn.recv(1024) back_data = str(back_data,encoding=‘utf-8‘) if back_data.startswith(‘Start‘): # 发送数据 conn.send(send_data) except Exception:#如果出现异常 break #断开连接 conn.close()
4、更多功能 :sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
服务端套接字函数
sk.bind(address)
s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5,这个值不能无限大,因为要在内核中维护连接队列;
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()类似,但接收UDP数据,返回值是(data,address)。其中data是包含接收数据的字节,address是发送数据的套接字地址。当接收到的数据为空时不阻塞,会接收到一个空的字节。
sk.send(bytes[,flag])
将字符串数据以字节的方式发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于指定数据的字节大小。即:可能未将指定内容全部发送。
sk.sendall(bytes[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。
sk.sendto(bytes[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
面向锁的套接字方法
sk.setblocking(bool)
是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
sk.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
5、基于UDP的套接字
#服务端 1 ss = socket() #创建一个服务器的套接字 2 ss.bind() #绑定服务器套接字 3 inf_loop: #服务器无限循环 4 cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送) 5 ss.close()
#客户端 cs = socket() # 创建客户套接字 comm_loop: # 通讯循环 cs.sendto()/cs.recvfrom() # 对话(发送/接收) cs.close()
6、总结
1、概述
Windows Python: #提供: select Mac Python: #提供: select Linux Python: #提供: select、poll、epoll
注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
2、伪并发:利用select实现伪同时处理多个Socket客户端请求
import socket import select ‘‘‘IO多路复用服务端‘‘‘ ip_port = (‘127.0.0.1‘,9999) s = socket.socket() s.bind(ip_port) s.listen(5) inputs = [s,] outputs = [] msg = {} #{"charlie":[消息1,消息2,]} while True: # 监听sk(服务端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为【s】 # 监听conn对象,如果conn对象发生变化,表示客户端有消息进来,此时rlist值为【客户端】 rlist,wlist,elist = select.select(inputs,outputs,[],1)#1表示间隔1s print(len(inputs),len(rlist),len(wlist)) for r in rlist: if r == s: conn,addr = r.accept() #conn也是一个socket对象,每连接一个客户端,就新建一个对应的conn inputs.append(conn) conn.sendall(bytes(‘hello...‘,encoding=‘utf-8‘)) #客户链接之后,为客户创建一个存放消息的列表 msg[conn] = [] else: try: recv_data = r.recv(1024) print(‘收到‘) #判断接收到的消息是否为空 if not recv_data: #如果为空,主动抛出异常 raise Exception(‘断开连接‘) else: #所有发过消息的客户端对象加入列表 outputs.append(r) msg[r].append(recv_data) except Exception as ex: #如果客户端关闭,清除接收到的对象和消息 inputs.remove(r) del msg[r] #收发分离,发送部分 for w in wlist: last_msg = msg[w].pop()#上一次接收的消息 resp = bytes(‘response:‘,encoding=‘utf-8‘) + last_msg w.sendall(resp) #发送完消息,就移除该对象 outputs.remove(w)
import socket ‘‘‘IO多路复用客户端‘‘‘ ip_port = (‘127.0.0.1‘,9999) s = socket.socket() s.connect(ip_port) recv_data = s.recv(1024) print(str(recv_data,encoding=‘utf-8‘)) while True: inp = input(‘>>>:‘) if len(inp) == 0: continue else: s.sendall(bytes(inp,encoding=‘utf-8‘)) recv_data = s.recv(1024) print(recv_data.decode())
此处的Socket服务端相比与原生的Socket,他支持当某一个请求不再发送数据时,服务器端不会等待而是可以去处理其他请求的数据。但是,如果每个请求的耗时比较长时,select版本的服务器端也无法完成同时操作。
1、ThreadingTCPServer基础
import socketserver #服务端 class MyServer(socketserver.BaseRequestHandler): def handle(self): conn = self.request conn.send(bytes(‘欢迎致电... ‘,encoding=‘utf-8‘)) while True: try: recv_data = conn.recv(1024) print("[%s] says %s."%(self.client_address,recv_data.decode())) conn.send(recv_data) except Exception as e: print(e) break if __name__ == ‘__main__‘: server = socketserver.ThreadingTCPServer((‘127.0.0.1‘, 8009), MyServer) #无限循环接收新链接 server.serve_forever()
import socket #客户端 ip_port = (‘127.0.0.1‘,8009) s = socket.socket() s.connect(ip_port) #接收欢迎信息 welcome_msg = s.recv(1024) print(welcome_msg.decode()) while True: send_data = input(‘>>:‘).strip() if len(send_data) == 0:continue #如果输入exit,客户端退出,不发送数据 if send_data == ‘exit‘: break s.send(bytes(send_data, encoding=‘utf-8‘)) recv_data = s.recv(1024) print(recv_data.decode())
2、断点续传
文件打开方式:a,追加 w,清空写入
文件指针:seek(num)
#服务端方法 def put(self,args): ‘‘‘接收上传文件‘‘‘ conn = self.request filesize = args["filesize"] file_dir = os.path.join(settings.USER_DIR,args["file_owner"]) file_name = os.path.join(file_dir, args["filename"]) if args["filename"] in file_dir: #判断文件是否存在,断点传续 has_recv = os.path.getsize(args["filename"]) # 计算临时文件大小 conn.sendall(bytes(has_recv, encoding=self.coding)) with open(file_name,‘ab‘) as f: while has_recv < filesize: recv_data = conn.recv(self.max_packet_size) f.write(recv_data) has_recv += len(recv_data) print("接收完成。。。") self.edit_user_info(args) else: start_tag = ‘Start‘ conn.sendall(bytes(start_tag, encoding=self.coding)) recv_size = 0 with open(file_name,‘wb‘) as f: while recv_size < filesize: recv_data = conn.recv(self.max_packet_size) f.write(recv_data) recv_size += len(recv_data) print("接收完成。。。") self.edit_user_info(args)
#客户端方法 def put(self,args): ‘‘‘ 客户端上传数据 :param args: [命令,文件名] :return: 无 ‘‘‘ cmd = args[0] filename = args[1] if not os.path.isfile(filename): print("%s is not exists..."%filename) else: filesize = os.path.getsize(filename) head_dic = {"cmd":cmd,"filename":os.path.basename(filename), "filesize":filesize,"file_owner":CURRENT_USER["username"]} print(head_dic)####### head_json = json.dumps(head_dic) self.socket.sendall(bytes(head_json,encoding=self.coding)) start_tag = self.socket.recv(self.max_packet_size).decode() if start_tag.startswith(‘Start‘): send_size = 0 else: send_size = int(start_tag) with open(filename,‘rb‘) as f: f.seek(send_size) while send_size < filesize: data = f.read(1024) self.socket.sendall(data) send_size += len(data) self.view_bar(send_size, filesize) print("\n上传成功,文件大小:%s 已发送大小:%s"%(filesize,send_size))
3、作用域
if 1 == 1: name = ‘charlie‘ print(name) #charlie
#无法实现 def func(): name = ‘charlie‘ func() print(name)
#作用域链 name = ‘charlie‘ def f1(): name = ‘a‘ def f2(): name = ‘b‘ print(name) f2() f1()#b
name = ‘charlie‘ def f1(): print(name) def f2(): name = ‘alex‘ f1() f2()#charlie
4、面试知识点:列表+lambda表达式+for循环
‘‘‘ 函数在执行之前,函数内部代码不执行,函数中的变量由for循环定义, 变量一直被重新赋值,直到循环结束,变量等于最后一个循环的值,列表 内部元素是函数,函数加()表示执行函数,得到返回值 ‘‘‘ li = [lambda :x for x in range(10)] ret1 = li[0]() ret2 = li[9]() print(ret1)#9 print(ret2)#9
#每执行一次循环,函数的参数就被重新赋值,所以函数内部的变量是已经被定好的 li = [lambda x=i:x for i in range(10)] ret1 = li[0]() ret2 = li[1]() ret9 = li[9]() print(ret1)#0 print(ret2)#1 print(ret9)#9
5、ThreadingTCPServer源码剖析
ThreadingTCPServer的类图关系如下:
内部调用流程为:
6、ForkingTCPServer
基本使用:
#ForkingTCPServer只是将 ThreadingTCPServer 实例中的代码: server = SocketServer.ThreadingTCPServer((‘127.0.0.1‘,8009),MyRequestHandler) 变更为: server = SocketServer.ForkingTCPServer((‘127.0.0.1‘,8009),MyRequestHandler)
SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 os.fork 两个东西,其实本质上就是在服务器端为每一个客户端创建一个进程,当前新创建的进程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
FTP作业:上传下载文件
import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding=‘utf-8‘ request_queue_size = 5 server_dir=‘file_upload‘ def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print(‘from client ‘,self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack(‘i‘, head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={‘cmd‘:‘put‘,‘filename‘:‘a.txt‘,‘filesize‘:123123} cmd=head_dic[‘cmd‘] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args[‘filename‘] )) filesize=args[‘filesize‘] recv_size=0 print(‘----->‘,file_path) with open(file_path,‘wb‘) as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print(‘recvsize:%s filesize:%s‘ %(recv_size,filesize)) tcpserver1=MYTCPServer((‘127.0.0.1‘,8080)) tcpserver1.run()
import socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding=‘utf-8‘ request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print(‘file:%s is not exists‘ %filename) return else: filesize=os.path.getsize(filename) head_dic={‘cmd‘:cmd,‘filename‘:os.path.basename(filename),‘filesize‘:filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack(‘i‘,len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,‘rb‘) as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print(‘upload successful‘) client=MYTCPClient((‘127.0.0.1‘,8080)) client.run()
标签:通信模型 cvs 代码 mys str clean 监视 .com 抽象
原文地址:https://www.cnblogs.com/charliedaifu/p/10066256.html