python3实战-udp协议的聊天室

首先说一下聊天室需要的技术:

客户端-服务器模式的搭建

tcp/udp协议的选择

设计思路:

    服务器接收客户端的登录(连接),然后接收客户端的消息,并根据消息内容选择回发对象。

    服务器端先输入姓名作为登录的姓名,然后连接到服务器之后就可以发送给服务器,服务器进行处理。

    选择通讯协议udp,因为这里是不需要三次握手四次挥手的过程,用udp就完全可以进行的,实现起来也比较简单。

 问题点:

    先启动服务器,然后多个客户端连接到服务器,此时假设客户端c1发送消息到服务器,c1希望能够收到消息,但是其他客户端还是在等待输入的地方,这样就会造成了c1与 c2...等其他客户端状态不一致的情况,此时服务器收到消息,服务器是不能阻塞的,所以只能讲将收到的消息回发,此时其他客户端因为阻塞在了输入函数,所以无法收到消息和显示。

    解决这个问题的思路是在客户端建立2个进程,分别处理输入事件和接收事件,在哪个事件状态就绪,就可以获得到系统时间片,就能够得到执行,这样就可以保证c1发送了消息,服务器收到消息的时候,转发该条消息到连接到该服务器的每个客户端,实现了群聊天的功能。

    聊天室具有如下功能

    1,新客户端连接时,相当于有人加入群聊,系统通知到所有玩家该客户端的加入

    2,客户端发送的消息,服务器转发给所有玩家

    3,客户端断开连接,代表退出了群聊,系统发送该玩家退出消息给所有人(或者管理员)

    其他说明:enrc="^^^^"是选择的一种加密方式,在这里类似于一个宏定义的功能定义一个全局变量,对发送的内容进行加密。因为是udp协议,所以其他人写的客户端程序也能连接到我的服务器,但是通过加密,就能识别到自己的客户端的内容,然后进行处理,其他程序的客户端不做处理来实现将其他客户端的连接屏蔽的目的。


具体代码如下:

服务器端代码---

# myserver.py
import socket
import select
import sys
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(("127.0.0.1",9527))

enrc = "^^^^"
addrlist = []
namelist = []
# data = "welcome %s to join us"
while True:
    # print(addrlist)
    # print(namelist)
    #recvfrom接收UDP消息,参数是每次接收消息的大小,返回接收到的内容
    data,address = s.recvfrom(1024)
    if enrc in data.decode():
        name = data.decode().split(enrc)[0]
        # print("name:",name)
        if data.decode().endswith("end"): 
            addrlist.remove(address)
            namelist.remove(name)  
            for i in range(len(addrlist)):
                s.sendto((name+" quit").encode(),addrlist[i])       
        else:
            if address in addrlist:
                pass
            else:
                addrlist.append(address)
                namelist.append(name)
                # print("received string :",data.decode("utf-8"))
            #sendto 发送UDP消息,,参数分别为消息和发送方的地址,返回发送的字节数            
            for x in addrlist:
                s.sendto(data,x)
s.close()


客户端代码---

import socket
import os
import time
HOST = "127.0.0.1"
PORT = 9527

enrc = "^^^^"
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# s.bind((HOST,PORT))
# hello = "hello"
name = input("please input your name:\t")

data = name + enrc
# +hello

s.sendto(data.encode("utf-8"),(HOST,PORT))

#login successful

pid = os.fork()

if pid < 0: 
    print("create process failed")
#子进程执行
elif pid == 0:
    while True:
        data = s.recv(1024).decode('utf-8')
        
        if data.endswith(enrc):
            datalist = data.split(enrc)
            print(("system msg:"+datalist[0]+" join group").center(80))
            # print("said: ".join(datalist))
        elif data.endswith("quit"):
            print(data.center(80))
        else:
            datalist = data.split(enrc)
            if name == datalist[0]:
                print("%80s"%(datalist[0]+"  "+time.ctime()))
                print("%80s"%datalist[1])
            else:
                print("%-80s"%(datalist[0]+"  "+time.ctime()))
                print("%-80s"%datalist[1])
                # print("%-80s"%(datalist[0]+" said: "+datalist[1]))
        # print("receive from server\n",data.decode('utf-8'))

#父进程执行
else:
    while True:
        info = input() 
        if not info:
            s.sendto((name+ enrc+"end").encode("utf-8"),(HOST,PORT))
            break
        else:
            data = name + enrc + info 
            s.sendto(data.encode("utf-8"),(HOST,PORT)) 

"""
zhang3^^^^hello
zhang3^^^^end
zhang3 + "^^^^" 

"""

在客户端需要说明一下:

父进程执行的是输入事件,因为2个进程都是死循环,但是都会阻塞,所以不会出现真正意义上的死循环造成系统资源过度浪费,客户端只有在自己主动退出群聊的情况下才会得到退出,所以肯定是客户端主动输入退出消息,这里是回车键,这样父进程就会结束,然后发送了一条退出消息给到服务器,然后服务器回发消息到客户端,只是本客户端子进程此时收到的消息是退出消息,所以选择不打印出来,然后结束了。这样做的目的是父进程先退出,子进程被系统进程收养,不会出现僵尸进程对进程资源的浪费。

总结:

首先,选择合理的加密方式和解析方式,给程序带来了很大的方便,也可以避免其他人的恶意攻击。

其次,在选择协议上采用的是更方便的udp协议,对于聊天室来说更易于实现。

再则分清楚客户端和服务器分别需要干嘛,将各自的功能确认清楚,这样逻辑就不会混乱。

最后当然是针对问题的根本点进行解决,解决了客户端状态不一致的问题,那么整个程序也就可以顺利的运行起来了。

特殊说明:该程序肯定还是有一些潜在的bug,难免考虑不到的地方,如果读者有看到的,请指出改正,本人后续也会时常翻看并完善的。



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值