软件开发定制定制Python+Socket实现多人聊天室,功能:好友聊天、群聊、图片、表情、文件等

一、项目简介

软件开发定制定制本项目主要基于python软件开发定制定制实现的多人聊天室,软件开发定制定制主要的功能如下:

  • 登录注册
  • 添加好友
  • 软件开发定制定制与好友进行私聊
  • 创建群聊
  • 邀请/软件开发定制定制申请加入群聊
  • 聊天发送图片
  • 聊天发送表情
  • 聊天发送文件
  • 聊天记录保存在本地中
  • 聊天过程中发送的文件保存本地

二、环境介绍

  • python3.8
  • mysql8.0
  • tkinter:作为程序的gui库
  • flask :主要用于登录/注册、表情下载、信息修改等http请求等
  • socket:主要用户聊天过程中消息发送、对方在线状态更新等
  • pygame:用于播放新消息提示音

三、运行展示

登录:

注册:

登录后主界面:

点击右上方“修改资料”:

添加好友或群:

双击好友或群打开聊天窗口:

点击表情按钮选择发送的表情:

发送图片可以预览,点击文件名称直接打开:

四、关键代码

配置文件:server.conf

配置服务器ip、http端口、端口、数据库的账号密码、是否启用新消息提示音

[server]SERVER_IP = 127.0.0.1HTTP_PORT = 8000SOCKET_PORT = 8001SQLALCHEMY_DATABASE_URI = mysql://root:root@127.0.0.1:3306/chatdbENABLE_MUSIC = 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

服务端主要代码:ChatServer.py

维持Socket通信、开启Flask进行http

