Python-socket网络编程
一、计算机网络
多台独立的计算机用网络通信设备连接起来的网络。实现资源共享和数据传递。比如,我们之前的学过的知识可以将D盘的一个文件传到C盘,但如果你想从你的电脑传一个文件到我的电脑上目前是做不到的; 或者我们现在是不是只能设计单机版的游戏而网络编程则可以解决这些问题。
二、网络编程
通过某种计算机语言来实现不同设备间的资源共享和信息传递。计算机网络的创造可能比计算机本身意义更重大!!!(否则,你只能玩单机版游戏)
- OSI模型 OSI模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、 网络层、传输层、会话层、表示层和应用层。
-
网络通信要素:
a、IP地址
b、端口号
c、传输协议
注意:在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接# “我能给你讲个关于tcp的笑话吗?” # “行,给我讲个tcp笑话.” # “好吧那我就给你讲个tcp笑话.”
三、socket编程
TCP/UDP协议,TCP链接可靠,UDP链接不可靠,贯穿学习的为TCP协议
通信流程:
流程描述:
# 1 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket # # 2 服务器为socket绑定ip地址和端口号 # # 3 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开 # # 4 客户端创建socket # # 5 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket # # 6 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求 # # 7 客户端连接成功,向服务器发送连接状态信息 # # 8 服务器accept方法返回,连接成功 # # 9 客户端向socket写入信息(或服务端向socket写入信息) # # 10 服务器读取信息(客户端读取信息) # # 11 客户端关闭 # # 12 服务器端关闭
相关参数及方法介绍
sk = socket.socket() #创建一个socket对象,它会有三个默认参数family=AF_INET, type=SOCK_STREAM, proto=0
sk.bind(address) #s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 sk.listen(backlog) #开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。 #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5 #这个值不能无限大,因为要在内核中维护连接队列 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() #套接字的文件描述符
举例:
上传文件代码
========================================== 服务端 =============================================
import socket import os #调用socket和OS模块 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sk = socket.socket() #创建服务端socket对象 addr = ("127.0.0.1", 8001) #设置地址及端口 sk.bind(addr) #绑定地址和端口 sk.listen(3) #监听 conn, addrs = sk.accept() #等待客户端连接,阻塞 file_info = conn.recv(1024) #获取客户端发送的文件信息 cmd, file_name, file_size = str(file_info, "utf-8").split("|") #将客户端需求、文件名称、文件大小识别 file_name = os.path.join(BASE_DIR, "db", file_name) #确定文件储存路径 f = open(file_name, "ab") #用追加的方式打开绝对路径文件 has_recv = 0 #设置标志 while has_recv != file_size: #当标志的大小不等于文件的大小时,循环接受客户端发送的文件内容 data = conn.recv(1024) f.write(data) has_recv += len(data) f.close() 关闭文件
=============================================== 客户端 ======================================
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import socket
sk = socket.socket()
addr = ("127.0.0.1", 8001)
sk.connect(addr) #连接服务端地址、端口
while True:
inp = input(">>:") #发送请求
cmd, path = inp.split("|")
file_path = os.path.join(BASE_DIR, path) #获取文件绝对路径
file_name = os.path.basename(path) #获取文件名
file_size = os.stat(path).st_size #获取文件大小
file_info = "post|%s|%s" % (file_name, file_size)
sk.sendall(bytes(file_info, "utf-8")) #发送请求、文件名字、文件大小
f = open(path, "rb")
has_send = 0
while has_send != file_size: #当标志位不等于发送的文件内容大小时,循环发送文件内容
data = f.read(1024)
sk.sendall(data)
has_send += len(data)
f.close()
print("上传成功")
四、socketserver
虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也是Python标准库中很多服务器框架的基础。
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。
Server类
它包含了种五种server类,BaseServer(不直接对外服务)。TCPServer使用TCP协议,UDPServer使用UDP协议,还有两个不常使用的,即UnixStreamServer和UnixDatagramServer,这两个类仅仅在unix环境下有用(AF_unix)。
BaseServer的源码:
class BaseServer: """Base class for server classes. Methods for the caller: - __init__(server_address, RequestHandlerClass) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you do not use serve_forever() - fileno() -> int # for select() Methods that may be overridden: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - server_close() - process_request(request, client_address) - shutdown_request(request) - close_request(request) - service_actions() - handle_error() Methods for derived classes: - finish_request(request, client_address) Class variables that may be overridden by derived classes or instances: - timeout - address_family - socket_type - allow_reuse_address Instance variables: - RequestHandlerClass - socket """ timeout = None def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False def server_activate(self): """Called by constructor to activate the server. May be overridden. """ pass def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: while not self.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set() def shutdown(self): """Stops the serve_forever loop. Blocks until the loop has finished. This must be called while serve_forever() is running in another thread, or it will deadlock. """ self.__shutdown_request = True self.__is_shut_down.wait() def service_actions(self): """Called by the serve_forever() loop. May be overridden by a subclass / Mixin to implement any code that needs to be run during the loop. """ pass # The distinction between handling, getting, processing and # finishing a request is fairly arbitrary. Remember: # # - handle_request() is the top-level call. It calls # select, get_request(), verify_request() and process_request() # - get_request() is different for stream or datagram sockets # - process_request() is the place that may fork a new process # or create a new thread to finish the request # - finish_request() instantiates the request handler class; # this constructor will handle the request all by itself def handle_request(self): """Handle one request, possibly blocking. Respects self.timeout. """ # Support people who used socket.settimeout() to escape # handle_request before self.timeout was available. timeout = self.socket.gettimeout() if timeout is None: timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) fd_sets = _eintr_retry(select.select, [self], [], [], timeout) if not fd_sets[0]: self.handle_timeout() return self._handle_request_noblock() def _handle_request_noblock(self): """Handle one request, without blocking. I assume that select.select has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). """ try: request, client_address = self.get_request() except OSError: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.shutdown_request(request) def handle_timeout(self): """Called if no new request arrives within self.timeout. Overridden by ForkingMixIn. """ pass def verify_request(self, request, client_address): """Verify the request. May be overridden. Return True if we should proceed with this request. """ return True def process_request(self, request, client_address): """Call finish_request. Overridden by ForkingMixIn and ThreadingMixIn. """ self.finish_request(request, client_address) self.shutdown_request(request) def server_close(self): """Called to clean-up the server. May be overridden. """ pass def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request): """Called to shutdown and close an individual request.""" self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" pass def handle_error(self, request, client_address): """Handle an error gracefully. May be overridden. The default is to print a traceback and continue. """ print('-'*40) print('Exception happened during processing of request from', end=' ') print(client_address) import traceback traceback.print_exc() # XXX But this goes to stderr! print('-'*40)
五类的继承关系图,四个代表四种类型的同步服务器
RequestHandler类
所有requestHandler都继承BaseRequestHandler基类。
class BaseRequestHandler: """Base class for request handler classes. This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method. The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define arbitrary other instance variariables. """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass
创建一个socketserver 至少分以下几步:
1、首先,你必须创建一个请求处理程序的类,通过子类化BaseRequestHandlerclass和重写handle()方法,该方法将处理传入的请求。
2、其次,你必须实例化一个服务器的类,通过服务器的地址和请求处理程序类。
3、然后调用handle_request() 或者serve_forever服务器对象的()方法来处理一个或多个请求。
4、最后,调用server_close()关闭套接字。