小白犯的Python错误[1]

Update:C程序已经更正。(啥?第一句就错了?打脸)

 

到今天也差不多当了1个半月的码农了,作为菜鸟,自然错误是犯了很多的啦。多亏老板和负责服务端的Z帮忙,我才能把这些错误都给解决掉。下面自爆一下Python菜鸟的初级错误两则:

0与None:

0 != None,要充分认识到这点。否则就会犯下面的错误:

1
2
3
4
5
6
7
8
9
10
11
def checkItHasValue(a):
    #check whether a has a value or not.
    #If a has a value, print 'yes'.
    #Else, print 'no'
    if a:
        print "yes"
    else:
        print "no"
 
b = 0
test(b)

变量b有一个值,为0。函数checkItHasValue检查形参b是否有一个数值,显然b是有的,但if a这样的判断语句导致了错误结果的产生。所以如果是检查变量是否为None,则应该使用判断语句if a == None来检查。记住:0 != None

1 and 0 or 1:

这是一个三目运算符,但不是bool? True : False这种C语言中的运算符。(之前我一直当作bool? True : False来用。太小白了。书没看好!话说那本啥书我还没看完。打脸。)1 and 0 or 1是一个与或运算符。首先计算前面的1 and 0部分,然后用得出的结果再计算后面的x or 1部分。在C中试试这个:

1
2
3
4
5
6
7
8
#include "stdio.h"
 
main():
{
    int a = 1?0:1;
    printf("%d",a);
    return 0;
}

(在写上面一段C代码的时候我差点笑了。因为我以为写好了,然后再看了一眼,妈呀,include都没,{}都没,C那么就没碰了,果然是忘光了。另外,这段代码是否正确我也不知道,那个Visio Studio早被我卸载了,它太占地方了。看来以后还是要安装一个minGW或者Cygwin备用啊)

验证结果:

在Python中试试这个:

1 and 0 or 1

结果不同吧(Python的部分大家自己去试试啦。只要打开解析器然后打一行就出现结果了。不像C还要编译,总觉得好麻烦哦)。(C的我没验证。。。打脸)

 

Python菜鸟在这一个半月中犯的其他错误嘛,无非就是打错变量名或者漏了XXX之类的,小白啊小白。多看书才是王道!!!(我懒,就没认真看过。打脸!)

大蟒蛇飞行棋编写总结

最终我完成了这个作业,而且应该是班中完成较好的一份作业。虽然美观上是比不上一些其他同学的作品,但功能上是非常完善的。通过上一次聊天室程序的编写,在编写这个程序时少走了许多弯路。在攻克了数据结构和界面操作难点后,功能的编写就顺利了很多。

本飞行棋游戏实现以下功能:

  • 色子掷出6才能从机场出发
  • 色子掷出6奖励额外的掷色子机会一次
  • 实现了同色位置跳棋功能
  • 实现了飞机在18号位置的飞行功能
  • 实现了吃掉同位子其他颜色棋子的功能
  • 自动判断玩家选择的棋子是否可走

1.数据结构

在经历这次程序编写后,深刻的体会到一个良好的数据结构的重要性。客户端处,我为每种颜色的棋子都设计了一个棋盘,并且进行位置标号,这样在进行位子判断时可以省去很多写很多判断语句的麻烦,因为每一色棋子所可能走过的位置标号都时相同的。

蓝色棋子棋盘

 

红色棋子棋盘

同时我写了一个chess类,成员是蓝、绿、黄、红四色棋子字典,用于保存每色4个棋子的位置。同时写了SetPosition和GetPosition两个方法,用于设定新位置和获取棋子位置。此外,我还使用了4个全局的字典,用于保存每色棋子每一个位子的坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python
#encoding:utf-8
 