# controller定义@app.route('/login', methods=['POST'])def login():    try:        params = request.values        login_name = params['loginName']        pwd = params['pwd']        md5 = hashlib.md5()        md5.update(pwd.encode(encoding='utf-8'))        password = md5.hexdigest()        users = Users.query.filter(Users.loginName == login_name)\            .filter(Users.pwd == password).all()        if len(users) == 0:            return Result.fail('账号不存在或密码错误')        else:            # 服务返回uid,客户端打开好友界面后,凭借此uid与服务器进行socket连接            uid = users[0].id            # 已存在uid:已登录,重新登录,原登录退出连接,退出程序            if uid in online_users.keys():                # logout                connection = online_users[int(uid)]                send_msg = {'type': UtilsAndConfig.SYSTEM_LOGOUT}                connection.send(json.dumps(send_msg).encode())            online_users[uid] = None            return Result.success(uid)    except Exception as e:        return Result.fail('参数异常')# 监听socketdef socket_listen_thread():    while True:        connection, address = mySocket.accept()        # 用户连接携带的uid,判断是否和服务器相同        data_dic = json.loads(connection.recv(1024).decode())        uid = None        if data_dic['type'] == UtilsAndConfig.CONNECTION_REQUEST:            uid = int(data_dic['uid'])        else:            connection.send(UtilsAndConfig.CONNECTION_NOT_ALLOWED.encode())        if uid in online_users.keys():            # 可建立连接            online_users[uid] = connection            connection.send(UtilsAndConfig.CONNECTION_ALLOWED.encode())            # 通知好友们,我上线了            friends = get_friends_by_uid(uid)            for f in friends:                if f.id in online_users.keys():                    friend_connection = online_users[f.id]                    send_msg = {'type': UtilsAndConfig.FRIENDS_ONLINE_CHANGED, 'uid': uid, 'online': 1}                    friend_connection.send(json.dumps(send_msg).encode())            # 创建子线程,保持通信            keep_link_thread = threading.Thread(target=socket_keep_link_thread, args=(connection, ))            keep_link_thread.setDaemon(True)            keep_link_thread.start()        else:            connection.send(UtilsAndConfig.CONNECTION_NOT_ALLOWED.encode())def socket_keep_link_thread(connection):    while True:        try:            msg = connection.recv(1024).decode()            if not msg:                if connection in online_users.values():                    uid = list(online_users.keys())[list(online_users.values()).index(connection)]                    online_users.pop(uid)                    friends = get_friends_by_uid(uid)                    for f in friends:                        if f.id in online_users.keys():                            friend_connection = online_users[f.id]                            send_msg = {'type': UtilsAndConfig.FRIENDS_ONLINE_CHANGED, 'uid': uid, 'online': 0}                            friend_connection.send(json.dumps(send_msg).encode())                    connection.close()                return            else:                msg_json = json.loads(str(msg))                # 发消息                if msg_json['type'] == UtilsAndConfig.CHAT_SEND_MSG:                    to_id = msg_json['toId']                    is_friend = msg_json['isFriend']                    from_uid = msg_json['fromId']                    send_time = msg_json['sendTime']                    msg_text = msg_json['msgText']                    data = {'from_uid': from_uid, 'to_id': to_id, 'send_time': send_time, 'msg_text': msg_text,                            'is_friend': is_friend, 'type': '', 'msg_type': 'train'}                    # 通知接收方,收到新消息                    if is_friend == 1:                        if to_id in online_users.keys():                            friend_connection = online_users[to_id]                            data['type'] = UtilsAndConfig.CHAT_HAS_NEW_MSG                            friend_connection.send(json.dumps(data).encode())                            # 通知发送方,发送成功                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_SUCCESS                            connection.send(json.dumps(data).encode())                        else:                            # 通知发送方,发送失败,对方不在线                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_ERR                            connection.send(json.dumps(data).encode())                    else:                        # 群                        members = get_group_members(to_id)                        members_online = False                        for m in members:                            if m.uId in online_users.keys() and m.uId != from_uid:                                members_online = True                                member_connection = online_users[m.uId]                                data['type'] = UtilsAndConfig.CHAT_HAS_NEW_MSG                                member_connection.send(json.dumps(data).encode())                        if members_online:                            # 通知发送方,发送成功                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_SUCCESS                            connection.send(json.dumps(data).encode())                        else:                            # 通知发送方,发送失败,对方不在线                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_ERR                            connection.send(json.dumps(data).encode())                if msg_json['type'] == UtilsAndConfig.CHAT_SEND_FILE:                    from_id = msg_json['from_id']                    to_id = msg_json['to_id']                    is_friend = msg_json['is_friend']                    send_date = msg_json['send_date']                    file_length = msg_json['file_length']                    file_suffix = msg_json['file_suffix']                    file_name = msg_json['file_name']                    file_save_name = str(uuid.uuid1()) + '.' + file_suffix                    return_file_path = '/static/tmp/' + file_save_name                    file_path = os.path.abspath(os.path.dirname(__file__)) + return_file_path                    if not os.path.exists(os.path.dirname(file_path)):                        os.makedirs(os.path.dirname(file_path))                    data = {'from_uid': from_id, 'to_id': to_id, 'send_time': send_date, 'file_name': file_name,                            'is_friend': is_friend, 'type': UtilsAndConfig.CHAT_SEND_FILE_SUCCESS,                            'file_path': return_file_path}                    if is_friend == 1:                        if to_id not in online_users.keys():                            # 通知发送方,发送失败,对方不在线                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_ERR                            connection.send(json.dumps(data).encode())                            continue                    else:                        members = get_group_members(to_id)                        flag = True                        for m in members:                            if m.uId in online_users.keys() and m.uId != from_id:                                flag = False                                break                        if flag:                            # 通知发送方,发送失败,对方不在线                            data['type'] = UtilsAndConfig.CHAT_SEND_MSG_ERR                            connection.send(json.dumps(data).encode())                            continue                    # 接收文件                    total_data = b''                    file_data = connection.recv(1024)                    total_data += file_data                    num = len(file_data)                    while num < file_length:                        file_data = connection.recv(1024)                        num += len(file_data)                        total_data += file_data                    with open(file_path, "wb") as f:                        f.write(total_data)                    connection.send(json.dumps(data).encode())                    # 通知接收方,收到新文件消息                    if is_friend == 1:                        friend_connection = online_users[to_id]                        data['type'] = UtilsAndConfig.CHAT_HAS_NEW_FILE                        friend_connection.send(json.dumps(data).encode())                    else:                        members = get_group_members(to_id)                        for m in members:                            if m.uId in online_users.keys() and m.uId != from_id:                                member_connection = online_users[m.uId]                                data['type'] = UtilsAndConfig.CHAT_HAS_NEW_FILE                                member_connection.send(json.dumps(data).encode())        except ConnectionAbortedError:            if connection in online_users.values():                uid = list(online_users.keys())[list(online_users.values()).index(connection)]                online_users.pop(uid)                friends = get_friends_by_uid(uid)                for f in friends:                    if f.id in online_users.keys():                        friend_connection = online_users[f.id]                        send_msg = {'type': UtilsAndConfig.FRIENDS_ONLINE_CHANGED, 'uid': uid, 'online': 0}                        friend_connection.send(json.dumps(send_msg).encode())                connection.close()            return        except ConnectionResetError:            if connection in online_users.values():                uid = list(online_users.keys())[list(online_users.values()).index(connection)]                online_users.pop(uid)                friends = get_friends_by_uid(uid)                for f in friends:                    if f.id in online_users.keys():                        friend_connection = online_users[f.id]                        send_msg = {'type': UtilsAndConfig.FRIENDS_ONLINE_CHANGED, 'uid': uid, 'online': 0}                        friend_connection.send(json.dumps(send_msg).encode())                connection.close()            return# 主线程if __name__ == '__main__':    # 启动socket线程    socketThread = threading.Thread(target=socket_listen_thread)    socketThread.setDaemon(True)    socketThread.start()    # 启动Flask服务器    app.run(host=serverConfig.SERVER_IP, port=serverConfig.HTTP_PORT, debug=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225

