Python 最简单的我的第一个聊天室QQ软件【基于Socket服务】

文章目录

  • 什么是Socket
  • Python for socket
  • 最简单程序通讯
  • 基本函数介绍
  • MyChat
    • 原理解释
      • 服务端
        • 原理
        • 代码
      • 客户端
        • 原理
        • 代码
    • 使用介绍
  • 全局代码

什么是Socket

所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口

Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API(应用程序编程接口),也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制

一般Socket服务分为三个步骤
1.服务器监听
所谓服务器监听,是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态
2.客户端请求
所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端接字提出连接请求 。
3.连接确认
所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,就会响应客户端套接字的请求,建立一个新的线程,并把服务器端套接字的描述发送给客户端。一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,接收其他客户端套接字的连接请求。

Python for socket

import socket

最简单程序通讯

server

import socket
port = 1314
server = socket.socket()
server.bind(("0.0.0.0", port))
server.listen(10)
connect, address = self.__socket.accept()
print(f"连接方ip为:{address}")
connect.send(byte("Hello World", encoding="utf-8"))
connect.close()
server.close()

client

import socket
client = socket.socket()
client.connect(("127.0.0.1", 1314))
get = str(client.recv(1024), encoding="utf-8")  # 缓存区为1kb
print(get)
client.close()

基本函数介绍

server端函数

函数名 参数 返回值
bind (ip,port) None
accept None socket
listen int None

client端函数

函数名 参数 返回值
connect (ip,port) None

common函数

函数名 参数 返回值
send bytes None
recv int bytes
close None None

对于服务端,bind的时候如果ip为’0.0.0.0’,说明绑定ip为全绑定,无论是连接内外网ip,都可以连接。而连接connect的时候,如果服务端在公网,就连接公网ip,port需要选择一个未被占用的port

MyChat

原理解释

服务端

原理


服务端做消息转发,服务端架设到公网服务器上,内网客户端连接公网服务器后实现转发消息来通讯,这种模式对于少量的连接很方便,但是连接人数多了就会出现拥挤等错误


开始
初始化socket
绑定ip和端口bind
设置listen
启动循环线程A
是否关闭
退出不再监听新的连接
监听新的连接
将新的连接加入list
启动专属循环线程B
是否停止
退出
接收数据
移入转发函数

代码

server.py

