📅  最后修改于: 2023-12-03 15:41:43.701000             🧑  作者: Mango
本篇日志将介绍如何使用Python套接字(Socket)来开发一个简单的记录器,实现将日志信息保存到远程主机中的功能。本记录器主要分为两个部分:客户端和服务端,分别负责将日志信息发送到服务端和将信息保存到本地磁盘。
客户端主要负责将日志信息发送到服务端。下面是客户端的实现代码:
import logging
import socket
import struct
class SocketHandler(logging.Handler):
def __init__(self, host, port):
super().__init__()
self.host = host
self.port = port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def emit(self, record):
try:
msg = self.format(record)
msg = msg.encode('utf-8')
header = struct.pack('I', len(msg))
self.socket.connect((self.host, self.port))
self.socket.sendall(header + msg)
except Exception as e:
print("Error:", e)
def close(self):
self.socket.close()
super().close()
上述代码中,我们首先定义了一个SocketHandler
类,继承了Python的标准库logging中的Handler
类,用于将日志信息转换为指定格式的字符串。
在该类中,我们实现了两个方法:构造器__init__
和emit
。构造器主要负责初始化Socket连接,并且保存远程主机的地址和端口。而emit
方法则负责发送日志信息到远程主机。首先我们将日志信息转换为指定格式的字符串,然后将其编码为UTF-8格式。接下来我们使用struct模块打包消息长度,并将其与消息内容一并发送到远程主机中。
关闭连接时,我们需要显式地调用socket.close()
方法来关闭套接字。
服务端主要负责接收客户端发送过来的消息,并将其写入到本地的日志文件中。下面是服务端的实现代码:
import logging
import os
import socket
import struct
import threading
class SocketServer(threading.Thread):
def __init__(self, host, port, log_file):
super().__init__()
self.host = host
self.port = port
self.log_file = log_file
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def run(self):
self.socket.bind((self.host, self.port))
self.socket.listen(1)
while True:
conn, addr = self.socket.accept()
try:
header = self.receive_all(conn, 4)
msg_len = struct.unpack('I', header)[0]
msg = self.receive_all(conn, msg_len)
msg = msg.decode('utf-8')
with open(self.log_file, 'a') as f:
f.write(msg)
except Exception as e:
print("Error:", e)
finally:
conn.close()
def receive_all(self, conn, n):
data = b''
while len(data) < n:
packet = conn.recv(n - len(data))
if not packet:
return None
data += packet
return data
上述代码中,我们定义了一个SocketServer
类,继承了Python中的threading.Thread
类,用于创建一个服务端Socket。在该类中,我们同时实现了类的run
方法和辅助方法receive_all
。run
方法主要负责启动服务端Socket并监听来自客户端的连接请求。每当有新的连接请求时,我们就会创建一个新的线程来处理该连接,以避免阻塞主线程。在run
方法中,我们首先调用socket.bind
方法来绑定服务端的地址和端口,然后调用socket.listen
方法开始监听连接请求。
当有新的连接请求时,我们使用socket.accept
方法获取客户端的套接字和远程主机的地址。接下来,我们使用辅助方法receive_all
从客户端套接字中读取数据。由于Socket是基于数据流的协议,因此我们不能保证每次读取的数据量总是等于指定的字节数,需要使用循环来读取整个数据流,直到读取到指定字节数的数据为止。
接着,我们使用struct
模块解码之前打包的消息长度,并再次使用receive_all
方法读取整个日志消息内容。最后,我们将日志信息写入到本地指定的日志文件中,并关闭该连接。需要注意的是,由于每个客户端连接对应一个线程,因此self.log_file
和f.write(msg)
在不同的线程之间可以正确地共享资源。
最后,我们就可以将客户端和服务端连成一体来使用了。下面是使用的示例代码:
import logging
from socketserver import SocketServer
from socket_handler import SocketHandler
# 创建SocketServer实例
server = SocketServer("localhost", 12345, "app.log")
# 启动服务端
server.start()
# 创建Logger实例并添加SocketHandler
logger = logging.getLogger()
logger.setLevel(logging.INFO)
handler = SocketHandler("localhost", 12345)
logger.addHandler(handler)
# 记录日志
logger.info("Hello, world!")
# 关闭连接
handler.close()
server.join()
在上述代码中,我们首先创建了SocketServer
类的实例并启动服务端。接着,我们创建了Logger实例,并在其中添加了SocketHandler
。最后,我们可以像平常一样调用logger来记录日志。在关闭连接时,我们需要显式地调用handler.close()
和server.join()
方法来关闭套接字和等待服务端线程退出。