客户端主界面:ChatHome.py

与服务器保持Socket通信、与服务端进行http交互

class ChatHome:    def run(self):        pygame.mixer.init()        # Socket连接        self.socket.connect((self.server_config.SERVER_IP, self.server_config.SOCKET_PORT))        send_data = {'type': UtilsAndConfig.CONNECTION_REQUEST, 'uid': self.uid}        self.socket.send(json.dumps(send_data).encode())        socket_result = self.socket.recv(1024).decode()        if socket_result != UtilsAndConfig.CONNECTION_ALLOWED:            tkinter.messagebox.showwarning('提示', '参数出错,socket连接被拒绝!')            sys.exit()        # 创建子线程保持socket通信        keep_link_thread = threading.Thread(target=self.socket_keep_link_thread)        keep_link_thread.setDaemon(True)        keep_link_thread.start()        # 基本信息        self.root = tk.Tk()        self.root.title('ChatRoom')        self.root.geometry('320x510+100+0')        # 用户名        self.frame_user_info = Frame(self.root, relief=RAISED, width=320, borderwidth=0, height=70, bg='#4F7DA4')        self.frame_user_info.place(x=0, y=0)        self.init_user_info()        # 中间画布canvas        self.frame_mid = Frame(self.root, width=320, height=340)        self.frame_mid.place(x=0, y=70)        # # 画布中的frame        self.init_friends_and_group_view()        # 下方按钮        frame_bottom_button = Frame(self.root, relief=RAISED, borderwidth=0, width=320, height=50)        frame_bottom_button.place(x=0, y=420)        button_bottom_add_friends = Button(frame_bottom_button, width=11,                                           text='加好友/加群', command=self.open_add_friends)        button_bottom_add_friends.place(x=55, y=10)        button_bottom_create_groups = Button(frame_bottom_button, width=11,                                             text='创建群', command=self.open_create_groups)        button_bottom_create_groups.place(x=165, y=10)        # 新消息        frame_message = Frame(self.root, relief=RAISED, borderwidth=0, width=320, height=50)        frame_message.place(x=0, y=460)        self.label_message_tip = Label(frame_message)        self.label_message_tip.place(x=55, y=12)        self.refresh_message_count()        button_message_open = Button(frame_message, width=7,                                     text='查看', command=self.open_message_window)        button_message_open.place(x=193, y=10)        self.root.mainloop()    # 保持socket通信    def socket_keep_link_thread(self):        while True:            try:                back_msg = self.socket.recv(1024).decode()                msg = json.loads(back_msg)                # 好友状态改变                if msg['type'] == UtilsAndConfig.FRIENDS_ONLINE_CHANGED:                    self.frames_friend_view[msg['uid']].online_type_change(msg['online'])                # 有新验证消息                if msg['type'] == UtilsAndConfig.MESSAGE_NEW_MSG:                    self.refresh_message_count()                    self.play_new_msg_music()                # 好友/群数量改变                if msg['type'] == UtilsAndConfig.FRIENDS_GROUPS_COUNT_CHANGED:                    self.init_friends_and_group_view()                    self.refresh_message_count()                # 有新文本消息, 写入缓存,更新显示                if msg['type'] == UtilsAndConfig.CHAT_HAS_NEW_MSG:                    from_uid = msg['from_uid']                    to_id = msg['to_id']                    is_friend = msg['is_friend']                    txt = {'type': 'get', 'from_uid': from_uid, 'datetime': msg['send_time'],                           'msg': msg['msg_text'], 'msg_type': 'train'}                    UtilsAndConfig.add_one_chat_record(self.uid, is_friend, from_uid, to_id,                                                       json.dumps(txt, cls=UtilsAndConfig.MyJSONEncoder,                                                                  ensure_ascii=False), False)                    # 是否打开聊天界面,打开则更新,未打开则好友列表提示新消息                    if self.window_chat_context is not None and self.window_chat_context.to_id == from_uid\                            and self.window_chat_context.is_friend == 1 and is_friend == 1:                        self.window_chat_context.get_new_msg()                        pass                    elif self.window_chat_context is not None and self.window_chat_context.to_id == to_id\                            and self.window_chat_context.is_friend == 0 and is_friend == 0:                        self.window_chat_context.get_new_msg()                    else:                        if is_friend == 1:                            self.frames_friend_view[from_uid].new_msg_comming()                        else:                            self.frames_group_view[to_id].new_msg_comming()                    self.play_new_msg_music()                # 发送文本消息成功, 写入本地缓存,更新显示                if msg['type'] == UtilsAndConfig.CHAT_SEND_MSG_SUCCESS:                    from_uid = msg['from_uid']                    to_id = msg['to_id']                    send_time = msg['send_time']                    msg_text = msg['msg_text']                    is_friend = msg['is_friend']                    txt = {'type': 'send', 'datetime': send_time, 'msg': msg_text, 'msg_type': 'train'}                    UtilsAndConfig.add_one_chat_record(self.uid, is_friend, from_uid, to_id,                                                       json.dumps(txt, cls=UtilsAndConfig.MyJSONEncoder,                                                                  ensure_ascii=False), True)                    self.window_chat_context.get_new_msg()                # 发送文件成功                if msg['type'] == UtilsAndConfig.CHAT_SEND_FILE_SUCCESS:                    to_id = msg['to_id']                    send_time = msg['send_time']                    file_name = msg['file_name']                    is_friend = msg['is_friend']                    txt = {'type': 'send', 'datetime': send_time, 'msg': file_name, 'msg_type': 'file'}                    UtilsAndConfig.add_one_chat_record(self.uid, is_friend, self.uid, to_id,                                                       json.dumps(txt, cls=UtilsAndConfig.MyJSONEncoder,                                                                  ensure_ascii=False), True)                    self.window_chat_context.get_new_msg()                    self.window_chat_context.sending_file(False)                # 收到文件                if msg['type'] == UtilsAndConfig.CHAT_HAS_NEW_FILE:                    to_id = msg['to_id']                    from_uid = msg['from_uid']                    send_time = msg['send_time']                    file_name = msg['file_name']                    is_friend = msg['is_friend']                    file_path = msg['file_path']                    files_dir = os.path.abspath(os.path.dirname(__file__)) + '/static/LocalCache/' \                                + str(self.uid) + '/files/'                    if not os.path.exists(os.path.dirname(files_dir)):                        os.makedirs(os.path.dirname(files_dir))                    all_file_name = file_name.split('/')[-1]                    file_suffix = all_file_name.split('.')[-1]                    end_index = len(all_file_name) - len(file_suffix) - 1                    file_name = all_file_name[0:end_index]                    file_save_path = files_dir + file_name + '.' + file_suffix                    i = 1                    while os.path.exists(file_save_path):                        file_save_path = files_dir + file_name + '(' + str(i) + ')' + '.' + file_suffix                        i += 1                    # http下载文件,保存到本地                    try:                        url = self.server_config.HTTP_SERVER_ADDRESS + file_path                        res = requests.get(url)                        file_content = res.content                        file = open(file_save_path, 'wb')                        file.write(file_content)                        file.close()                    except requests.exceptions.InvalidSchema:                        pass                        # 服务器中文件不存在                    txt = {'type': 'get', 'from_uid': from_uid, 'datetime': send_time,                           'msg': file_save_path, 'msg_type': 'file'}                    UtilsAndConfig.add_one_chat_record(self.uid, is_friend, from_uid, to_id,                                                       json.dumps(txt, cls=UtilsAndConfig.MyJSONEncoder,                                                                  ensure_ascii=False), False)                    if self.window_chat_context is not None and self.window_chat_context.to_id == from_uid\                            and self.window_chat_context.is_friend == 1 and is_friend == 1:                        self.window_chat_context.get_new_msg()                        pass                    elif self.window_chat_context is not None and self.window_chat_context.to_id == to_id\                            and self.window_chat_context.is_friend == 0 and is_friend == 0:                        self.window_chat_context.get_new_msg()                    else:                        if is_friend == 1:                            self.frames_friend_view[from_uid].new_msg_comming()                        else:                            self.frames_group_view[to_id].new_msg_comming()                    self.play_new_msg_music()                    # 告诉服务器 文件下载完成,可删除                    url = self.server_config.HTTP_SERVER_ADDRESS + '/downloadFileSuccess?path=' + file_path                    requests.get(url)                # 发送聊天消息失败,不写入缓存,提示对方已下线                if msg['type'] == UtilsAndConfig.CHAT_SEND_MSG_ERR:                    tkinter.messagebox.showwarning('提示', '对方已下线,不能发送消息')                # 服务器强制下线                if msg['type'] == UtilsAndConfig.SYSTEM_LOGOUT:                    self.socket.close()                    tkinter.messagebox.showwarning('提示', '此账号已在别处登录!')                    self.root.destroy()                    return            except ConnectionAbortedError:                tkinter.messagebox.showwarning('提示', '与服务器断开连接!')                self.root.destroy()                return            except ConnectionResetError:                tkinter.messagebox.showwarning('提示', '与服务器断开连接!')                self.root.destroy()                return
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191

五、私聊或评论告诉我,获取源码

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发