class FlyChess():
    """棋子类,存有四方的所有十六枚棋子的状态:颜色和当前位置"""
    def __init__(self):
        """初始化变量"""
        self.blue = {1:None,2:None,3:None,4:None}
        self.green = {1:None,2:None,3:None,4:None}
        self.yellow = {1:None,2:None,3:None,4:None}
        self.red = {1:None,2:None,3:None,4:None}
 
    def GetPosition(self,color,num):
        """供外部调用得到棋子当前位置"""
        if color == 'blue':
            return self.blue[num]
        elif color == 'green':
            return self.green[num]
        elif color == 'yellow':
            return self.yellow[num]
        elif color == 'red':
            return self.red[num]
        else:
            return False
 
    def SetPosition(self,color,num,pos):
        if color == 'blue':
            self.blue[num] = pos
        elif color == 'green':
            self.green[num] = pos
        elif color == 'yellow':
            self.yellow[num] = pos
        elif color == 'red':
            self.red[num] = pos

服务端处,这个程序面令的难点是如何表示4人一组的游戏。我使用的方法是在每一个transport中建立一个partner字典,当4人一组的游戏建立时更新partner字典内存,把维持4个客户端连接的transport映射到入字典。

2.客户端界面操作的实现

学过的东西总还是会有用的时候,虽然自己不知道何时会用到。上学期学习的MFC部分界面实现方法,在这次的程序编写中派上了用处。在开始写程序起的一周内,我都为如何实现鼠标点击获取所选棋子的功能烦恼,曾经考虑过ButtonImage,但效果不理想。最终我想到了MFC中的GDI,通过一些查阅和ZetCode的一些代码示例,这个功能实现了。

