【python】TCP socket基于多线程的局域网多人聊天室,双版本(实现可私聊)附完整源码

一、局域网多人聊天室-无私聊功能版

1.1 代码效果演示

在这里插入图片描述

1.2服务器端源码

'''
描述:局域网内基于多线程的TCP socket 聊天室。
'''
import socket,threading,time

clients = {}

class client(object):
    def __init__(self,socket,addr,username):
        self.addr = addr[0]
        self.port = addr[1]
        self.username = username
        self.socket=socket

    def sendMsg(self,msg,username,admin):
        try:
            if admin:
                self.socket.send(("%s %s(管理员): %s" % (self.getTime(), username, msg)).encode("utf-8"))
            else:
                self.socket.send(("%s %s: %s" %(self.getTime(), username, msg)).encode("utf-8"))
            return True
        except:
            return False

    def recv(self,mtu=1024):
        try:
            data = self.socket.recv(mtu).decode("utf-8")
            if data == "-!-quit-!-" or not data:
                return False
            return data
        except:
            return False

    def close(self):
        try:
            self.socket.close()
            return True
        except:
            return False

    def getId(self):
        return "%s-%s" % (self.addr,self.port)
    def getTime(self):
        return str(time.strftime("%Y-%m-%d %H:%M:%S"))

def new_client(c):
    try:
        print("%s(%s) 尝试连接" %(c.addr,c.port))
        data = c.recv()
        if not data:
            return
        if len(data) >= 16:
            c.socket.send("用户名太长了")
            return
        c.username = data
        print("用户%s %s(%s)已连接" %(c.username,c.addr,c.port))
        c.socket.send("已连接".encode("utf-8"))
        while True:
            data = c.recv()
            if not data:
                break
            else:
                print("用户%s %s(%s) 发送了: %s" % (c.username,c.addr, c.port, data))
                broadcast(data,c.username)

    except socket.errno as e:
        print("Socket error: %s" % str(e))
    except Exception as e:
        print("Other exception: %s" % str(e))
    finally:
        print("%s(%s) 断开连接" % (c.addr, c.port))
        c.close()
        clients.pop(c.getId())

def broadcast(msg,username,admin=False):
    for c in clients.values():
        c.sendMsg(msg,username,admin)

def start_server(port):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    host = "192.168.2.158"
    server.bind((host, port))

    # 监听客户端
    server.listen(10)
    print("服务器已开启,正在监听{}".format(server.getsockname()))

    while True:
        # 接受客户端连接
        conn, addr = server.accept()
        c = client(conn,addr,"")
        clients[c.getId()] = c
        t = threading.Thread(target=new_client, args=(c,))
        t.start()


if __name__ == "__main__":
    start_server(23333)
    print("服务器已关闭")

1.3客户端源码

import socket,threading
import time,sys

running = False

def send(c):
    time.sleep(0.5)
    while True:
        data = input('')
        c.send(data.encode("utf-8"))
        if data == "-!-quit-!-":
            running = False
            break

def recv(c,t2):
    username = input("输入用户名: ")
    c.send(username.encode("utf-8"))
    t2.start()
    while running:
        try:
            data = c.recv(1024).decode("utf-8")
            if not data:
                break
            print(data)
        except:
            break


if __name__ == "__main__":
    ip = ""
    addrs = socket.getaddrinfo(socket.gethostname(), None)
    print("开始扫描可用的服务器")
    for i in range(100,254):
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        client.settimeout(0.1)
        temp = "192.168.2.%s" % i
        try:
            data = client.connect_ex((temp, 23333))
            if data == 0:
                print("找到服务器%s" % temp)
                a = input("是否连接 y/n")
                if a == "y":
                    ip = temp
                    break
                else:
                    continue
        except:
            print("2")
        client.close()
    if ip == "":
        print("未连接任何服务器")
        sys.exit()
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 导入 socket 模块
    try:
        client.connect((ip, 23333))
        running = True
        t2 = threading.Thread(target=send, args=(client,))
        t1 = threading.Thread(target=recv, args=(client,t2))
        t1.start()
        t1.join()
        t2.join()
    except:
        pass
    finally:
        print("连接已被关闭")
        client.close()

二、局域网多人聊天室-带私聊功能版

