Python 多人聊天工具 ( 多线程 )

程序实现:
1、单或多客户端使用 telnet 登陆服务端 ( 可远程 ) 进行会话
2、服务端实现登陆、注册、退出功能
3、客户端发送的消息会被广播到已经登陆的其他用户界面
4、连接到服务端后,可以执行相应的程序指令

程序代码:https://coding.net/u/wangxiaoqiangs/p/pycode/git/tree/master/socket/GServer

GServer.py

#!/usr/bin/env python
# coding: utf-8
# author: Xiao Guaishou

import socket
from db import DB
from threading import currentThread, Thread


class HandlerThread(object):
    queue = [] # sockect 队列
    db = DB()

    def __init__(self, sock):
        self.sock = sock

    def recv(self):
        data = self.sock.recv(1024).strip() # 如果使用 while 接收数据时,会导致用户必须多敲一次回车键
        return data

    def send(self, data):
        self.sock.sendall('\n[System]: %s\n' % data)

    # 向队列中广播消息
    def broadcast(self, user, data):
        for sock in self.queue:
            sock.sendall('\n[%s]: %s\n' % (user, data))

    # 关闭客户端连接
    def stop(self):
        self.send('ByeBye!')
        self.sock.close()
        self.queue.remove(self.sock) # 关闭连接后,记得从队列中删除

    # 程序入口
    def handler(self):
        funcdict = {
                    'login': self.login,
                    'register': self.register
        }

        try:
            thname = currentThread().getName()
            print('[%s] Got connection from %s' % (thname, self.sock.getpeername())) # 该程序中所有 print 的数据,将全部使用 loging 模块代替

            self.send('请选择功能:login/register/exit')

            data = self.recv()
            if data == 'exit':
                self.stop() # 其实这里应该单独使用 self.sock.close() 来关闭连接,因为这时队列中并没有该连接,不过有了下面的捕获就没有问题了 ^_^
            elif data in funcdict:
                return funcdict.get(data)()
            else:
                self.handler()
        except: # 如果这里不捕获一下,就无法正常断开客户端连接
            pass

    # 处理用户登陆
    def login(self):
        self.send('Login... 请输入用户名密码,格式:User Password,输入 Server: 执行程序指令!')
        user_data = self.recv()

        # 程序内部指令
        if user_data == 'Server:':
            self.send('\n\tServer:use reged\t切换到注册页\n\tServer:exit\t\t退出系统')
            user_data = self.recv()
            if user_data == 'Server:use reged':
                self.register()
            elif user_data == 'Server:exit':
                self.stop()
            else:
                self.send('输入错误...')

        datalist = user_data.split()

        # 判断用户输入,格式是否正确
        if len(datalist) == 2:
            user = datalist[0]
            password = datalist[1]

            db_data = self.db.get_data() or {}

            if user in db_data and password == db_data.get(user):
                self.queue.append(self.sock) # 有权限登陆系统者,连接被加入到队列中
                self.send('欢迎加入聊天室,输入 Server: 获取功能方法!')
                self.broadcast('System', '[%s] 加入聊天室!' % user)
                self.chat_room(user)
            else:
                self.send('用户名、密码错误!')
                self.login()
        self.login()

    def register(self):
        self.send('Register... 请输入用户名密码,格式:User Password,输入 Server: 执行程序指令!')
        user_data = self.recv()

        if user_data == 'Server:':
            self.send('\n\tServer:use login\t切换到注册页\n\tServer:exit\t\t退出系统')
            user_data = self.recv()
            if user_data == 'Server:login':
                self.login()
            elif user_data == 'Server:exit':
                self.stop()
            else:
                self.send('输入错误...')

        datalist = user_data.split()

        if len(datalist) == 2:
            user = datalist[0]
            password = datalist[1]

            db_data = self.db.get_data() or {}

            if user in db_data:
                self.send('该用户名已被注册!')
                self.register()
            else:
                db_data[user] = password
                self.db.put_data(db_data)
                self.queue.append(self.sock)
                self.broadcast('System', '新用户 [%s] 加入聊天室!' % user)
                self.chat_room(user)
        self.register()

    def chat_room(self, user):
        user_data = self.recv()
        if user_data == 'Server:':
            self.send('\n\tServer:logout\t退出聊天室')
            user_data = self.recv()
            if user_data == 'Server:logout':
                self.stop()
                return # 这里如果不加 return ,会将客户端执行的 Server: 指令也广播出去
            else:
                self.send('输入错误...')
                self.chat_room(user)
        else:
            self.broadcast(user, user_data)
            self.chat_room(user)

# 为每连接创建线程
def Startthread(sock, addr):
    print('Received new client connection. %s:%s' % (addr[0], addr[1]))

    th = HandlerThread(sock)
    t = Thread(target=th.handler)
    t.setDaemon(True)
    t.start()

# 启动服务
def Server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('0.0.0.0', 17170))
    s.listen(1)

    while True:
        try:
            sock, addr = s.accept()
        except KeyboardInterrupt:
            exit('\nByeBye!')

        Startthread(sock, addr)

    s.close()

if __name__ == '__main__':
    Server()
    
db.py

# coding: utf-8

import json

# 创建一个类,代替数据库
class DB(object):
    def __init__(self, path='Storage.db'):
        self.path = path

    def get_data(self, data=None):
        try:
            with open(self.path) as f:
                data = json.load(f)
        except IOError as e:
            return data # 首次取数据时,由于文件不存在或没数据,将返回默认值 None
        finally:
            return data

    def put_data(self, data):
        with open(self.path, 'w') as f:
            json.dump(data, f)
posted @ 2016-06-30 16:25  WangXiaoQiang  阅读(6214)  评论(0编辑  收藏  举报