`
cloverprince
  • 浏览: 127396 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

飞鸽02:朴素的实现

阅读更多
最朴素的实现就是过程化的方法。

简单起见,实现使用命令行界面。分成两个线程,一个用于接受键盘输入,另一个用于接受UDP Socket的输入。

警告:代码十分纠结,堪比意大利面条。初学者和OO程序员切勿模仿。

第一部分是初始化。

#!/usr/bin/env python

import threading
import socket
import constants    # 存放飞鸽协议需要的常数,一般为IPMSG_*的形式
import pdu     # 自动构造数据报报文的模块,略。

PORT = 2425    # 飞鸽使用2425端口
MAX_PACKET = 16384  # 任意设置的数值。报文的最大大小

BROADCAST_ADDR = ("255.255.255.255",2425)  # 广播地址。

SELF_USER = "Shirouzu"  # 本地用户名。应该通过操作系统接口获取。
SELF_HOST = "Jupiter"   # 本地主机名。

# 创建socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(("",PORT))

# 因为Python语言的函数内部不能随意修改全局变量,
# 所以干脆创造一个简单的对象,其成员专门存放全局变量。
class SharedData(object):
    pass

shared_data = SharedData()
shared_data.cur_pkt_no = 1 # 当前报文编号。每次发出一个,加一。(注意:线程不安全)

shared_data.contacts = {}  # 储存邻居列表。关键字是IP地址;值是用户名、主机名、昵称等。
shared_data.nick = "ThisIsMyNickname" # 昵称
shared_data.readlist = []  # 待读的(加信封的)信息的队列。


下一部分是接收线程。从网络接收报文,逐一解析(在pdu模块中解析,这里省略),然后根据命令的类型进行下一步动作。

def receiver_proc():
    while True:
        # 接收,解析
        data,addr = s.recvfrom(MAX_PACKET)
        try:
            result = pdu.parse_pdu(data)
        except pdu.MalformedPduError:
            print "Malformed PDU:"
            print '[%s]'%data
            continue

        # 分离命令和标志
        cmd = pdu.get_mode(result["command"])
        opt = pdu.get_opt(result["command"])
        
        if cmd==constants.IPMSG_BR_ENTRY:
            # 用户新加入
            update_contact(addr,result['user'],result['host'],
                    pdu.is_set(opt,constants.IPMSG_ABSENCEOPT),
                    result['additional'])
            print "New client. user:[%s], host:[%s], addr:[%s]"%(
                    result["user"],result["host"],addr)
            reply=pdu.make_pdu(
                    1,
                    shared_data.cur_pkt_no,
                    SELF_USER,
                    SELF_HOST,
                    constants.IPMSG_ANSENTRY,
                    shared_data.nick,
                    )
            shared_data.cur_pkt_no+=1
            s.sendto(reply,addr)
        if cmd==constants.IPMSG_ANSENTRY:
            # 其他节点回应了自己的加入
            update_contact(addr,result['user'],result['host'],
                    pdu.is_set(opt,constants.IPMSG_ABSENCEOPT),
                    result['additional'])
            print "Answer entry: user:[%s], host:[%s], addr:[%s]"%(
                    result["user"],result["host"],addr)
        if cmd==constants.IPMSG_BR_ABSENCE:
            # 其他节点发布了状态变更消息
            update_contact(addr,result['user'],result['host'],
                    pdu.is_set(opt,constants.IPMSG_ABSENCEOPT),
                    result['additional'])
            print "User [%s] changed state: nick:[%s], away:%s" % (
                    result["user"],result["additional"],
                    pdu.is_set(opt,constants.IPMSG_ABSENCEOPT))
        if cmd==constants.IPMSG_SENDMSG:
            # 收到其他节点发来的消息
            print "Message from [%s]:"%result['user']
            print "   PKT_NO: [%d]:"%result['packet_num']
            print result['additional']
            if pdu.is_set(opt,constants.IPMSG_SENDCHECKOPT):
                reply = pdu.make_pdu(
                        1,
                        shared_data.cur_pkt_no,
                        SELF_USER,
                        SELF_HOST,
                        constants.IPMSG_RECVMSG,
                        result['packet_num'],
                        )
                shared_data.cur_pkt_no+=1
                s.sendto(reply,addr)
            if pdu.is_set(opt,constants.IPMSG_SECRETOPT):
                # 对于包含信封的信息,在将来予以回应。
                # 严格的说,也不应该立即显示信息内容。
                shared_data.readlist.append((
                    result['additional'],result['user'],
                    result['packet_num'],addr))
        if cmd==constants.IPMSG_RECVMSG:
            # 对端节点对本地发出的消息予以确认
            print "Message to [%s] pktnum %s is acknowledged."%(result['user'],result['additional'])

def update_contact(addr,user,host,is_absent,nick):
    """ 辅助函数。更新一个用户的状态。 """
    if addr in shared_data.contacts:
        del shared_data.contacts[addr]
    shared_data.contacts[addr]={
            "user":user,
            "result":host,
            "is_absent":is_absent,
            "nick":nick,
            }


第三部分就是键盘响应。

def keyboard_proc():
    while True:
        try:
            ln = raw_input()
            args = ln.split()
            try:
                cmd = args[0]
            except IndexError:
                continue
            if cmd == "join":
                # join命令发出加入网络的数据报
                target = BROADCAST_ADDR
                request = pdu.make_pdu(
                        1,
                        shared_data.cur_pkt_no,
                        SELF_USER,
                        SELF_HOST,
                        constants.IPMSG_BR_ENTRY,
                        shared_data.nick,
                        )
                shared_data.cur_pkt_no+=1
                s.sendto(request,target)
            if cmd == "send":
                # 给某个邻居发出消息
                user = args[1]
                msg = " ".join(args[2:])
                try:
                    target = [addr
                            for addr,info
                            in shared_data.contacts.iteritems()
                            if info['user']==user
                            ][0]
                except IndexError:
                    print "No such user"
                    continue
                request = pdu.make_pdu(
                        1,
                        shared_data.cur_pkt_no,
                        SELF_USER,
                        SELF_HOST,
                        constants.IPMSG_SENDMSG | constants.IPMSG_SENDCHECKOPT,
                        msg,
                        )
                shared_data.cur_pkt_no+=1
                s.sendto(request,target)
            if cmd == "chnick":
                # 修改昵称
                shared_data.nick = args[1]
                target = BROADCAST_ADDR
                request = pdu.make_pdu(
                        1,
                        shared_data.cur_pkt_no,
                        SELF_USER,
                        SELF_HOST,
                        constants.IPMSG_BR_ABSENCE,
                        shared_data.nick,
                        )
                shared_data.cur_pkt_no+=1
                s.sendto(request,target)
            if cmd == "readmsg":
                # 阅读加信封的消息;回复确认
                try:
                    msg,user,packet_num,addr = shared_data.readlist.pop(0)
                except IndexError:
                    print "No more message."
                    continue
                print "Message from [%s]:"%user
                print "[%s]"%msg
                target = addr
                request = pdu.make_pdu(
                        1,
                        shared_data.cur_pkt_no,
                        SELF_USER,
                        SELF_HOST,
                        constants.IPMSG_READMSG,
                        shared_data.nick,
                        )
                shared_data.cur_pkt_no+=1
                s.sendto(request,target)

        except KeyboardInterrupt:
            break
        except EOFError:
            break

# 创建接收线程
receiver_thread = threading.Thread(target=receiver_proc)
receiver_thread.daemon=True
receiver_thread.start()

keyboard_proc()


就这样,190多行代码实现了主要流程。这只是为了证明我对IP Messenger协议的理解没有重大偏差。实践证明这个程序可以和现有的GNOME2 IP Messenger实现进行简单通信。

我想,这就是典型的spaghetti code吧。一共就一个函数(好吧,两个,不,三个,有一个辅助的,还有一个解析数据报文用)。所有的功能串行在一起。

接下来的一步,就是对这个代码进行“切分“了,或者叫“模块化”了。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics