python 全双工 socket聊天

发布时间:2019-09-19 08:04:45编辑:auto阅读(1593)

    自学python一段时间,一直想弄个有意思的东西,所以就拿socket做一个聊天室,可以一对多,一对一全双工聊天。后续可能完善代码在鼓弄一个带gui界面的,比较有逼格j_0061.gif


    服务端:

    使用socketserver模块,多线程异步处理客户端消息,接受客户消息并转发 既服务端为一个中转站。

    加入了 登陆 注册 多人聊天 一对一聊天 防止同时在线


    客户端:

    主线程连接服务端,两个子线程分别负责读写


    sercer:
    
    # _*_ coding:utf-8 _*_
    import SocketServer
    from time import ctime
    import threading, traceback
    import Queue
    from db import DB
    
    lock = threading.Lock()
    local_school = threading.local()
    
    
    class Handler(object):
        queue = []
        db = DB()
        user_name = {}
    
        def __init__(self, sock):
            self.sock = sock
            self.input = [self.sock]
            # self.queue.append(self.sock)
    
        def recv(self):
            data = self.sock.recv(1024).strip()
            return data
    
        def send(self, data):
            self.sock.sendall(data)
    
        def stop(self):
            self.send('ByeBye')
            self.sock.close()
            self.queue.remove(self.sock)
            del self.user_name[local_school.user]
    
        def exit(self):
            self.sock.close()
            try:
                self.queue.remove(self.sock)
                del self.user_name[local_school.user]
            except ValueError:
                pass
    
        def broadcast(self, user, data):
            for sock in self.queue:
                sock.sendall('[%s] %s::%s' % (ctime(), user, data))
    
        def one(self, user_sock, user, data):
            self.user_name[user_sock].sendall('--------------\n%s\n[%s]::(%s)\n---------------' % (ctime(), user, data))
    
        def yiduiduo(self, user, data):
            time_data = ctime()
            for sock in [x for x in self.queue if x != self.sock]:
                sock.sendall('----------------\n%s\n[%s]::(%s)\n----------------' % (time_data, user, data))
    
        def handler(self):
            funcdict = {
                'login': self.login,
                'registered': self.registered,
            }
            try:  ###异常处理 当客户端异常断开连接或者正常断开连接:服务端处理异常
                self.sock.send('请选择::login/registered/exit')
                data = self.recv()
                if data == 'exit':
                    self.stop()
                    # self.send('exit')
                elif data in funcdict:
                    funcdict[data]()
                else:
                    self.handler()
            except:
                if self.queue:
                    self.exit()
    
                else:
                    pass
    
        def login(self):
            self.send('输入账号密码 格式:  user passwd  /server')
            data = self.recv()
            if data == 'server':
                self.send('选择 exit/handler')
                data = self.recv()
                if data == 'exit':
                    self.stop()
                elif data == 'handler':
                    self.handler()
                else:
                    self.login()
            user_data = data.split()
            if len(user_data) == 2:
                user = user_data[0]
                passwd = user_data[1]
                user_data = self.db.get_data() or {}
                data_scok = self.user_name.get(user)  # 检测该用户是否在别处登陆 存在则登陆中  获得登陆的sock
                if data_scok:
                    try:
                        data_scok.sendall('账号在别处登陆,被迫下线')
                        data_scok.close()
                        self.queue.remove(data_scok)
                        del self.user_name[local_school.user]
                    except:  ##异常处理  捕获此处所有异常不做处理
                        pass
                if user in user_data and user_data[user] == passwd:
                    local_school.user = user
                    self.send('欢迎加入聊天室')
                    self.queue.append(self.sock)
                    self.broadcast('systemctl', '[%s]加入聊天室\n' % user)
                    self.user_name[user] = self.sock  ##用户——sock 映射
                    self.send('选择:单(d)/多(s)')
                    data = self.recv()
                    if data == 's':
                        self.Ltian()
                    elif data == 'd':
                        self.one_to_one()
                    else:
                        self.send('错误\n')
                        self.handler()
    
                else:
                    self.send('账号或密码不正确!\n')
                    self.login()
    
            else:
                self.send('格式错误!\n')
                self.login()
    
        def registered(self):
            self.send('注册账号密码-格式 user passwd  /server')
            data = self.recv()
            if data == 'server':
                self.send('选择 exit/handler')
                data = self.recv()
                if data == 'exit':
                    self.stop()
                    self.send('exit')
                else:
                    self.handler()
            user_data = data.split()
            if len(user_data) == 2:
                user = user_data[0]
                passwd = user_data[1]
    
                db_data = self.db.get_data() or {}
                if user in db_data:
                    self.send('用户已注册!')
                    self.registered()
                else:
                    db_data[user] = passwd
                    local_school.user = user
                    lock.acquire()  # 添加线程锁,防止线程同时修改  数据文件
                    try:
                        self.db.put_data(db_data)
                    finally:
                        lock.release()
                    self.queue.append(self.sock)
                    self.broadcast('system', '[%s]加入聊天室\n' % user)
                    self.user_name[user] = self.sock
                    self.send('选择:单人聊天(d)/多人聊天(s)\n')
                    data = self.recv()
                    if data == 's':
                        self.Ltian()
                        print self.queue
                    elif data == 'd':
                        self.one_to_one()
                    else:
                        self.send('错误!\n')
                        self.handler()
            else:
                self.send('格式错误\n\n')
                self.registered()
    
        def Ltian(self, ):  # 多人聊天
            print self.queue
            print self.user_name
            self.send('kaishiliaotian')
            while True:
                data = self.recv()
                if data == 'exit':
                    print 'queue1 ::%s' % self.queue
                    self.stop()
                    # self.send('关闭++++++++')
    
                    print 'queue2 ::%s' % self.queue
                    break
                self.yiduiduo(local_school.user, data)  # 组播消息
    
        def one_to_one(self):
            self.send('选择对象:to:user')
            user_data = self.recv()[3:]
            if user_data == local_school.user:
                self.one_to_one()
            if user_data in self.db.get_data():
    
                if self.user_name.get(user_data) and self.user_name[user_data] in self.queue:
                    self.send('kaishiliaotian')
                    while True:
                        data = self.recv()
    
                        # if  data is None:
                        if data == 'server':
                            self.send('选择:exit/Ltian(s)')
                            data = self.recv()
                            if data == 'exit':
                                self.one(user_data, local_school.user, '已下线')
                                self.stop()
                                break
                            elif data == 's':
                                self.Ltian()
                        elif not data == '' and self.user_name.get(user_data):  # 判断 数据不为空 且用户状态在线否
                            self.one(user_data, local_school.user, data)
                else:
                    self.send('用户不在线')
                    self.one_to_one()
            else:
                self.send('用户不存在!\n')
                self.one_to_one()
    
    
    class MyServer(SocketServer.BaseRequestHandler):
        def handle(self):
            print self.client_address
            self.mysock = Handler(self.request)
            print self.mysock.queue
            self.mysock.handler()
    
    
    if __name__ == '__main__':
        host = '127.0.0.1'
        port = 9999
        addr = (host, port)
        server = SocketServer.ThreadingTCPServer(addr, MyServer)
        server.request_queue_size = 4399
        server.serve_forever()
    
        server.shutdown()
    
    
    client:
    
    # _*_ coding:utf-8 _*_
    from socket import *
    import threading
    threads=[]
    class Client_Handler(object):
        def __init__(self, ipadr='127.0.0.1', port=9999):
            self.sock = socket(AF_INET, SOCK_STREAM)
            self.sock.connect((ipadr, port))
            self.input=[self.sock]
            print self.input
        def send(self,data):
            self.sock.sendall(data)
    
        def recv(self):
            data = self.sock.recv(1024).strip()
            print data
            return  data
    
    
        def write(self):
            while True:
                try:
                    data=raw_input('>>>')
                    if data=='exit':
                        self.send('exit')
                        self.sock.close()
                        break
                    self.send(data)
                except socket.error: #加入异常处理  当服务端断开sock连接时跳出while循环
                    break
                except:
                    break
        def read(self):
            while  True:
                try:
                    self.recv()
                except socket.error:
                    break
                except:
                    break
    a1=Client_Handler()
    chat = threading.Thread(target=a1.write)
    threads.append(chat)
    chat = threading.Thread(target=a1.read)
    threads.append(chat)
    print threads
    for i in range(len(threads)):
        threads[i].start()


关键字