class SocketServer:
    def __init__(self, logger: Log):
        self.__logger = logger
        self.__socket = socket.socket()
        self.__isrun = False
        self.__connects = []
        self.__max = 0

    def loginfo(self, msg):
        print("|INFO|\t" + str(msg))
        self.__logger.logmsg(msg)

    def createmsg(self, sender, receiver, action, **data) -> str:
        msg = {
            "code": 200,
            "sender": sender,
            "receiver": receiver,
            "action": action,
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": data}
        return json.dumps(msg)

    def _logobj(self, data: dict):
        action = data["action"]
        if action == "msg":
            msg = "收到{}发往{}的信息:{}".format(data["sender"], data["receiver"], data["data"]["msg"])
            self.loginfo(msg)
        if action == "close":
            msg = "用户{}关闭了连接".format(data["sender"])
            self.loginfo(msg)
        if action == "live":
            msg = "用户{}查询了当前在线人数:{}".format(data["sender"], len(self.__connects))
            self.loginfo(msg)

    def _forward(self, data: dict):
        try:
            goal = data["receiver"]
            # 判断是否为服务器消息
            if goal == "host":
                return

            # 判断是否为群发消息
            elif goal == "all":
                print("|INFO|\t开始转发群消息")
                self.__logger.logmsg("开始转发群消息")
                for i in self.__connects:
                    i[1].send(bytes(json.dumps(data), encoding=BufferEncoding))
                return

            # 监测目标是否上线
            else:
                for i in self.__connects:
                    if goal in i:
                        # 找到目标
                        receiver: socket.socket = i[1]
                        # 转发消息
                        receiver.send(bytes(json.dumps(data), encoding=BufferEncoding))
                        return
                # 未检测到
                self.loginfo("目标未上线")


        except Exception as e:

            self.loginfo(str(e))

    def _action(self, index, receiver: socket.socket, address, data: dict):

        action = data["action"]

        if action == "msg":
            self._forward(data)
            return 0

        if action == "close":
            self.__connects.remove((index, receiver, address))
            receiver.close()
            return -1

        if action == "live":
            live = []
            for i in self.__connects:
                live.append(i[0])

            msg = self.createmsg("host", data["sender"], "live", count=len(live), index=live)
            receiver.send(bytes(msg, encoding=BufferEncoding))
            return 0

    def _getmessage(self, index, receiver: socket.socket, address):
        try:
            while self.__isrun:
                data = json.loads(str(receiver.recv(BufferSize), encoding=BufferEncoding))  # 4KB缓存

                ###LOG - Start 服务器Log记录
                self._logobj(data)
                ###End

                ###Deal - Start 消息处理
                result = self._action(index, receiver, address, data)
                if result == -1:
                    return
                if result == 0:
                    continue
                ###End

        except Exception as e:

            self.loginfo(str(e))

        finally:
            self.loginfo(f"{index}的消息接收线程已退出")

    def _listen(self):

        self.loginfo("成功启动套接字监听")

        while self.__isrun:

            if len(self.__connects) == self.__max:
                break  # 达到最大连接数量断开监听

            connect, address = self.__socket.accept()  # 获取到连接

            # 生成标识符
            index = ""
            for i in range(5):
                index += str(random.randint(0, 9))

            self.loginfo("接受到:{}的连接,分配的ID:{}".format(address, index))

            self.__connects.append((index, connect, address))  # 挂起连接并加入库

            msg = self.createmsg("host", index, "msg", index=index, msg="连接成功")
            connect.send(bytes(msg, encoding="utf-8"))  # 发生返回信息

            _thread.start_new_thread(self._getmessage, (index, connect, address,))  # 打开信息监听进程

            continue

        self.loginfo("监听线程已退出")

    def startlisten(self, port, ip="0.0.0.0", count=10):
        self.__socket.bind((ip, port))
        self.__socket.listen(count)
        self.__isrun = True
        self.__max = count
        self.loginfo("监听初始化完毕,正在启动")
        self.loginfo(f"开启端口:{port},支持最大连接数量:{count}")
        _thread.start_new_thread(self._listen, ())

    def send(self, address, msg):
        if not self.__isrun:
            return

        # 监测目标是否上线
        for i in self.__connects:
            if address in i:
                msg = self.createmsg("host", address, "msg", msg=msg)
                i[1].send(bytes(msg, encoding=BufferEncoding))
                self.loginfo("发生成功")
                return
        self.loginfo("目标不在线")

    def sendall(self, msg):
        if not self.__isrun:
            return

        for i in self.__connects:
            msg = self.createmsg("host", i[0], "msg", msg=msg)
            i[1].send(bytes(msg, encoding=BufferEncoding))
        self.loginfo("发送成功")
        return

    def close(self):
        self.__isrun = False
        # 释放log对象
        self.__logger.close()
        # 等待未完成的任务
        time.sleep(5)
        # 释放所有套接字
        self.__socket.close()
        for i in self.__connects:
            i[1].close()


log.py

FileHeader = '''
Socket Log
StartTime:{}
----------------------------------------------------------------
'''

FileTail = '''
----------------------------------------------------------------
EndTime:{}
Count:{}
'''

InfoTemplate = "|INFO|\t[{}]{}"


class Log:
    def __init__(self, path):
        self.__path = "{}\\{}.log".format(path, time.strftime("%Y%m%d%H%M%S", time.localtime()))
        self.__lock = threading.Lock()
        self.__counter = 0

    def _writer(self, message):
        self.__lock.acquire()
        self.__counter += 1
        stream = open(self.__path, "a+")
        stream.write(message + "\n")
        stream.close()
        self.__lock.release()

    def open(self):
        self._writer(FileHeader.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime())))

    def close(self):
        self._writer(FileTail.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), self.__counter - 1))

    def logmsg(self, msg):
        data = InfoTemplate.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), msg)
        self._writer(data)

    def logobj(self, obj):
        data = InfoTemplate.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), json.loads(obj))
        self._writer(data)

main.py

import json
import _thread
import random
import socket
import threading
import time

BufferSize = 4096  # 最大缓存4KB
BufferEncoding = "utf-8"  # 缓存编码