开发要求: 用python TCP开发一个聊天室,实现如下功能:该程序实现局域网内的聊天功能,包括服务器端程序和客户端程序两部分。
要求:(1)支持汉字,(2)客户端连接到服务器后,显示在线的用户;然后可以选择任意其他在线用户聊天。
建议不使用GUI(可视化用户界面),以降低开发难度。

2.1服务器端源码

import json
import threading
from socket import *
from time import ctime


class PyChattingServer:
    __socket = socket(AF_INET, SOCK_STREAM, 0)
    __address = ('', 12231)

    __buf = 1024

    def __init__(self):
        self.__socket.bind(self.__address)
        self.__socket.listen(20)
        self.__msg_handler = ChattingHandler()

    def start_session(self):
        print('等待客户连接...\r\n')
        try:
            while True:
                cs, caddr = self.__socket.accept()
                # 利用handler来管理线程,实现线程之间的socket的相互通信
                self.__msg_handler.start_thread(cs, caddr)
        except socket.error:
            pass


class ChattingThread(threading.Thread):
    __buf = 1024

    def __init__(self, cs, caddr, msg_handler):
        super(ChattingThread, self).__init__()
        self.__cs = cs
        self.__caddr = caddr
        self.__msg_handler = msg_handler

    # 使用多线程管理会话
    def run(self):
        try:
            print('...连接来自于:', self.__caddr)
            data = '欢迎你到来kenwanmao聊天室!请输入你的的昵称(不能带空格):'
            self.__cs.sendall(bytes(data, 'utf-8'))
            while True:
                data = self.__cs.recv(self.__buf).decode('utf-8')
                if not data:
                    break
                self.__msg_handler.handle_msg(data, self.__cs)
                print(data)
        except socket.error as e:
            print(e.args)
            pass
        finally:
            self.__msg_handler.close_conn(self.__cs)
            self.__cs.close()


