飞鸽02:朴素的实现

最朴素的实现就是过程化的方法。

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

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

第一部分是初始化。


#!/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吧。一共就一个函数(好吧,两个,不,三个,有一个辅助的,还有一个解析数据报文用)。所有的功能串行在一起。

接下来的一步,就是对这个代码进行“切分“了,或者叫“模块化”了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值