reader = open("C:\\0_data\\config.json", "r")
data = json.loads(reader.read())
reader.close()
log = Log(data["path"])
log.open()
server = SocketServer(log)
server.startlisten(data["port"])
try:
    while True:

        get = input()

        if "Close" in get:
            server.close()
            print("|INFO|\t已关闭所有进程")
            break

        if "Send" in get:
            data = get.split("/")
            server.send(data[1], data[2])

        else:
            print("|INFO|\t键盘输出:", get)

except Exception as e:
    server.close()

finally:
    print("|INFO|\t程序已退出")

config.json

{
  "path": "C:\\0_data",
  "port": 1234
}

客户端

原理


与服务端相似,只不过仅仅只有一个获取数据的线程


代码

client.py

class SocketClient:
    def __init__(self):
        self.__socket = socket.socket()
        self.__isrun = False
        self.__index = -1

    def close(self):
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": "host",
            "action": "close",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
            }}
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        self.__isrun = False
        time.sleep(5)
        self.__socket.close()

    def send(self, address, msg):
        if not self.__isrun: return
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": address,
            "action": "msg",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
                "msg": msg
            }}  # 构造返回字符串
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        print("发送成功")

    def checklive(self):
        if not self.__isrun: return
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": "host",
            "action": "live",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
            }}  # 构造返回字符串
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        print("发送成功")

    def _recive(self):
        try:
            while self.__isrun:
                data = json.loads(str(self.__socket.recv(BufferSize), encoding="utf-8"))
                if data["action"] == "live":
                    print("目前在线人数为", data["data"]["count"])
                    for i in data["data"]["index"]:
                        print(i)
                    continue
                msg = "[{}]{}:{}".format(data["time"], data["sender"], data["data"]["msg"])
                print(msg)
            # 退出消息
            print("已退出消息接收")

        except Exception as e:
            print("接收错误", str(e))

    def startconnect(self, ip, port):
        self.__isrun = True
        self.__socket.connect((ip, port))
        # 连接成功
        # 获取序列号
        data = json.loads(str(self.__socket.recv(BufferSize), encoding="utf-8"))
        self.__index = data["data"]["index"]
        print("连接成功,您的序列号为:", self.__index)
        # 开启消息监听线程
        _thread.start_new_thread(self._recive, ())

main.py

import _thread
import json
import socket
import time

BufferSize = 4096
client = SocketClient()
client.startconnect("127.0.0.1", 3414)

while True:
    get = input()
    if get == "Close":
        client.close()
        break
    if "Send" in get:
        data = get.split("/")
        client.send(data[1], data[2])

    if "Check" in get:
        client.checklive()

print("程序已结束")

使用介绍

log类会在服务端处理时记录下所有的信息,在客户端连接后,服务端会生成一个index作为标识符保存在服务器,并且返回一个json字符串到客户端告知index,而客户端想要发送消息的时候,只需要向服务器说明对方的index即可,比如
你的index:12345,对方的index:96354,在控制台里面输入
Send/96354/Hello World
然后程序会创建一个json字符串并发送到服务端

{
	"code": 200,
    "sender": "12345",
   	"receiver": "96354",
    "action": "msg",
  	"time": "2022/4/8 11:09:23",
    "data": 
    {
     	"msg": "Hello World"
    }
}  # 构造返回字符串

然后服务端会先判断消息的action标签,如果是msg标签,那么服务器会调用_forward函数,首先检测receiver的标识符是否存在,然后找到对应的socket,然后使用该socket把这条msg发送过去
如果你想要查询有哪儿些其他用户,可以在控制台输入
Check
然后程序会创建一个json字符串,不同于上面的,该字符串的actionlive

{
	"code": 200,
    "sender": "12345",
   	"receiver": "host",
    "action": "live",
  	"time": "2022/4/8 11:09:23",
    "data": 
    {
     	
    }
}

当服务器检测到action不为msg的json串,会调用_action函数进行检测然后返回数据
而服务端和客户端关闭的时候也仅需要输入
Close
服务端输入会释放掉所有连接的socket,而客户端会先构造一个actionclose的json串传到服务器,服务器会释放掉对于的socket,然后客户端才会释放socket

全局代码

server.py

import json
import _thread
import random
import socket
import threading
import time

BufferSize = 4096  # 最大缓存4KB
BufferEncoding = "utf-8"  # 缓存编码
FileHeader = '''
Socket Log
StartTime:{}
----------------------------------------------------------------
'''

