suifeng_king 发表于 2019-9-17 19:34

【Python】利用socket做的一个类似早期QQ的聊天程序

完整程序还未完成,有什么问题或者建议欢迎大家提出来:loveliness:

登录窗口:



注册窗口:



聊天界面(直接点击X关闭程序接收线程不会退出):

双击左侧用户列表的用户名开始一对一发送消息。




**程序运行:**

1. 首先运行服务端servers.py;
2. 然后运行login_GUI.py启动登录界面。

**数据库结构:**
由于当前功能还不完整,只有一个用户表和消息表
用户表(database_usersprofile):
```
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(65) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`nickname` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`date_joined` datetime(6) NOT NULL,
`last_login` datetime(6) NULL DEFAULT NULL,
`last_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
```
消息表(messages):

```
`id` int(11) NOT NULL AUTO_INCREMENT,
`send_to` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`from_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`status` tinyint(1) NOT NULL,
`send_time` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`send_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
```

**客户端client.py的代码:**
```
# -*- coding: utf-8 -*-
# @file    : client.py
# @Software: PyCharm

import socket
import threading
import json
import time


class SFChatClient(object):
    def __init__(self):
      # 创建TCP套接字
      self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      # 链接服务器
      server_ip = '192.168.72.1'
      server_port = 8888
      server_addr = (server_ip, server_port)
      self.socket.connect(server_addr)

      self.username = ''
      self.is_login = False
      self.online_users_list = list()

    def __del__(self):
      # 关闭套接字
      self.socket.close()
      print('已结束!!!')

    def login(self, username, password):
      """用户登录"""
      auth_recv = threading.Thread(target=self.recv_msg)
      auth_recv.start()
      print('正在登录, 请稍等……')
      self.username = username
      self.send_msg(content={'username': username, 'password': password}, send_type='login')
      time.sleep(1.5)

    def send_msg(self, content, send_to='', send_type='msg'):
      """
      :param content: <class 'dict'> The message that you what to send.
      :param send_to: <class 'str'> Who receives information.
      :param send_type:
      :return:
      """
      data = content
      data['send_type'] = send_type
      data['send_to'] = send_to
      data['from_user'] = self.username
      data['send_time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(time.time() + 28800))
      # 发送数据
      self.socket.send(json.dumps(data).encode('utf-8'))# 以json字符串经过utf-8编码发送

    def recv_msg(self):
      """接收各种消息并处理"""
      recv_data = self.socket.recv(1024)
      recv_dict = json.loads(recv_data.decode('utf-8'))
      if recv_dict.get('send_type', '') == 'login':
            if recv_dict.get('result', None) == 'ok':
                self.is_login = True
                print('登陆成功!!!')
            else:
                print(recv_dict.get('result'))
                self.username = ''
      elif recv_dict.get('send_type', '') == 'register':
            print(recv_dict.get('result'))
      elif recv_dict.get('send_type', '') == 'msg':
            content = recv_dict.get('content', '')
            send_time = recv_dict.get('send_time', '')
            print(send_time + '\n收到来自'+recv_dict.get('from_user', '')+'的消息:' + content)
      elif recv_dict.get('send_type', '') == 'is_online':
            pass
      elif recv_dict.get('send_type', '') == 'online_users':
            self.online_users_list = recv_dict.get('result', None)
      return recv_dict

    def recv_msg_always(self):
      while True:
            self.recv_msg()

    def get_users_list(self):
      self.send_msg(content={}, send_type='online_users')

    def register(self, username, password):
      auth_recv = threading.Thread(target=self.recv_msg)
      auth_recv.start()
      self.username = username
      time.sleep(0.5)
      self.send_msg(content={'username': username, 'password': password}, send_type='register')
      print('正在验证信息, 请稍等……')

    def search_users(self, username=''):
      pass

    def add_friends(self, username=''):
      pass

    def delete_friend(self, username=''):
      pass

    def create_group(self, group_id=''):
      pass

    def join_group(self, group_id=''):
      pass

    def quit_group(self, group_id=''):
      pass

```

## 客户端图形界面

1. **login_GUI.py**

```
# -*- coding: utf-8 -*-
# @File    : login_GUI.py.py
# @Software: PyCharm
import threading
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QMessageBox
from login_window import Ui_LoginWindow
from users_lists_GUI import UsersListsWindow
from register_GUI import RegisterWindow
from client import SFChatClient


class LoginWindow(QMainWindow, Ui_LoginWindow):
    def __init__(self):
      super(LoginWindow, self).__init__()
      self.setupUi(self)

      self.users_lists_window = UsersListsWindow()
      self.register_window = RegisterWindow()

      self.pushButton_login.clicked.connect(self.pushButton_login_click)
      self.pushButton_register.clicked.connect(self.pushButton_register_click)

    def pushButton_login_click(self):
      username = str(self.lineEdit_username.text())
      password = str(self.lineEdit_password.text())
      try:
            chat_obj = SFChatClient()
            chat_obj.login(username=username, password=password)
      except:
            QMessageBox.warning(self, '网络出错', '链接服务器失败,请检查网络或联系管理员!!!')
            return
      if chat_obj.is_login:
            self.users_lists_window.get_chat_obj(chat_obj)
            self.users_lists_window.show()
            self.hide()
            threading.Thread(target=self.recv_msg_always, args=(chat_obj,)).start()
            self.users_lists_window.get_online_users()
            self.users_lists_window.label_username.setText("<html><head/><body><p align=\"center\"><span style=\" font-size:11pt; font-weight:600;\">"+chat_obj.username+"</span></p></body></html>")
      else:
            QMessageBox.warning(self, '登陆失败!!!', '用户名或密码错误!!!')

    def pushButton_register_click(self):
      try:
            chat_obj = SFChatClient()
      except:
            QMessageBox.warning(self, '网络出错', '链接服务器失败,请检查网络或联系管理员!!!')
            return
      self.register_window.get_chat_obj(chat_obj)
      self.register_window.show()

    def recv_msg_always(self, chat_obj):
      while True:
            recv_dict = chat_obj.recv_msg()
            if recv_dict.get('send_type', '') == 'msg':
                content = recv_dict.get('content', '')
                send_time = recv_dict.get('send_time', '')
                from_user = recv_dict.get('from_user', '')
                print(send_time + '\n收到来自' + from_user + '的消息:' + content)
                self.users_lists_window.listWidget_recv_msg.addItem(send_time + '//' + from_user + ' : ' + content)
            elif recv_dict.get('send_type', '') == 'logout':
                break


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = LoginWindow()
    window.show()
    sys.exit(app.exec_())

```
2. **login_window.py**

```
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_LoginWindow(object):
    def setupUi(self, LoginWindow):
      LoginWindow.setObjectName("LoginWindow")
      LoginWindow.resize(446, 291)
      LoginWindow.setMinimumSize(QtCore.QSize(446, 291))
      LoginWindow.setMaximumSize(QtCore.QSize(446, 291))
      LoginWindow.setStyleSheet("")
      self.centralwidget = QtWidgets.QWidget(LoginWindow)
      self.centralwidget.setObjectName("centralwidget")
      self.label_name = QtWidgets.QLabel(self.centralwidget)
      self.label_name.setGeometry(QtCore.QRect(40, 20, 361, 31))
      self.label_name.setObjectName("label_name")
      self.pushButton_register = QtWidgets.QPushButton(self.centralwidget)
      self.pushButton_register.setGeometry(QtCore.QRect(100, 200, 101, 31))
      self.pushButton_register.setObjectName("pushButton_register")
      self.pushButton_login = QtWidgets.QPushButton(self.centralwidget)
      self.pushButton_login.setGeometry(QtCore.QRect(250, 200, 101, 31))
      self.pushButton_login.setObjectName("pushButton_login")
      self.lineEdit_username = QtWidgets.QLineEdit(self.centralwidget)
      self.lineEdit_username.setGeometry(QtCore.QRect(160, 80, 221, 31))
      self.lineEdit_username.setObjectName("lineEdit_username")
      self.lineEdit_password = QtWidgets.QLineEdit(self.centralwidget)
      self.lineEdit_password.setGeometry(QtCore.QRect(160, 130, 221, 31))
      self.lineEdit_password.setObjectName("lineEdit_password")
      self.label_password = QtWidgets.QLabel(self.centralwidget)
      self.label_password.setGeometry(QtCore.QRect(70, 80, 71, 31))
      self.label_password.setObjectName("label_password")
      self.label_username = QtWidgets.QLabel(self.centralwidget)
      self.label_username.setGeometry(QtCore.QRect(70, 140, 71, 21))
      self.label_username.setObjectName("label_username")
      LoginWindow.setCentralWidget(self.centralwidget)
      self.menubar = QtWidgets.QMenuBar(LoginWindow)
      self.menubar.setGeometry(QtCore.QRect(0, 0, 446, 26))
      self.menubar.setObjectName("menubar")
      LoginWindow.setMenuBar(self.menubar)
      self.statusbar = QtWidgets.QStatusBar(LoginWindow)
      self.statusbar.setObjectName("statusbar")
      LoginWindow.setStatusBar(self.statusbar)

      self.retranslateUi(LoginWindow)
      QtCore.QMetaObject.connectSlotsByName(LoginWindow)

    def retranslateUi(self, LoginWindow):
      _translate = QtCore.QCoreApplication.translate
      LoginWindow.setWindowTitle(_translate("LoginWindow", "登录"))
      LoginWindow.setToolTip(_translate("LoginWindow", "<html><head/><body><p><br/></p></body></html>"))
      self.label_name.setText(_translate("LoginWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">名字还没想好……</span></p></body></html>"))
      self.pushButton_register.setText(_translate("LoginWindow", "注册"))
      self.pushButton_login.setText(_translate("LoginWindow", "登录"))
      self.label_password.setText(_translate("LoginWindow", "<html><head/><body><p><span style=\" font-size:11pt; font-weight:600;\">用户名:</span></p></body></html>"))
      self.label_username.setText(_translate("LoginWindow", "<html><head/><body><p><span style=\" font-size:11pt; font-weight:600;\">密 码:</span></p></body></html>"))

```
3. **register_GUI.py**

```
# -*- coding: utf-8 -*-
# @Time    : 2019/9/16 16:00
# @File    : register_GUI.py
# @Software: PyCharm
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox

from register_window import Ui_RegisterWindow


class RegisterWindow(QWidget, Ui_RegisterWindow):
    def __init__(self):
      super(RegisterWindow, self).__init__()
      self.setupUi(self)

      self.chat_obj = None

      self.pushButton_register.clicked.connect(self.register_submit)

    def register_submit(self):
      username = str(self.lineEdit_username.text())
      if username == '':
            QMessageBox.warning(self, '不能为空', '用户名不能为空!!!')
            return
      password = str(self.lineEdit_password.text())
      re_password = str(self.lineEdit_re_password.text())
      if password == '' or password != re_password:
            QMessageBox.warning(self, '密码不匹配', '两次输入的密码不一致或为空!!!')
            return
      self.chat_obj.send_msg(content={'username': username, 'password': password}, send_type='register')
      response = self.chat_obj.recv_msg()
      if response.get('code', 1) == 0:
            QMessageBox.information(self, '注册成功', response.get('result', '注册成功!!!'))
            self.hide()
      else:
            QMessageBox.warning(self, '注册失败', response.get('result', '该用户名已存在!!!'))

    def get_chat_obj(self, chat_obj):
      self.chat_obj = chat_obj

```

4. **register_window.py**

```
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_RegisterWindow(object):
    def setupUi(self, RegisterWindow):
      RegisterWindow.setObjectName("RegisterWindow")
      RegisterWindow.resize(381, 321)
      RegisterWindow.setMinimumSize(QtCore.QSize(381, 321))
      RegisterWindow.setMaximumSize(QtCore.QSize(381, 321))
      self.label_title = QtWidgets.QLabel(RegisterWindow)
      self.label_title.setGeometry(QtCore.QRect(30, 30, 321, 31))
      self.label_title.setObjectName("label_title")
      self.label_username = QtWidgets.QLabel(RegisterWindow)
      self.label_username.setGeometry(QtCore.QRect(30, 100, 111, 31))
      self.label_username.setObjectName("label_username")
      self.label_password = QtWidgets.QLabel(RegisterWindow)
      self.label_password.setGeometry(QtCore.QRect(30, 150, 111, 31))
      self.label_password.setObjectName("label_password")
      self.label_re_password = QtWidgets.QLabel(RegisterWindow)
      self.label_re_password.setGeometry(QtCore.QRect(30, 200, 111, 31))
      self.label_re_password.setObjectName("label_re_password")
      self.lineEdit_username = QtWidgets.QLineEdit(RegisterWindow)
      self.lineEdit_username.setGeometry(QtCore.QRect(170, 100, 181, 31))
      self.lineEdit_username.setObjectName("lineEdit_username")
      self.lineEdit_password = QtWidgets.QLineEdit(RegisterWindow)
      self.lineEdit_password.setGeometry(QtCore.QRect(170, 150, 181, 31))
      self.lineEdit_password.setObjectName("lineEdit_password")
      self.lineEdit_re_password = QtWidgets.QLineEdit(RegisterWindow)
      self.lineEdit_re_password.setGeometry(QtCore.QRect(170, 200, 181, 31))
      self.lineEdit_re_password.setObjectName("lineEdit_re_password")
      self.pushButton_register = QtWidgets.QPushButton(RegisterWindow)
      self.pushButton_register.setGeometry(QtCore.QRect(30, 260, 321, 31))
      font = QtGui.QFont()
      font.setPointSize(14)
      self.pushButton_register.setFont(font)
      self.pushButton_register.setObjectName("pushButton_register")

      self.retranslateUi(RegisterWindow)
      QtCore.QMetaObject.connectSlotsByName(RegisterWindow)

    def retranslateUi(self, RegisterWindow):
      _translate = QtCore.QCoreApplication.translate
      RegisterWindow.setWindowTitle(_translate("RegisterWindow", "用户注册"))
      self.label_title.setText(_translate("RegisterWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">用户注册</span></p></body></html>"))
      self.label_username.setText(_translate("RegisterWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">用户名:</span></p></body></html>"))
      self.label_password.setText(_translate("RegisterWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">密码:</span></p></body></html>"))
      self.label_re_password.setText(_translate("RegisterWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">重复密码:</span></p></body></html>"))
      self.pushButton_register.setText(_translate("RegisterWindow", "提交"))

```

5. **users_lists_GUI.py**

```
# -*- coding: utf-8 -*-
# @Time    : 2019/9/12 10:12
# @File    : users_lists_GUI.py
# @Software: PyCharm
import time
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QListWidgetItem
from PyQt5.QtGui import QPixmap

# from MyGUI.users_lists_window import Ui_UsersLists
from users_lists_window import Ui_UsersLists


class UsersListsWindow(QWidget, Ui_UsersLists):
    def __init__(self):
      super(UsersListsWindow, self).__init__()
      self.setupUi(self)

      self.chat_obj = None
      self.send_to = ''

      self.label_show.setPixmap(QPixmap('tushansusu.jpg'))
      self.listWidget_online_users.doubleClicked.connect(lambda: self.start_chat(self.listWidget_online_users))
      # self.listWidget_friends.doubleClicked.connect(lambda: self.start_chat(self.listWidget_friends))
      self.pushButton_fresh_online_users.clicked.connect(self.get_online_users)
      self.pushButton_send_msg.clicked.connect(self.send_msg)
      self.pushButton_exit.clicked.connect(self.log_out_exit)

    def start_chat(self, listwidget):
      print(listwidget.currentItem().text())
      self.send_to = listwidget.currentItem().text()
      self.label_user_to.setText('<html><head/><body><p align=\"center\"><span style=\" font-size:11pt; font-weight:600;\">正在给'+listwidget.currentItem().text()+'发送消息</span></p></body></html>')

    def send_msg(self):
      text = self.textEdit_msg_edit.toPlainText()
      if text == '':
            QMessageBox.warning(self, '内容为空', '内容为空!!!')
            return
      if self.send_to == '':
            QMessageBox.warning(self, '未选择接收用户', '请双击左侧列表用户名选择接收用户!!!')
            return
      self.chat_obj.send_msg(content={'content': text}, send_to=self.send_to)
      self.textEdit_msg_edit.clear()

    def get_chat_obj(self, chat_obj):
      self.chat_obj = chat_obj
      self.setWindowTitle(self.chat_obj.username)

    def get_online_users(self):
      self.listWidget_online_users.clear()
      self.chat_obj.get_users_list()
      time.sleep(1.5)
      for user in self.chat_obj.online_users_list:
            self.listWidget_online_users.addItem(user)

    def log_out_exit(self):
      self.chat_obj.send_msg(content={}, send_type='logout')
      self.close()

```

6. **users_lists_window.py**

```
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_UsersLists(object):
    def setupUi(self, UsersLists):
      UsersLists.setObjectName("UsersLists")
      UsersLists.resize(951, 646)
      UsersLists.setMinimumSize(QtCore.QSize(951, 646))
      UsersLists.setMaximumSize(QtCore.QSize(951, 646))
      self.listWidget_online_users = QtWidgets.QListWidget(UsersLists)
      self.listWidget_online_users.setGeometry(QtCore.QRect(20, 120, 131, 471))
      self.listWidget_online_users.setObjectName("listWidget_online_users")
      self.label_online_users = QtWidgets.QLabel(UsersLists)
      self.label_online_users.setGeometry(QtCore.QRect(20, 90, 131, 21))
      self.label_online_users.setObjectName("label_online_users")
      self.label_friends = QtWidgets.QLabel(UsersLists)
      self.label_friends.setGeometry(QtCore.QRect(180, 90, 101, 21))
      self.label_friends.setObjectName("label_friends")
      self.listWidget_friends = QtWidgets.QListWidget(UsersLists)
      self.listWidget_friends.setGeometry(QtCore.QRect(180, 120, 131, 471))
      self.listWidget_friends.setObjectName("listWidget_friends")
      item = QtWidgets.QListWidgetItem()
      self.listWidget_friends.addItem(item)
      self.label_username = QtWidgets.QLabel(UsersLists)
      self.label_username.setGeometry(QtCore.QRect(70, 30, 171, 41))
      self.label_username.setObjectName("label_username")
      self.pushButton_fresh_online_users = QtWidgets.QPushButton(UsersLists)
      self.pushButton_fresh_online_users.setGeometry(QtCore.QRect(20, 600, 131, 31))
      self.pushButton_fresh_online_users.setObjectName("pushButton_fresh_online_users")
      self.pushButton_exit = QtWidgets.QPushButton(UsersLists)
      self.pushButton_exit.setGeometry(QtCore.QRect(180, 600, 131, 31))
      self.pushButton_exit.setObjectName("pushButton_exit")
      self.pushButton_send_msg = QtWidgets.QPushButton(UsersLists)
      self.pushButton_send_msg.setGeometry(QtCore.QRect(610, 600, 93, 28))
      self.pushButton_send_msg.setObjectName("pushButton_send_msg")
      self.listWidget_recv_msg = QtWidgets.QListWidget(UsersLists)
      self.listWidget_recv_msg.setGeometry(QtCore.QRect(340, 120, 371, 311))
      self.listWidget_recv_msg.setObjectName("listWidget_recv_msg")
      self.textEdit_msg_edit = QtWidgets.QTextEdit(UsersLists)
      self.textEdit_msg_edit.setGeometry(QtCore.QRect(340, 450, 371, 141))
      self.textEdit_msg_edit.setObjectName("textEdit_msg_edit")
      self.label_show = QtWidgets.QLabel(UsersLists)
      self.label_show.setGeometry(QtCore.QRect(750, 120, 171, 471))
      self.label_show.setObjectName("label_show")
      self.label_user_to = QtWidgets.QLabel(UsersLists)
      self.label_user_to.setGeometry(QtCore.QRect(350, 80, 341, 41))
      self.label_user_to.setObjectName("label_user_to")

      self.retranslateUi(UsersLists)
      QtCore.QMetaObject.connectSlotsByName(UsersLists)

    def retranslateUi(self, UsersLists):
      _translate = QtCore.QCoreApplication.translate
      UsersLists.setWindowTitle(_translate("UsersLists", "Form"))
      self.label_online_users.setText(_translate("UsersLists", "<html><head/><body><p><span style=\" font-size:11pt; font-weight:600;\">在线用户列表:</span></p></body></html>"))
      self.label_friends.setText(_translate("UsersLists", "<html><head/><body><p><span style=\" font-size:11pt; font-weight:600;\">我的好友:</span></p></body></html>"))
      __sortingEnabled = self.listWidget_friends.isSortingEnabled()
      self.listWidget_friends.setSortingEnabled(False)
      item = self.listWidget_friends.item(0)
      item.setText(_translate("UsersLists", "功能暂未开发"))
      self.listWidget_friends.setSortingEnabled(__sortingEnabled)
      self.label_username.setText(_translate("UsersLists", "<html><head/><body><p align=\"center\"><span style=\" font-size:11pt; font-weight:600;\">我的用户名</span></p></body></html>"))
      self.pushButton_fresh_online_users.setText(_translate("UsersLists", "刷新在线用户"))
      self.pushButton_exit.setText(_translate("UsersLists", "退出程序"))
      self.pushButton_send_msg.setText(_translate("UsersLists", "发送"))
      self.label_show.setText(_translate("UsersLists", "TextLabel"))
      self.label_user_to.setText(_translate("UsersLists", "<html><head/><body><p align=\"center\"><span style=\" font-size:11pt; font-weight:600;\">正在与某某某聊天</span></p></body></html>"))

```

**服务端servers.py的代码(数据库需要按照自己的配置):**
```
# -*- coding: utf-8 -*-
# @File    : servers.py
# @Software: PyCharm

import socket
import threading
import time
import json
import MySQLdb
import hashlib


class SFChatServers(object):
    def __init__(self):
      # 创建TCP套接字
      self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      # 绑定本地信息
      self.socket.bind(('192.168.72.1', 8888))
      # 将套接字由主动变为被动
      self.socket.listen(128)

      self.online_users = {}      # 用字典信息存储当前在线的用户
      self.users_addr = {}      # 保存用户IP

      self.db = MySQLdb.connect(
            host='localhost',
            port=3306,
            user='root',
            password='root',
            db='sf_chat',
            charset='utf8'
      )

    def __del__(self):
      """服务端程序退出时关闭所有已连接的用户并关闭监听socket"""
      # 关闭套接字
      for username in self.online_users:
            self.online_users.close()
      self.socket.close()
      self.db.close()
      print('已结束!!!')

    def listen(self):
      """监听连接,每当用户登录到聊天工具时开始开启一个该用户的线程"""
      while True:
            new_socket, client_addr = self.socket.accept()
            print(client_addr)
            t1 = threading.Thread(target=self.recv_data, args=(new_socket, client_addr))
            t1.start()
            print('线程启动')

    def recv_data(self, temp_socket, client_addr):
      """接收用户的各种信息并分别处理"""
      while True:
            # 接受数据,并解码,转换为字典形式暂时存储
            recv_data = temp_socket.recv(1024)
            recv_data = recv_data.decode('utf-8')
            recv_data = json.loads(recv_data)
            print(recv_data)      # 调试打印调试打印调试打印调试打印调试打印调试打印调试打印调试打印调试打印调试打印
            if recv_data['send_type'] == 'login':
                # 登录处理, 验证成功继续等待, 直到客户端退出, 断开连接
                if not self.login_auth(temp_socket, recv_data, client_addr=client_addr):
                  break
            elif recv_data['send_type'] == 'register':# v1.1版本新添加了用户注册功能
                self.register(temp_socket, recv_data)
            elif recv_data['send_type'] == 'msg':
                self.send_msg(temp_socket, recv_data, send_to=recv_data['send_to'])   # 普通消息处理,转发至目标用户
            elif recv_data['send_type'] == 'online_users':
                self.send_users_list(temp_socket)       # 请求在线人数处理,
            elif recv_data['send_type'] == 'logout':
                logout_user = recv_data.get('from_user', '')
                del self.online_users
                self.send_msg(temp_socket, recv_data)
                print(logout_user + '已退出')
                break
            elif recv_data['send_type'] == 'recv_return':
                pass

    def login_auth(self, temp_socket, recv_data, client_addr):
      """用户登录身份验证的具体实现"""
      username = recv_data['username']
      password = recv_data['password']
      send_time = recv_data['send_time']
      sh = hashlib.sha256()
      sh.update((username + password + 'cyj').encode('utf8'))
      password_hash = sh.hexdigest()
      cur = self.db.cursor()
      cur.execute('select * from database_usersprofile where username=%s and password=%s', )
      user = cur.fetchone()
      if user is not None:
            self.send_msg(temp_socket, {'result': 'ok', 'send_type': 'login'}, recv_data['send_to'])
            self.online_users] = temp_socket
            cur.execute("""update database_usersprofile set last_login=%s,last_ip=%s where username=%s""", )
            self.db.commit()
            cur.close()
            return True
      else:
            self.send_msg(temp_socket, {'result': '用户不存在或密码错误!!!', 'send_type': 'login'}, recv_data['send_to'])
            cur.close()
            return False

    def send_msg(self, temp_socket, content, send_to=''):
      """
      向客户端发送消息, 或者存入数据库
      :param temp_socket:
      :param content:
      :param send_to:
      :return:
      """
      # 消息反馈, 对接收到的客户端大多数行为进行反馈, 比如登录注册
      if content.get('send_type', '') == 'msg':
            temp_socket.send(json.dumps({'send_type': 'recv_return', 'result': 'ok'}).encode('utf-8'))   #
      else:
            temp_socket.send(json.dumps(content).encode('utf-8'))       #

      if content.get('send_type', '') == 'msg':
            # 此处从数据库查找目的用户,在线尝试发送,离线保存数据库
            user_socket = self.online_users.get(send_to, None)
            from_user = content.get('from_user', '')
            send_time = content.get('send_time', '')
            msg = content.get('content', '')
            cur = self.db.cursor()
            if user_socket is None:
                # 此处用户离线存入数据库
                cur.execute("""insert into messages(send_to,from_user,status,send_time,content,send_type)
                            values(%s,%s,%s,%s,%s,%s)""",
                            )
            else:
                try:
                  user_socket.send(json.dumps(content).encode('utf-8'))
                  # 发送过去也应该存入数据库, 只是状态是已读
                  cur.execute("""insert into messages(send_to,from_user,status,send_time,content,send_type)
                              values(%s,%s,%s,%s,%s,%s)""",
                              )
                except:
                  cur.execute("""insert into messages(send_to,from_user,status,send_time,content,send_type)
                              values(%s,%s,%s,%s,%s,%s)""",
                              )
            self.db.commit()
            cur.close()

    def save_to_database(self):
      """把数据存入数据库"""
      # 数据应该包括发送人, 发送时间, 内容, 消息状态, 接收人
      # 可以考虑使用MySQL的外键
      pass

    def send_users_list(self, temp_socket):
      """获取在线用户并发送"""
      users_list = list()
      logout_list = list()
      for user in self.online_users:
            try:
                self.online_users.send(json.dumps({'send_type': 'is_online'}).encode('utf-8'))
                users_list.append(user)
            except:
                logout_list.append(user)
                print(user, '已退出')
      for i in logout_list:
            if self.online_users.get(i, None) is not None:
                del self.online_users
      temp_socket.send(json.dumps({'send_type': 'online_users', 'result': users_list}).encode('utf-8'))

    def register(self, temp_socket, recv_data):
      username = recv_data['username']
      password = recv_data['password']
      send_time = recv_data['send_time']
      cur = self.db.cursor()
      cur.execute('select * from database_usersprofile where username=%s', )
      user = cur.fetchone()
      if user is not None:
            temp_socket.send(json.dumps({'send_type': 'register', 'result': '用户名已存在!!!', 'code': 1}).encode('utf8'))
      else:
            sh = hashlib.sha256()
            sh.update((username + password + 'cyj').encode('utf8'))
            password = sh.hexdigest()
            cur.execute('''insert into database_usersprofile(username, password, nickname, date_joined)
            value (%s,%s,%s,%s)''', )
            self.db.commit()
            temp_socket.send(json.dumps({'send_type': 'register', 'result': '注册成功!!!', 'code': 0}).encode('utf8'))
      cur.close()


if __name__ == '__main__':
    server = SFChatServers()
    server.listen()

```

以下是全部代码:


yike911 发表于 2019-9-19 00:38

dongbala 发表于 2019-9-19 16:14

厉害了,楼主,是个神人{:1_927:}
页: [1]
查看完整版本: 【Python】利用socket做的一个类似早期QQ的聊天程序