我把棋盘和棋子的绘制全部改成了GDI的DrawBitmap实现。获取鼠标所选棋子,则通过获取鼠标点击时的位置,与玩家所控制的4个棋子的位置进行比较判断(通过设定一个反映面积,实现只要点击在棋子上都可以作出响应)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#此处仅贴出棋盘和蓝色棋子的绘制
    def OnPaint(self,event):
        """wx.EVT_PAINT event function"""
        dc = wx.PaintDC(self)
        # 载入图片
        blue_chess = wx.Image(r'resource/blue.gif',wx.BITMAP_TYPE_ANY)
        blue_chess_half = blue_chess.Scale(blue_chess.GetWidth()/2,blue_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        green_chess = wx.Image(r'resource/green.gif',wx.BITMAP_TYPE_ANY)
        green_chess_half = green_chess.Scale(green_chess.GetWidth()/2,green_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        yellow_chess = wx.Image(r'resource/yellow.gif',wx.BITMAP_TYPE_ANY)
        yellow_chess_half = yellow_chess.Scale(yellow_chess.GetWidth()/2,yellow_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        red_chess = wx.Image(r'resource/red.gif',wx.BITMAP_TYPE_ANY)
        red_chess_half = red_chess.Scale(red_chess.GetWidth()/2,red_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        chess_board = wx.Image(r'resource/flying_chess.jpg',wx.BITMAP_TYPE_ANY)
        chess_board = chess_board.Scale(chess_board.GetWidth()/2,chess_board.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
 
        # Draw chess board
        dc.DrawBitmap(wx.BitmapFromImage(chess_board),0,0,True)
 
        # Draw blue chess
        if self.chess.blue[1] in (90,91,92,93): # 判断棋子所在位置,决定使用图片的大小
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',1)][0],blue_step[self.chess.GetPosition('blue',1)][1],True)
        if self.chess.blue[2] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',2)][0],blue_step[self.chess.GetPosition('blue',2)][1],True)
        if self.chess.blue[3] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',3)][0],blue_step[self.chess.GetPosition('blue',3)][1],True)
        if self.chess.blue[4] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',4)][0],blue_step[self.chess.GetPosition('blue',4)][1],True)

3.通信协议格式

在上一次聊天室的程序编写中,由于自己对通信协议格式制订的不完善,导致了各种麻烦的产生。这次学乖了,打上开始和结束标签,即使几个指令在一个包中传输过来也不怕了:

xx+数据内容+_99

当然这次也不是一帆风顺的。由于位置代码都是数字,曾经不加_的情况下引起了数据截断错误的出现。这个还是考虑不周。顺带还是要指出一个错误,这个在答辩时由老师指出的:数据长度没指定。万一数据长度超出包长度,socket会自动截断数据分成多个包发送,接收端没处理好会造成错误的产生。

4.同色位置跳棋功能及18号位置飞行功能的实现

其实这2个功能实现起来很简单。因为数据结构的关系,棋盘上对于每种颜色棋子的同色位置都是相同的,所以程序可以使用同一段代码实现此功能,而不用为每种颜色都单独写出代码来实现。在把肯定不能跳棋的位子进行排除后,根据我所标的位置特点,对于一种颜色的棋子在其自己的棋盘上,只有在位置除4余2的时,该位置和棋子同色。18号位置飞行功能就更为方便了,只要判断是否位子为18号即可。

1
2
3
4
5
6
7
8
9
10
        if not new in (90,91,92,93,0,50,51,52,53,54,55,56,18) and new%4 == 2: # 检查是否同色能跳到下一位置
            new = new +4
            self.chess.SetPosition(self.color,current,new)
            self.SendNewPosition(current,new)
            self.OnPaint(self)
        elif new == 18: # 检查是否能飞到30.18-->30
            new = 30
            self.chess.SetPosition(self.color,current,new)
            self.SendNewPosition(current,new)
            self.OnPaint(self)

5.吃棋功能实现

我设计的数据结构在这个功能的实现上是麻烦了,因为要进行位置转换,位置必须转换为同一张棋盘的位置才性。如果数据结构设计成使用一张棋盘,这个功能实现起来就很方便了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    def HitCheck(self,pos):
        """检查是否发生吃其他玩家的情况发生,发生则棋子被吃发送被吃棋子的新位置(返回机场)"""
        pos_tmp = None
        # 检查蓝色棋子
        if self.color != 'blue':
            if self.color == 'green': # 位置转换成蓝色棋子位置表
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'red':
                if pos in range(1,25):
                    pos_tmp = pos+26
                elif pos in range(27,51):
                    pos_tmp = pos-26
            elif self.color == 'yellow':
                if pos in range(1,12):
                    pos_tmp = pos+39
                elif pos in range(14,51):
                    pos_tmp = pos-13
            if pos_tmp:
                if self.chess.blue[1] == pos_tmp:
                    self.socket.send('06blue_iso1_1_iso2_90_99') # 发送被吃棋子新位置。06代表棋子被吃更新信息
                if self.chess.blue[2] == pos_tmp:
                    self.socket.send('06blue_iso1_2_iso2_91_99')
                if self.chess.blue[3] == pos_tmp:
                    self.socket.send('06blue_iso1_3_iso2_92_99')
                if self.chess.blue[4] == pos_tmp:
                    self.socket.send('06blue_iso1_4_iso2_93_99')
        # 检查绿色棋子
        if self.color != 'green':
            if self.color == 'blue':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            elif self.color == 'red':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'yellow':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            if pos_tmp:
                if self.chess.green[1] == pos_tmp:
                    self.socket.send('06green_iso1_1_iso2_90_99')
                if self.chess.green[2] == pos_tmp:
                    self.socket.send('06green_iso1_2_iso2_91_99')
                if self.chess.green[3] == pos_tmp:
                    self.socket.send('06green_iso1_3_iso2_92_99')
                if self.chess.green[4] == pos_tmp:
                    self.socket.send('06green_iso1_4_iso2_93_99')
        # 检查黄色棋子
        if self.color != 'yellow':
            if self.color == 'blue':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'green':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            elif self.color == 'red':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            if pos_tmp:
                if self.chess.yellow[1] == pos_tmp:
                    self.socket.send('06yellow_iso1_1_iso2_90_99')
                if self.chess.yellow[2] == pos_tmp:
                    self.socket.send('06yellow_iso1_2_iso2_91_99')
                if self.chess.yellow[3] == pos_tmp:
                    self.socket.send('06yellow_iso1_3_iso2_92_99')
                if self.chess.yellow[4] == pos_tmp:
                    self.socket.send('06yellow_iso1_4_iso2_93_99')
        # 检查红色棋子
        if self.color != 'red':
            if self.color == 'blue':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            elif self.color == 'green':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            elif self.color == 'yellow':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            if pos_tmp:
                if self.chess.red[1] == pos_tmp:
                    self.socket.send('06red_iso1_1_iso2_90_99')
                if self.chess.red[2] == pos_tmp:
                    self.socket.send('06red_iso1_2_iso2_91_99')
                if self.chess.red[3] == pos_tmp:
                    self.socket.send('06red_iso1_3_iso2_92_99')
                if self.chess.red[4] == pos_tmp:
                    self.socket.send('06red_iso1_4_iso2_93_99')

6.服务端游戏匹配功能实现

当用户开始游戏,服务端就会为用户寻找可用的游戏组。通过查找其他transport的state变量值,当state为__waiting__状态,该用户就会加入这个transport所在的游戏组,若游戏组中成员达到4人则开始游戏,否则就等待。若无可用的游戏组,则自动开启一个新的游戏组等待其他客户端加入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    def GetReady(self,data):
        """对用户加入游戏请求的处理函数"""
        if data[:2] == '02': # 判断数据内容是否是加入游戏请求。02代表加入游戏请求
            self.state = '__Ready__'
            has_game = False # 标记是否已经找到游戏编号
            group = 0 # 记录同一游戏编号内的排队玩家数
            group_in_game = [] # 记录已经使用的游戏编号
            for name,protocol in self.users.iteritems():
                if name != self.name:
                    if protocol.state == '__Ready__':
                        has_game = True # 找到游戏编号
                        if self.game is None: # 加入游戏编号
                            self.game = protocol.game
                        if self.game == protocol.game: # protocol的游戏编号相同,加入同组游戏
                            group = group + 1
                            self.partner[protocol.color] = protocol # 记录同编号游戏玩家
                            if group == 3: # 检查是否到4人可以开始游戏。可以开始游戏,则所有人都进入'__InGame__'状态
                                self.state = '__InGame__'
                                for color,partner in self.partner.iteritems():
                                    partner.state = '__InGame__'
                    elif protocol.state == '__InGame__': # 记录已使用的游戏编号
                        group_in_game.append(protocol.game)
            if not has_game: # 如果没有找到可用的游戏编号,则启用一个新的游戏编号
                new_game = 1
                while self.game == None:
                    if not new_game in group_in_game:
                        self.game = new_game
                    else:
                        new_game = new_game + 1
            if not has_game: # 给玩家分配颜色
                self.color = 'blue'
                self.partner['blue'] = self
                self.transport.write('30blue_99')
            elif not 'green' in self.partner:
                self.color = 'green'
                self.partner['green'] = self
                self.transport.write('30green_99')
            elif not 'yellow' in self.partner:
                self.color = 'yellow'
                self.partner['yellow'] = self
                self.transport.write('30yellow_99')
            else:
                self.color = 'red'
                self.partner['red'] = self
                self.transport.write('30red_99')
            for color,partner in self.partner.iteritems(): # 更新同游戏编号玩家的partner数据
                if color != self.color:
                    partner.partner[self.color] = self
            if group < 3:
                self.transport.write('31_99') # 缺少玩家,发送等待信息
            else:
                # 玩家到齐,发送游戏开始信息
                for color,partner in self.partner.iteritems():
                    partner.transport.write('32_99') # 游戏开始
                    if color == 'blue':
                        partner.transport.write('34_99') # 34代表该玩家走棋
                    else:
                        partner.transport.write('33蓝方_99') # 33代表该玩家等待其他玩家走棋

 

其他功能就不叙述了,都是一些很简单的判断和循环语句就可以实现的功能。游戏测试中还是发现了BUG:吃棋功能在特殊情况下会出现不应出现吃棋还是有棋子被吃。到目前为止还不清楚是在什么情况下会出现,光是看代码也还看不出个所以然来。然后就是,作业教完了,于是我就懒得修补了。(话说这些内容在应聘的时候被雇主看到怎么办啊?)

最后献上源代码(这次就不搞什么license装逼了。第一次使用git,有些棋盘位置设计图片也被上传上去了):https://github.com/Tynox/FlyingChess

聊天室程序编写小结

老师布置作业编写一个聊天室软件,本着我不喜VC++、不碰Java、拒绝C的原则,只剩下Python这条路供我走下去了。于是操起Vi,背上Python API,wxPython API/doc,Twisted API/doc,开始写我的PyChatRoom。经过艰苦卓绝的战斗,终于我解决了众多名为error,bug的敌人,来到目的地–它可以基本无错的运行起来!————废话说完,进入正题。

考虑到聊天室的多人聊天需求,自然想到服务器端需要多线程处理。但我自知自己在多线程上经验甚少,而且对服务器程序了解更少,那么索性就使用现成的技术吧。Twisted是一个Python实现的异步网络编程框架,有了它我就不需要为多线程而烦恼了:Twisted已经完成了一切操作!Twisted的入门教程可以看这里:。当然我们不能忘记最强大的官方文件和示例:http://twistedmatrix.com/trac/

鄙人不才,看了2个入门教程还是没有写服务器的头绪,看了官方教程才有所领悟:因为官方的示例正好是一个聊天室啦!于是在此基础上进行二次开发,使之成为满足我需求的服务器端程序。我的服务器程序使用示例构架,按照标准的Twisted方式编写。首先是创建一个自己的Protocol:

1
2
3
4
5
6
7
8
9
10
11
12
class ChatProtocol(Protocol):
    def __init__(self):
        pass
 
    def connectionMade(self):
        pass
 
    def connectionLost(self,reason):
        pass
 
    def dataReceived(self,data):
        pass

第二步创建一个ServerFactory,用于处理每一个Protocol

1
2
3
4
5
class ChatServerFactory(ServerFactory):
    def __init__(self):
        pass
    def buildProtocol(self,addr):
        pass

第三步实现一个reactor,它是Twisted真正的关键:

1
2
reactor.listenTCP(port,ChatServerFactory())
reactor.run()

Twisted的构架看上去很棒,而且它确实很棒!虽然它看上去很简单,但是即使我完成了这个聊天室服务器端的程序,我还是认为我不会写Twisted程序。

好,其他问题咱们放到最后说。下面来看看客户端程序吧。

这次我想让我的程序看上去有个清晰的结构,因为我发觉年初我写的第一个Python程序的结构实在太糟糕了:所有的代码都写在了一个文件当中!足足1500行代码都堆叠在一起,又脏又乱!最丑的要属SQL处理代码了,足足有十个独立的函数,那时我没有写一个类来统一它们,实在是太糟糕了。年中为了写一个串口程序作业而看到了PySerial模块作者写的那个示例代码,真让我体会到了阅读拥有清晰结构的程序代码时那种愉悦的心情!这次在编写客户端时我就努力让自己的程序结构变得清晰可读。虽然没达到非常棒的效果,但比年初那个代码的结构是好了很多!

我把程序分成三块来处理,分别是三个不同的类:Chat类、MainFrame类和Login类。其中MainFrame类是聊天客户端的主界面,Login类从名字上就可以判断出它是用于用等的,而Chat类起了衔接MainFrame和Login的功能。socket的初始化和等入的验证也都是在Chat中完成的。对于客户端的结构部分就不多叙述了,它的代码可以在本文末尾处下载到。我想直接进入问题讨论部分。

面对新的技术,第一次接触总会遇到很多问题。网络编程我本就接触不多,这次果然遇到多多的问题。有些技术问题可以通过查月资料来解决,而有些则属于特定问题,不论是好的方法还是不好的方法,最终还是解决了吧。本文只列出我遇到的最大问题。

问题:socket buffer中的数据不是立即发送,导致多条transport.write()中的数据在一个数据包中发送。好吧,这个问题还是我自己在设定协议时造成的麻烦。最初我并没考虑很多,对socket机制也了解不够。所以我就按照一次recv产生一次数据处理的方法来编写协议代码了。而socket对buffer的管理机制应该是当buffer中数据达到一定量后发送一个数据包清空buffer,或者是存在一个计数器在buffer没有达到一定数据量的情况下当计数器计数完成则强制发送一个数据包清空buffer。Python的socket中貌似没有提供可令程序员强制flush buffer的方法(可能有,但我不知道),那么同样的,Twisted这个基于Python的框架自然也没有提供类似的方法了。其他语言比如Java中是有提供这类操作的方法。那么我只能通过其他方法来解决这个问题了。这个问题我在2中情况下都有遇到:

情况一是欢迎信息和登入之后的成员更新信息被封装在一个数据包中发送了,在我这种数据处理方法中直接把成员更新数据给当作欢迎信息发送到屏幕了。我的处理方法是把欢迎信息写成独立函数,把成员更新写成独立函数,然后在收到欢迎信息后客户端返回一个值,而收到更远更新之后客户端也返回一个值。

1
2
3
4
5
6
7
8
9
10
11
#服务器代码
def GetReady(self):
    """Client is ready. Server send welcome info."""
    self.transport.write("99Welcome to py chatroom")
 
def update_Member(self):
    """update the member list."""
    for name,protocol in self.users.iteritems():
        if not name == self.name:
            self.transport.writeSequence('03'+name)
            protocol.transport.write('03'+self.name)

或许是Python的效率救了我,让我能够用这个方法解决问题。有时候慢也有好处吗?再想想这个方法应该是有点问题的,只是在我自己这种测试中没暴露出来而已。

情况二:当聊天室同时在线人数在3人及以上时成员更新又出现了成员更新数据被封装成一个数据包发送。这次我的解决方法时从客户端入手,手动分割这个包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#客户端代码
if data[:2] == '03':
    #new user coming message
    new_user_count = len(data) / 18
    while new_user_count:
        new_user = data[:18]
        if len(data) > 18:
            data = data[18:]
        name = self.PrintName(new_user)
        if not name[2:] in self.member:
            self.memberText.AppendText(name[2:]+'\n')
            self.member.append(name[2:])
            self.memberBox.Append(name[2:])
            self.messageText.AppendText(name[2:]+'加入了聊天室\n')
        new_user_count = new_user_count - 1
    self.socket.send('96')

这个方法时不得已的,因为我实在想不出一个在服务器端解决的方法。

其实要真正解决这个问题,还是要从协议上解决。我应该在每个数据段中增加一个结束标识符,而客户端每次recv之后按照结束标识符来分割数据段来处理。这个方法就能彻底解决此问题。

从这次编程中我所获得的启示:

  1. 自己很懒,花了近一个月才完成。
  2. 网络编程中要正确处理自己的协议:要有一个好的协议头和一个正确的协议结束标识符,这点非常重要!
  3. 更多更全面的测试,这是完成一个程序所必须的步骤。
  4. 更好的注释,更多的草稿,让自己看的懂。
  5. 之前我还想到什么来的,为什么现在想不起来了?

新问题:刚才测试了以下,客户端忘记强制强制指定为utf-8编码,导致中文无法输入。好,我懒的更改了。

Python小白,欢迎吐槽。(我还在用print来debug!!!)

最后是下载源代码下载链接:http://sdrv.ms/YYS6FC    (使用MIT License)。

服务器端运行环境:Python 2.7,Twisted 12.2

客户端运行环境:Python 2.7,wxPython 2.8

 

wxPython界面布局小经验

年初第一次做wxPython程序的时候都是使用size和position参数对界面元素进行了固化操作。最近看《Python基础教程(第2版)》时发现了一个更好的界面布局方法:使用proportion参数使得界面元素自行根据窗口大小进行调整,再配合与flag中各类Style,就可以轻松写出布局干净的程序界面。

参数:proportion

功能:根据窗口改变大小时所分配的空间设置界面元素比例。

所属方法:wx.BoxSizer

 

参数:flag

功能:设置界面元素的Style

所属方法:wx.BoxSizer

 

下面时一段小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/env python
#coding:utf-8
 
import wx
import sys
 
class LoginDialog(wx.Dialog):
    def __init__(self):
        wx.Dialog.__init__(self,None,title="Login",size=(280,140))
        self.InitUI()
        self.ShowModal()
 
    def InitUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        nameLabel = wx.StaticText(self,label="      Name:")
        nameText = wx.TextCtrl(self)
        ipLabel = wx.StaticText(self,label="Server IP:")
        ipText = wx.TextCtrl(self)
 
        loginButton = wx.Button(self,label="Login")
        exitButton = wx.Button(self,label="Exit")
 
        loginButton.Bind(wx.EVT_BUTTON,self.OnLogin)
        exitButton.Bind(wx.EVT_BUTTON,self.OnExit)
 
        hbox_1 = wx.BoxSizer()
        hbox_1.Add(nameLabel,proportion=0,flag=wx.ALL | wx.EXPAND | wx.ALIGN_LEFT,border=2)
        hbox_1.Add(nameText,proportion=2,flag=wx.ALL | wx.EXPAND |wx.ALIGN_CENTER,border=2)
 
        hbox_2 = wx.BoxSizer()
        hbox_2.Add(ipLabel,proportion=0,flag=wx.ALL | wx.EXPAND | wx.ALIGN_LEFT,border=2)
        hbox_2.Add(ipText,proportion=2,flag=wx.ALL | wx.EXPAND | wx.ALIGN_CENTER,border=2)
 
        hbox_3 = wx.BoxSizer()
        hbox_3.Add(loginButton,proportion=0,flag=wx.ALL | wx.EXPAND | wx.ALIGN_CENTER,border=2)
        hbox_3.Add(exitButton,proportion=0,flag=wx.ALL | wx.EXPAND | wx.ALIGN_CENTER,border=2)
 
        vbox.Add(hbox_1,proportion=1,flag=wx.ALL | wx.EXPAND,border=2)
        vbox.Add(hbox_2,proportion=1,flag=wx.ALL | wx.EXPAND,border=2)
        vbox.Add(hbox_3,proportion=0,flag=wx.ALL | wx.ALIGN_CENTER,border=2)
 
        self.SetSizer(vbox)
 
    def OnLogin(self,e):
        self.Destroy()
 
    def OnExit(self,e):
        self.Destroy()
        sys.exit()
 
if __name__ == '__main__':
    app = wx.App()
    dlg = LoginDialog()
    app.MainLoop()

解释1:proportion是指BoxSizer绑定界面元素时界面元素所占BoxSizer的比例,默认proportion的值为0。在同一个BoxSizer中的界面元素会根据proportion的值进行自动调整元素的大小。也就是说proportion值越大,该元素所占空间就越多。那么当我们进行为界面布局进行编写代码时,只需合理的安排名proportion值即可实现各元素间的布局。

解释2:为了使proportion参数能够其作用,实现界面元素的自动大小调整,flag参数的wx.EXPAND值就非常重要。若proportion的值大于0(不包括0),那么flag参数中就应该有wx.EXPAND值。

下面是本例的运行图:

 

有任何疑问可评论探讨。欢迎指错。