基于python的tcp聊天室

1.目标和思路

1.功能和目标: 类似群聊天
    [1]有人进入聊天室需要输入姓名,姓名不能重复
    [2] 有人进入聊天室时,其他人会收到通知:
        *** 进入聊天室
    [3] 一个人发消息,其他人会收到:
        ××× :××××××××
    [4] 有人退出聊天室,则其他人会收到通知:
        ××× 退出聊天室

2.确定技术模型
    [1] 使用字典保存用户信息 {姓名:客户端套接字}
    [2] 套接字选择: tcp套接字 
    [3] 转换模型 : 客户端-->>服务端-->>所有客户端
    [4] 收发关系处理: 一个客户端发送消息给服务端,服务端再转发给其他客户端
                    客户端使用多进程来分别处理收发消息
                    服务端使用多线程来管理客户端

3.具体实现流程
    [1] 搭建网络连接
    [2] 实现登录
            客户端: × 输入姓名
                    × 将姓名发送给服务端
                    × 接收服务端反馈
                    × 如果不允许进入则重新输入,允许则进入聊天室
                    × 创建新的进程用于收发消息

            服务端:  * 创建新的线程用于管理客户端的连接
                    × 接收姓名
                    ×  判断姓名是否存在
                    ×  将结果反馈给客户端
                    × 不允许 登录结束,允许登录则将用户信息插入数据机构保存
                    × 将登录信息通知其他人
    [3] 聊天
            客户端: × 循环发送消息
                    × 循环接收消息
            
            服务器: × 接收消息,判断请求类型
                    × 将消息转发给其他用户
    [4] 退出
            客户端: ×输入quit进行退出
                    × 发送进程关闭,接收进程关闭

            服务器: × 退出信息发送给其他人
                    × 将其信息从服务器清除

2.客户端代码

import os,sys,signal
from socket import *

server_addr = (('127.0.0.1',9696))

def msg_recv(sockfd):       ##接收消息
    while True:
        data = sockfd.recv(1024)
        if data.decode() == 'q':
            break
        print(data.decode())

def msg_send(sockfd,name):      ##发送消息
    while True:
        try:
            data = input("发言>>")
        except KeyboardInterrupt:
            data = 'quit'
        if data == 'quit':  ##退出
            msg = 'Q %s'%name
            sockfd.send(msg.encode())
            break
        else:
            msg = 'C %s %s'%(data,name)
            sockfd.send(msg.encode())

def main():
    sockfd = socket()##创建套接字
    
    try:
        sockfd.connect(server_addr)
    except Exception:
        print("连接失败")
        sockfd.close()
        return
    
    while True:             ####输入姓名登录
        name = input("请输入昵称:")
        sockfd.send(('L '+name).encode())   ## L 区分消息类型
        data = sockfd.recv(128)
        if data.decode() == 'OK':
            print("你已经进入9696聊天室")
            break
        else:
            print(data.decode())

    signal.signal(signal.SIGCHLD,signal.SIG_IGN)
    pid = os.fork()

    if pid < 0:
        sys.exit("err")  
    elif pid == 0:        ##子进程
        msg_recv(sockfd)
    else:
        msg_send(sockfd,name) 
    sys.exit("退出")
     

if __name__ == "__main__":
    main()

3.服务器代码

from threading import Thread
from socket import *
import os,sys

Host = '0.0.0.0'
Post = 9696
address = (Host,Post)

chat_name = {}   ## name:conn
jobs = {}   ###conn:t  存储线程对象t    
#conn_stat = {}   ###  conn:'true'  存储套接字的状态 true为运行  false 为结束线程

def do_login(conn,name):        ##登录
    if name not in chat_name:
        conn.send(b'OK')
    else:
        conn.send("昵称已经存在".encode())
        return
    msg = "%s 进入9696聊天室" %name
    for c in chat_name:
        chat_name[c].send(msg.encode())
    chat_name[name] = conn

def do_chat(conn,msg,name):
    text = "%s :%s"%(name,msg)
    for n in chat_name:
        if n != name:
            chat_name[n].send(text.encode())

def do_quit(conn,name):
    msg = "%s 退出9696聊天室了"%name
    for c in chat_name:
        if c == name:
            conn.send(b'q')
        else:
            chat_name[c].send(msg.encode())
    del chat_name[name]
    #conn_stat[conn] = 'false'
    conn.close()

def do_request(conn):   ##用来处理客户端的请求
    while True:
        data = conn.recv(1024).decode().strip().split(' ')
        if data[0] == 'L':   ##登录
            name = data[-1]
            do_login(conn,name)
        elif data[0] == 'C':    ##聊天
            name = data[-1]
            msg = ''.join(data[1:-1])
            do_chat(conn,msg,name)
        elif data[0] == 'Q':  ##退出
            name = data[-1]
            do_quit(conn,name)
            break
        
def main():
    
    sockfd = socket()  ##tcp套接字
    sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)    ##端口重用
    sockfd.bind(address)
    sockfd.listen(5)

    while True:
        try:
            conn,addr = sockfd.accept()
        except KeyboardInterrupt:
            sockfd.close()
            sys.exit("退出")
        except Exception as e:
            print(e)
            continue
        print("Connect from:",addr)
        t = Thread(target=do_request,args=(conn,))
        t.start()
        jobs[conn] = t
        #conn_stat[conn] = 'true'
        ###回收线程  当有线程结束,只有当新的客户端连接进来才会回收结束的线程
        for c in jobs:
            if c._closed is True:
                jobs[c].join()

if __name__ == '__main__':
    main()

4.运行结果

1.运行客户端和服务器
图1
2.输入姓名
图2
3.发送消息
图3

4.退出客户端
图4

运行中收到的消息会显示在“发言>>”之后,可以在其后加上“\r”让光标回到行首,效果会好一点。。。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值