FileTail = '''
----------------------------------------------------------------
EndTime:{}
Count:{}
'''

InfoTemplate = "|INFO|\t[{}]{}"


class Log:
    def __init__(self, path):
        self.__path = "{}\\{}.log".format(path, time.strftime("%Y%m%d%H%M%S", time.localtime()))
        self.__lock = threading.Lock()
        self.__counter = 0

    def _writer(self, message):
        self.__lock.acquire()
        self.__counter += 1
        stream = open(self.__path, "a+")
        stream.write(message + "\n")
        stream.close()
        self.__lock.release()

    def open(self):
        self._writer(FileHeader.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime())))

    def close(self):
        self._writer(FileTail.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), self.__counter - 1))

    def logmsg(self, msg):
        data = InfoTemplate.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), msg)
        self._writer(data)

    def logobj(self, obj):
        data = InfoTemplate.format(time.strftime("%Y/%m/%d/ %H:%M:%S", time.localtime()), json.loads(obj))
        self._writer(data)


class SocketServer:
    def __init__(self, logger: Log):
        self.__logger = logger
        self.__socket = socket.socket()
        self.__isrun = False
        self.__connects = []
        self.__max = 0

    def loginfo(self, msg):
        print("|INFO|\t" + str(msg))
        self.__logger.logmsg(msg)

    def createmsg(self, sender, receiver, action, **data) -> str:
        msg = {
            "code": 200,
            "sender": sender,
            "receiver": receiver,
            "action": action,
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": data}
        return json.dumps(msg)

    def _logobj(self, data: dict):
        action = data["action"]
        if action == "msg":
            msg = "收到{}发往{}的信息:{}".format(data["sender"], data["receiver"], data["data"]["msg"])
            self.loginfo(msg)
        if action == "close":
            msg = "用户{}关闭了连接".format(data["sender"])
            self.loginfo(msg)
        if action == "live":
            msg = "用户{}查询了当前在线人数:{}".format(data["sender"], len(self.__connects))
            self.loginfo(msg)

    def _forward(self, data: dict):
        try:
            goal = data["receiver"]
            # 判断是否为服务器消息
            if goal == "host":
                return

            # 判断是否为群发消息
            elif goal == "all":
                print("|INFO|\t开始转发群消息")
                self.__logger.logmsg("开始转发群消息")
                for i in self.__connects:
                    i[1].send(bytes(json.dumps(data), encoding=BufferEncoding))
                return

            # 监测目标是否上线
            else:
                for i in self.__connects:
                    if goal in i:
                        # 找到目标
                        receiver: socket.socket = i[1]
                        # 转发消息
                        receiver.send(bytes(json.dumps(data), encoding=BufferEncoding))
                        return
                # 未检测到
                self.loginfo("目标未上线")


        except Exception as e:

            self.loginfo(str(e))

    def _action(self, index, receiver: socket.socket, address, data: dict):

        action = data["action"]

        if action == "msg":
            self._forward(data)
            return 0

        if action == "close":
            self.__connects.remove((index, receiver, address))
            receiver.close()
            return -1

        if action == "live":
            live = []
            for i in self.__connects:
                live.append(i[0])

            msg = self.createmsg("host", data["sender"], "live", count=len(live), index=live)
            receiver.send(bytes(msg, encoding=BufferEncoding))
            return 0

    def _getmessage(self, index, receiver: socket.socket, address):
        try:
            while self.__isrun:
                data = json.loads(str(receiver.recv(BufferSize), encoding=BufferEncoding))  # 4KB缓存

                ###LOG - Start 服务器Log记录
                self._logobj(data)
                ###End

                ###Deal - Start 消息处理
                result = self._action(index, receiver, address, data)
                if result == -1:
                    return
                if result == 0:
                    continue
                ###End

        except Exception as e:

            self.loginfo(str(e))

        finally:
            self.loginfo(f"{index}的消息接收线程已退出")

    def _listen(self):

        self.loginfo("成功启动套接字监听")

        while self.__isrun:

            if len(self.__connects) == self.__max:
                break  # 达到最大连接数量断开监听

            connect, address = self.__socket.accept()  # 获取到连接

            # 生成标识符
            index = ""
            for i in range(5):
                index += str(random.randint(0, 9))

            self.loginfo("接受到:{}的连接,分配的ID:{}".format(address, index))

            self.__connects.append((index, connect, address))  # 挂起连接并加入库

            msg = self.createmsg("host", index, "msg", index=index, msg="连接成功")
            connect.send(bytes(msg, encoding="utf-8"))  # 发生返回信息

            _thread.start_new_thread(self._getmessage, (index, connect, address,))  # 打开信息监听进程

            continue

        self.loginfo("监听线程已退出")

    def startlisten(self, port, ip="0.0.0.0", count=10):
        self.__socket.bind((ip, port))
        self.__socket.listen(count)
        self.__isrun = True
        self.__max = count
        self.loginfo("监听初始化完毕,正在启动")
        self.loginfo(f"开启端口:{port},支持最大连接数量:{count}")
        _thread.start_new_thread(self._listen, ())

    def send(self, address, msg):
        if not self.__isrun:
            return

        # 监测目标是否上线
        for i in self.__connects:
            if address in i:
                msg = self.createmsg("host", address, "msg", msg=msg)
                i[1].send(bytes(msg, encoding=BufferEncoding))
                self.loginfo("发生成功")
                return
        self.loginfo("目标不在线")

    def sendall(self, msg):
        if not self.__isrun:
            return

        for i in self.__connects:
            msg = self.createmsg("host", i[0], "msg", msg=msg)
            i[1].send(bytes(msg, encoding=BufferEncoding))
        self.loginfo("发送成功")
        return

    def close(self):
        self.__isrun = False
        # 释放log对象
        self.__logger.close()
        # 等待未完成的任务
        time.sleep(5)
        # 释放所有套接字
        self.__socket.close()
        for i in self.__connects:
            i[1].close()


reader = open("C:\\0_data\\config.json", "r")
data = json.loads(reader.read())
reader.close()
log = Log(data["path"])
log.open()
server = SocketServer(log)
server.startlisten(data["port"])
try:
    while True:

        get = input()

        if "Close" in get:
            server.close()
            print("|INFO|\t已关闭所有进程")
            break

        if "Send" in get:
            data = get.split("/")
            server.send(data[1], data[2])

        else:
            print("|INFO|\t键盘输出:", get)

except Exception as e:
    server.close()

finally:
    print("|INFO|\t程序已退出")

client.py

import _thread
import json
import socket
import time

BufferSize = 4096


class SocketClient:
    def __init__(self):
        self.__socket = socket.socket()
        self.__isrun = False
        self.__index = -1

    def close(self):
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": "host",
            "action": "close",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
            }}
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        self.__isrun = False
        time.sleep(5)
        self.__socket.close()

    def send(self, address, msg):
        if not self.__isrun: return
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": address,
            "action": "msg",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
                "msg": msg
            }}  # 构造返回字符串
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        print("发送成功")

    def checklive(self):
        if not self.__isrun: return
        msg = {
            "code": 200,
            "sender": self.__index,
            "receiver": "host",
            "action": "live",
            "time": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()),
            "data": {
            }}  # 构造返回字符串
        self.__socket.send(bytes(json.dumps(msg), encoding="utf-8"))
        print("发送成功")

    def _recive(self):
        try:
            while self.__isrun:
                data = json.loads(str(self.__socket.recv(BufferSize), encoding="utf-8"))
                if data["action"] == "live":
                    print("目前在线人数为", data["data"]["count"])
                    for i in data["data"]["index"]:
                        print(i)
                    continue
                msg = "[{}]{}:{}".format(data["time"], data["sender"], data["data"]["msg"])
                print(msg)
            # 退出消息
            print("已退出消息接收")

        except Exception as e:
            print("接收错误", str(e))

    def startconnect(self, ip, port):
        self.__isrun = True
        self.__socket.connect((ip, port))
        # 连接成功
        # 获取序列号
        data = json.loads(str(self.__socket.recv(BufferSize), encoding="utf-8"))
        self.__index = data["data"]["index"]
        print("连接成功,您的序列号为:", self.__index)
        # 开启消息监听线程
        _thread.start_new_thread(self._recive, ())


client = SocketClient()
client.startconnect("127.0.0.1", 3414)

while True:
    get = input()
    if get == "Close":
        client.close()
        break
    if "Send" in get:
        data = get.split("/")
        client.send(data[1], data[2])

    if "Check" in get:
        client.checklive()

print("程序已结束")

你可能感兴趣的:(学习日志,python,后端)