python之UDP实现局域网聊天

#coding=utf-8
import socket,threading,time,threading
ip='172.20.10.2'
# ip='169.254.187.118'
# ip='169.254.83.51'
#广播地址,表示向本局域网所有电脑发送,下面两个地址都可以
#addr='<broadcast>'
# addr='255.255.255.255'
PORT=8000
INTERVAL=1
TIMEOUT=5

# 邻居
neighbors=[]
nickname='马老师'
msgnum=0
msghis={}
'''
自定义协议,应用层。tcp、udp
指令:相应的内容
cmd:ok   代表接收数据成功
msg:文本消息   例子:   msg:ok

软件设计文档
1. 启动程序时,广播发送上线指令,协议:    cmd:online
2. 收到李雷的cmd:online指定的电脑,给李雷回复(打招呼)     cmd:hello



网络协议文档
cmd:online
cmd:hello
msg:序号:文本    例子:msg:100:我妈说的:不让我和傻子玩
cmd:ok:序号

'''
def recv():
    while True:
        data,addr=udp.recvfrom(1024)
        content=data.decode('utf-8')
        # print(content,addr)
        # ok不是普通的字符串,而是一种指令
        if content[:3]=='cmd':
            if content[4:6]=='ok':
                rmsgnum=content[7:]
                print('%s收到消息,rmsgnum:%s'%(addr[0],rmsgnum))
                msghis.pop(rmsgnum)
            elif content[4:10]=='online':
                rnickname=content[11:]
                # 判断接收到online指令的ip地址,如果是自己的ip,将不提示
                ips=getips()
                if addr[0] not in ips  :
                    print('【系统提示】:%s(%s) 上线'%(rnickname,addr[0]))
                    udp.sendto(('cmd:hello:'+nickname).encode('utf-8'),(addr[0],PORT))
                    addneighbors({'ip':addr[0],'nickname':rnickname})
            elif content[4:9]=='hello':
                rnickname=content[10:]
                addneighbors({'ip':addr[0],'nickname':rnickname})

        elif content[:3]=='msg':
            index=content.find(':',4)
            rmsgnum=content[4:index ]
            scontent='cmd:ok:%s'%rmsgnum
            udp.sendto(scontent.encode('utf-8'),(addr[0],8000)) 
            print(content[index+1:],addr)

def addneighbors(ip):
    # 做添加ip判断,判断是否是自己,判断是否重复添加
    if ip not in neighbors:
        neighbors.append(ip)

def getips():
    '''
    获取本机所有ipv4的ip地址
    '''
    ips=[]
    hostname = socket.gethostname()
    addrs = socket.getaddrinfo(hostname,None,family=socket.AF_INET)
    for item in addrs:
        ips.append(item[4][0])
    return ips
def broadcast(ip,port,content):
    '''
    广播
    '''
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp:
        # 192.168.0.100 8001
        # 169.254.0.100 8001
        udp.bind((ip,port+1))
        # print('-------',port)


        #下面这行代码允许发送广播数据
        udp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        data=content.encode('UTF-8')
        # data=b'abc'
        udp.sendto(data,('255.255.255.255',port))

def online():
    '''
    发送上线广播
    '''
    for ip in getips():
        broadcast(ip,PORT,'cmd:online:'+nickname)

def checkmsghis():
    global msghis
    for key in list(msghis.keys()):
        value=msghis[key]
        t=value[1]
        if time.time()-t > TIMEOUT:
            print('【提示】发送给%s的消息,对方未收到,消息:%s'%(value[2],value[0]))
            msghis.pop(key)

    threading.Timer(INTERVAL,checkmsghis).start()

if __name__ == "__main__":
    udp=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    # 一个端口既能实现收,也能实现发
    # portstr=input('请输入绑定端口号(默认8000):')
    # port=8000
    # if portstr!='':
    #     port=int(portstr)
    udp.bind(('0.0.0.0',PORT))
    print('-------2',PORT)


    print('绑定%d成功,开始接收数据...'%PORT)

    t=threading.Thread(target=recv)
    t.start()

    online()

    threading.Timer(INTERVAL,checkmsghis).start()
    while True:
        '''
        to ip:port msg ,例子 to 192.168.1.100 8000 你好
        '''
        s=input('>')
        l=s.split(' ')
        if l[0]=='to':
            msgnum+=1
            content=('msg:%s:%s')%(msgnum,l[2])
            udp.sendto(content.encode('UTF-8'),(l[1],PORT))
            msghis[str(msgnum)]=(l[2],time.time(),l[1])
        elif l[0]=='sn':
            print('邻居列表')
            for item in neighbors:
                print('  ',item)
        elif l[0]=='smh':
            print('msghis',msghis)
        elif l[0]=='help':
            print('to ip 消息')
            print('sn 查看邻居')
        else:
            print('格式错误,正确格式:to ip msg')