class ChattingHandler:
    __help_str = "[ SYSTEM ]\r\n" \
                 "输入/checkol,即可获得所有登陆用户信息\r\n" \
                 "输入/h,即可获得帮助\r\n" \
                 "输入@用户名 (注意用户名后面的空格)+消息,即可发动单聊\r\n" \
                 "输入/i,即可屏蔽群聊信息\r\n" \
                 "再次输入/i,即可取消屏蔽\r\n" \
                 "所有首字符为/的信息都不会发送出去"

    __buf = 1024
    __socket_list = []

    __user_name_to_socket = {}
    __socket_to_user_name = {}

    __user_name_to_broadcast_state = {}

    def start_thread(self, cs, caddr):
        self.__socket_list.append(cs)
        chat_thread = ChattingThread(cs, caddr, self)
        chat_thread.start()

    def close_conn(self, cs):
        if cs not in self.__socket_list:
            return
        # 去除socket的记录
        nickname = "SOMEONE"
        if cs in self.__socket_list:
            self.__socket_list.remove(cs)
        # 去除socket与username之间的映射关系
        if cs in self.__socket_to_user_name:
            nickname = self.__socket_to_user_name[cs]
            self.__user_name_to_socket.pop(self.__socket_to_user_name[cs])
            self.__socket_to_user_name.pop(cs)
            self.__user_name_to_broadcast_state.pop(nickname)
        nickname += " "
        # 广播某玩家退出聊天室
        self.broadcast_system_msg(nickname + "离开了本聊天室")

    # 管理用户输入的信息
    def handle_msg(self, msg, cs):
        js = json.loads(msg)
        if js['type'] == "login":
            if js['msg'] not in self.__user_name_to_socket:
                if ' ' in js['msg']:
                    self.send_to(json.dumps({
                        'type': 'login',
                        'success': False,
                        'msg': '账号不能够带有空格'
                    }), cs)
                else:
                    self.__user_name_to_socket[js['msg']] = cs
                    self.__socket_to_user_name[cs] = js['msg']
                    self.__user_name_to_broadcast_state[js['msg']] = True
                    self.send_to(json.dumps({
                        'type': 'login',
                        'success': True,
                        'msg': '昵称建立成功,输入/checkol可查看所有在线的人,输入/help可以查看帮助(所有首字符为/的消息都不会发送)'
                    }), cs)
                    # 广播其他人,他已经进入聊天室
                    self.broadcast_system_msg(js['msg'] + "已经进入了聊天室")
            else:
                self.send_to(json.dumps({
                    'type': 'login',
                    'success': False,
                    'msg': '账号已存在'
                }), cs)
        # 若玩家处于屏蔽模式,则无法发送群聊消息
        elif js['type'] == "broadcast":
            if self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]:
                self.broadcast(js['msg'], cs)
            else:
                self.send_to(json.dumps({
                    'type': 'broadcast',
                    'msg': '屏蔽模式下无法发送群聊信息'
                }), cs)
        elif js['type'] == "ls":
            self.send_to(json.dumps({
                'type': 'ls',
                'msg': self.get_all_login_user_info()
            }), cs)
        elif js['type'] == "help":
            self.send_to(json.dumps({
                'type': 'help',
                'msg': self.__help_str
            }), cs)
        elif js['type'] == "sendto":
            self.single_chatting(cs, js['nickname'], js['msg'])
        elif js['type'] == "ignore":
            self.exchange_ignore_state(cs)

    def exchange_ignore_state(self, cs):
        if cs in self.__socket_to_user_name:
            state = self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]
            if state:
                state = False
            else:
                state = True
            self.__user_name_to_broadcast_state.pop(self.__socket_to_user_name[cs])
            self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]] = state
            if self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]:
                msg = "通常模式"
            else:
                msg = "屏蔽模式"
            self.send_to(json.dumps({
                'type': 'ignore',
                'success': True,
                'msg': '[TIME : %s]\r\n[ SYSTEM ] : %s\r\n' % (ctime(), "模式切换成功,现在是" + msg)
            }), cs)
        else:
            self.send_to({
                'type': 'ignore',
                'success': False,
                'msg': '切换失败'
            }, cs)

    def single_chatting(self, cs, nickname, msg):
        if nickname in self.__user_name_to_socket:
            msg = '[TIME : %s]\r\n[ %s CHATTING TO %s ] : %s\r\n' % (
                ctime(), self.__socket_to_user_name[cs], nickname, msg)
            self.send_to_list(json.dumps({
                'type': 'single',
                'msg': msg
            }), self.__user_name_to_socket[nickname], cs)
        else:
            self.send_to(json.dumps({
                'type': 'single',
                'msg': '该用户不存在'
            }), cs)
        print(nickname)

    def send_to_list(self, msg, *cs):
        for i in range(len(cs)):
            self.send_to(msg, cs[i])

    def get_all_login_user_info(self):
        login_list = "[ SYSTEM ] ALIVE USER : "
        for key in self.__socket_to_user_name:
            login_list += self.__socket_to_user_name[key] + ",\r\n"
        return login_list

    def send_to(self, msg, cs):
        if cs not in self.__socket_list:
            self.__socket_list.append(cs)
        cs.sendall(bytes(msg, 'utf-8'))

    def broadcast_system_msg(self, msg):
        data = '[TIME : %s]\r\n[ SYSTEM ] : %s' % (ctime(), msg)
        js = json.dumps({
            'type': 'system_msg',
            'msg': data
        })
        # 屏蔽了群聊的玩家也可以获得系统的群发信息
        for i in range(len(self.__socket_list)):
            if self.__socket_list[i] in self.__socket_to_user_name:
                self.__socket_list[i].sendall(bytes(js, 'utf-8'))

    def broadcast(self, msg, cs):
        data = '[TIME : %s]\r\n[%s] : %s\r\n' % (ctime(), self.__socket_to_user_name[cs], msg)
        js = json.dumps({
            'type': 'broadcast',
            'msg': data
        })
        # 没有的登陆的玩家无法得知消息,屏蔽了群聊的玩家也没办法获取信息
        for i in range(len(self.__socket_list)):
            if self.__socket_list[i] in self.__socket_to_user_name \
                    and self.__user_name_to_broadcast_state[self.__socket_to_user_name[self.__socket_list[i]]]:
                self.__socket_list[i].sendall(bytes(js, 'utf-8'))


def main():
    server = PyChattingServer()
    server.start_session()


main()

2.2客户端源码

import json
import threading
from socket import *

is_login = False
is_broadcast = True

