26-1 Socket 编程
socket
socket的英文原义是“插槽”或“插座”,就像我们家里座机一样,如果没有网线的那个插口,电话是无法通信的。Socket是实现TCP,UDP协议的接口,便于使用TCP,UDP。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
网络通信三要素:IP地址、端口、协议
一、socket通信流程

流程描述:
- 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
- 服务器为socket绑定ip地址和端口号
- 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
- 客户端创建socket
- 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
- 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求
- 客户端连接成功,向服务器发送连接状态信息
- 服务器accept方法返回,连接成功
- 客户端向socket写入信息(或服务端向socket写入信息)
- 服务器读取信息(客户端读取信息)
- 客户端关闭
- 服务器端关闭
二、参数及方法
import socket sk = socket.socket()
1. 参数说明
- family:AF_INET表示ipv4,AF_INET6表示ipv6,AF_UNIX表示UNIX不同进程之间通信
- type:SOCK_STREAM表示用TCP协议,SOC_Dgram表示用UDP协议
2. 常用方法:
sk.bind(address) # s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 sk.listen(5) # 开始监听传入连接。在拒绝连接之前,可以挂起的最大连接数量为5。这个值不能无限大,因为要在内核中维护连接队列。 sk.setblocking(bool) # 是否阻塞(默认True),如果设置False,那么accept和recv有数据就接收,无数据时则报错。 sk.accept() # 阻塞式等待TCP连接的到来。若连接返回(conn,address),其中conn是新的套接字对象,可用来接收和发送数据,address是连接客户端的地址。 sk.connect(address) # 连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 sk.connect_ex(address) # 同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061 sk.close() # 关闭套接字。 sk.recv(bufsize[,flag]) # 接受套接字的数据。数据以bytes形式返回,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() # 套接字的文件描述符。
三、实例
#################server import socket ip_port = ('127.0.0.1',9997) sk = socket.socket() sk.bind(ip_port) sk.listen(5) print('server waiting...') conn, addr = sk.accept() client_data = conn.recv(1024) print(str(client_data,"utf8")) conn.sendall(bytes('滚蛋!',encoding="utf-8")) sk.close() ################client import socket ip_port = ('127.0.0.1',9997) sk = socket.socket() sk.connect(ip_port) sk.sendall(bytes('俺喜欢你',encoding="utf8")) # 发送必须为 bytes数据 server_reply = sk.recv(1024) print(str(server_reply,"utf8"))
#################server import socket ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(2) print("服务端启动...") conn,address = sk.accept() while True: client_data=conn.recv(1024) if str(client_data,"utf8")=='exit': # 这里条件也可设置为是否接受到了个空值。 break print (str(client_data,"utf8")) server_response=input(">>>") conn.sendall(bytes(server_response,"utf8")) conn.close() ################client import socket ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.connect(ip_port) print("客户端启动:") while True: inp = input('>>>') sk.sendall(bytes(inp,"utf8")) if inp == 'exit': break server_response=sk.recv(1024) print (str(server_response,"utf8")) sk.close() # 此句一执行,是给服务端发了个空值,同时也告诉服务端这边关闭了。 # 服务端会从recv阻塞继续往下走一步。自己输入的空值不允许发。
#虽然不是并发,但是可以在不重新开启server的前提下与多人聊天 ##################server import socket ip_port = ('127.0.0.1',8870) sk = socket.socket() sk.bind(ip_port) sk.listen(2) print ("服务端启动...") while True: conn,address = sk.accept() print(address) while True: try: # 如果等待收client信息时,client强制关闭连接,win服务端会报错 client_data=conn.recv(1024) except: print("意外中断") break print (str(client_data,"utf8")) server_response=input(">>>") conn.sendall(bytes(server_response,"utf8")) conn.close() ##################client import socket ip_port = ('127.0.0.1',8870) sk = socket.socket() sk.connect(ip_port) print ("客户端启动:") while True: inp = input('>>>') if inp == 'exit': break sk.sendall(bytes(inp,"utf8")) server_response=sk.recv(1024) print (str(server_response,"utf8")) sk.close()
#############################server import socket, subprocess sk = socket.socket() ip_port = ('127.0.0.1', 8001) sk.bind(ip_port) sk.listen(3) print('waiting...') while True: conn, address = sk.accept() while True: try: data = str(conn.recv(1024), 'utf8').strip() except Exception: break if data == '': break print(data) obj = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE) result = obj.stdout.read() # a.stdout.read()是bytes类型 result_len = bytes(str(len(result)), 'utf8') conn.send(result_len) # 若两个send紧挨着且第一个包很小时,可能会有粘包现象 conn.recv(1024) # 阻塞一下,避免粘包 conn.send(result) # 一次都发过去了 conn.close() sk.close() ########################client import socket sk = socket.socket() sk.connect(('127.0.0.1', 8001)) while True: inp = input('>>>:') if inp == 'exit': break elif inp == '': print('很抱歉!不能发送空') else: sk.send(bytes(inp, 'utf8')) recv_len = int(str(sk.recv(1024), 'utf8')) sk.send(bytes('收到数量了,开始发数据', 'utf8')) result = bytes() while len(result) != recv_len: r = sk.recv(1024) result += r print(str(result, 'gbk')) sk.close()
################server import socket, os sk = socket.socket() ip_port = ('192.168.3.33', 8000) sk.bind(ip_port) sk.listen(3) conn, addr = sk.accept() path_dir = 'C:\pictures' data = str(conn.recv(1024), 'utf8') cmd,file_name,file_size = data.split('|') path = os.path.join(path_dir, file_name) f = open(path, 'ab') has_received = 0 while has_received != int(file_size): data = conn.recv(1024) f.write(data) has_received += len(data) f.close() #################client import socket, os sk = socket.socket() address = ('192.168.3.33', 8000) sk.connect(address) path = r'F:\产品照片\123.mp3' file_name = os.path.basename(path) file_size = os.stat(path).st_size info = 'upload|%s|%s' % (file_name, file_size) sk.send(bytes(info, 'utf8')) f = open(path, 'rb') has_sent = 0 while has_sent != file_size: seq = f.read(1024) sk.send(seq) has_sent += len(seq) sk.close()
socketserver模块
#-----------------------------------------------------server.py #----------------------------------------------------- import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print ("服务端启动...") while True: conn = self.request print (self.client_address) while True: client_data=conn.recv(1024) print (str(client_data,"utf8")) print ("waiting...") conn.sendall(client_data) conn.close() if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8091), MyServer) # 建立server对象。内部功能是建立socket、bind、listen server.serve_forever() # 最后转到执行自己定义的handle,即业务逻辑 #-----------------------------------------------------client.py #----------------------------------------------------- import socket ip_port = ('127.0.0.1',8091) sk = socket.socket() sk.connect(ip_port) print("客户端启动:") while True: inp = input('>>>') sk.sendall(bytes(inp,"utf8")) if inp == 'exit': break server_response=sk.recv(1024) print(str(server_response,"utf8")) sk.close()
虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也是Python标准库中很多服务器框架的基础。
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。
一、Server类
它包含了种五种server类,BaseServer(不直接对外服务)。TCPServer使用TCP协议,UDPServer使用UDP协议,还有两个不常使用的,即UnixStreamServer和UnixDatagramServer,这两个类仅仅在unix环境下有用(AF_unix)。

1. class BaseServer
Base class for server classes.
2. class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server.
3. class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer
4. class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)
These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer.
二、RequestHandler类
所有requestHandler都继承BaseRequestHandler基类。
创建一个socketserver 至少分以下几步:
- First, you must create a request handler class by subclassing the
BaseRequestHandlerclass and overriding itshandle()method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()orserve_forever()method of the server object to process one or many requests. - Finally, call
server_close()to close the socket.
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): """ The request handler class for our server. It is instantiated once per connection to the server, and must override the handle() method to implement communication to the client. """ def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 9999 # Create the server, binding to localhost on port 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C server.serve_forever()
让你的socketserver并发起来, 必须选择使用以下一个多并发的类:
- class socketserver.ForkingTCPServer
- class socketserver.ForkingUDPServer
- class socketserver.ThreadingTCPServer
- class socketserver.ThreadingUDPServer
所以:
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) #替换为 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
浙公网安备 33010602011771号