📜  记录器:套接字开发日志 (1)

📅  最后修改于: 2023-12-03 15:41:43.701000             🧑  作者: Mango

记录器:套接字开发日志

简介

本篇日志将介绍如何使用Python套接字(Socket)来开发一个简单的记录器,实现将日志信息保存到远程主机中的功能。本记录器主要分为两个部分:客户端和服务端,分别负责将日志信息发送到服务端和将信息保存到本地磁盘。

开发环境
  • Python 3.8.5
客户端实现

客户端主要负责将日志信息发送到服务端。下面是客户端的实现代码:

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_allrun方法主要负责启动服务端Socket并监听来自客户端的连接请求。每当有新的连接请求时,我们就会创建一个新的线程来处理该连接,以避免阻塞主线程。在run方法中,我们首先调用socket.bind方法来绑定服务端的地址和端口,然后调用socket.listen方法开始监听连接请求。

当有新的连接请求时,我们使用socket.accept方法获取客户端的套接字和远程主机的地址。接下来,我们使用辅助方法receive_all从客户端套接字中读取数据。由于Socket是基于数据流的协议,因此我们不能保证每次读取的数据量总是等于指定的字节数,需要使用循环来读取整个数据流,直到读取到指定字节数的数据为止。

接着,我们使用struct模块解码之前打包的消息长度,并再次使用receive_all方法读取整个日志消息内容。最后,我们将日志信息写入到本地指定的日志文件中,并关闭该连接。需要注意的是,由于每个客户端连接对应一个线程,因此self.log_filef.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()方法来关闭套接字和等待服务端线程退出。