#接受消息类
class ClientReceiveThread(threading.Thread):
    __buf = 1024
    #初始化
    def __init__(self, cs):
        super(ClientReceiveThread, self).__init__()
        self.__cs = cs
    def run(self):
        self.receive_msg()

    def receive_msg(self):
        while True:
            msg = self.__cs.recv(self.__buf).decode('utf-8')
            if not msg:
                break
            js = json.loads(msg)
            if js['type'] == "login":
                if js['success']:
                    global is_login
                    is_login = True
                print(js['msg'])
            elif js['type'] == "ignore":
                if js['success']:
                    global is_broadcast
                    if is_broadcast:
                        is_broadcast = False
                    else:
                        is_broadcast = True
                print(js['msg'])
            else:
                if not is_broadcast:
                    print("[现在处于屏蔽模式]")
                print(js['msg'])

#发送消息类
class ClientSendMsgThread(threading.Thread):

    def __init__(self, cs):
        super(ClientSendMsgThread, self).__init__()
        self.__cs = cs
    def run(self):
        self.send_msg()

    # 根据不同的输入格式来进行不同的聊天方式
    def send_msg(self):
        while True:
            js = None
            msg = input()
            if not is_login:
                js = json.dumps({
                    'type': 'login',
                    'msg': msg
                })
            #包含@,表示进入私聊模式
            elif msg[0] == "@":
                data = msg.split(' ')
                if not data:
                    print("请重新输入")
                    break
                nickname = data[0]
                nickname = nickname.strip("@")
                if len(data) == 1:
                    data.append(" ")
                js = json.dumps({
                    'type': 'sendto',
                    'nickname': nickname,
                    'msg': data[1]
                })
            elif msg == "/help":
                js = json.dumps({
                    'type': 'help',
                    'msg': None
                })
            elif msg == "/checkol":
                js = json.dumps({
                    'type': 'ls',
                    'msg': None
                })
            elif msg == "/i":
                js = json.dumps({
                    'type': 'ignore',
                    'msg': None
                })
            else:
                if msg[0] != '/':
                    js = json.dumps({
                        'type': 'broadcast',
                        'msg': msg
                    })
            if js is not None:
                self.__cs.sendall(bytes(js, 'utf-8'))

def main():
    buf = 1024
    # 改变这个的地址,变成服务器的地址,那么只要部署到服务器上就可以全网使用了
    address = ("127.0.0.1", 12231)
    cs = socket(AF_INET, SOCK_STREAM, 0)
    cs.connect(address)
    data = cs.recv(buf).decode("utf-8")
    if data:
        print(data)
    receive_thread = ClientReceiveThread(cs)
    receive_thread.start()
    send_thread = ClientSendMsgThread(cs)
    send_thread.start()
    while True:
        pass

main()

参考博客

python实现简单的聊天小程序
python编写简易聊天室实现局域网内聊天

  • 10
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
基于socket通信的多人聊天室可以通过以下步骤实现: 1. 创建一个服务器端程序,使用特定的端口监听客户端的连接请求。可以使用Python中的socket实现。 2. 在服务器端程序中,使用socket的bind()方法将服务器端的IP地址和端口号绑定到一个socket对象上,并使用listen()方法开始监听客户端连接请求。 3. 在服务器端程序中,使用accept()方法来接收客户端的连接请求,并获得一个与客户端通信的socket对象。 4. 为每个客户端连接创建一个新的线程,以便能够同时处理多个客户端的消息。 5. 在服务器端程序中,使用recv()方法接收客户端发送的消息,并将消息广播给所有已连接的客户端。这样所有客户端之间就可以实现即时的多人聊天。 6. 在服务器端程序中,使用send()方法将服务器端接收到的消息发送给所有已连接的客户端。 7. 在客户端程序中,使用socket的connect()方法连接到服务器端的IP地址和端口号。 8. 在客户端程序中,使用send()方法将客户端发送的消息发送给服务器端。 9. 在客户端程序中,使用recv()方法接收服务器端发送的消息,并将其显示在客户端的窗口上。 需要注意的是,由于socket通信是基于TCP协议的,因此可以实现可靠的数据传输,但是在实现聊天室时,需要处理客户端进程退出的情况,并及时清理相关资源,避免程序异常或资源泄漏等问题的出现。 以上是基于socket通信的多人聊天室的简要实现步骤,具体实现过程还需要根据编程语言和具体的需求进行进一步的开发和调试。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值