kivy-ch4-chat-app

Kivy网络编程

前面我们尝试过单一平台Android的Kivy开发,通过原生的底层API来实现。下面我们探索另外一种天生具有跨平台能力的工具来做app——网络。在这一章,我们做一个聊天app,类似于QQ,但是简单版。

当然这个应用不能替代QQ,不过支持类似QQ群聊天功能,方便临时性的会议建群聊天。

为了简化,我们不实现认证授权功能,这是为彼此都很熟悉的用户设计的。如果你想让app更安全,自己可以实现一下。

为了在服务器端支持最大兼容性,这个app用Telnet收发消息。虽然不是Kivy的app的图形用户界面,Telnet可以在Windows 95和MS-DOS完美运行。

更严谨的考证一下,其实Telnet在1973年就标准化了,因此它甚至可以在8086 CPU上运行。Windows 95和MS-DOS相比之下已经很新了。

本章教学大纲如下:

  • 用Python的Twisted框架实现服务器端。
  • 在不同的抽象层面上开发两个客户端,一个是通过套接字实现命令行程序,一个是通过Twisted的事件驱动客户端实现的程序
  • 用Kivy的ScreenManager更容易的实现UI
  • 做一个ScrollView容器实现消息的无限长度

我们的应用将使用中心化的客户端-服务器架构,很多网站和应用都用这种主流的互联网方法论。与去中心化的P2P网络相比,你很快会发现这种方法是多么的容易。

这里没有区分互联网与局域网(local area network,LAN),但是两者在底层上没啥关系。但是,如果你要把应用发布到应用商店,你还需要准备很多其他的内容,比如设置一个安全网络服务器,配置防火墙来保证你的代码可以扩展到多核处理器和其他设备。实际上这并没有多可怕,但是仍然需要一些努力。

实现聊天服务器

开始写客户端之前我们先把服务器端做出来。我们用Twisted框架来提高效率,通过Python来实现,这样可以避开许多常见的、底层的网络任务。

兼容性提示 Twisted目前仍然不能很好的不支持Python3,所以这里得使用Python2.7。其实2to3很容易移植,因为没多少不兼容的设计方式。(不过,我们完全忽视了Unicode相关的问题,因为Python2和Python3的处理方式不同。如果是中文,还是Python3方便。)

Twisted是一个事件驱动的底层的服务器框架,不像Node.js(实际上,Node.js的设计受到Twisted的影响)。与Kivy很类似,事件驱动的架构意味着我们不需要把代码构建一个无限循环,相反我们只要为app绑定大量的事件监听器就行。许多底层的网络处理,都可以通过Twisted方便的实现。

和其他Python包一样,用pip就可以安装Twisted:

pip install -U twisted

有两点要注意:

  • 安装的时候得取得管理员权限(sudo)
  • 如果没装pip,easy_install也可以,Python2.7.9和Python3.4版本已经自带pip了

协议定义

现在我们来看下聊天服务器即将使用的通信协议,因为我们的app并不复杂,所以我们不用XMPP那样主流的、功能丰富的程序,我们自己实现一个简单的就行。

我们实现的协议层就是两条信息在客户端和服务器传递——连接服务器(进入聊天室),对其他人说话。服务器会反馈客户端传递的每件事情;服务器自己不会产生任何事件。

我们的协议执行原文传递,和很多其他的应用层如HTTP协议一样。这样做很合理,因为调试和实现起来都很方便。字符串协议比二进制码协议更具扩展性和前瞻性(future-proof)。缺点就是包压缩率底,占用资源多;二进制协议可以更紧凑。不过在这个app讨论这样不太重要,也可以通过压缩技术缓解这个不足(这就是为啥很多服务器都是HTTP协议)。

现在,让我们梳理一下每条消息在应用协议的思路:

  • 连接到服务器的信息只有用户现在是否在聊天室,每次就用一个单词CONNECT来检测。这个信息不需要参数化,直接用单词。
  • 在聊天室说话更有趣。有两个参数:用户名和文字信息。让我们把格式定义为A:BA就是用户名(我们要求用户名不能包含分号:字符)。

根据这个思路,我们写出下面的算法(伪代码):

In [ ]:
if ':' not in message
    then
        // it's a CONNECT message
        add this connection to user list
    else
        // it's a chat message
        nickname, text := message.split on ':'
        for each user in user list
            if not the same user:
                send "{nickname} said: {text}"

对同一个用户的测试就是把向用户自己传递的信息去掉。

服务器代码

有了Twisted的帮助,我们的伪代码可以很直接的写出Python代码。下面就是我们应用的server.py文件:

In [ ]:
from twisted.internet import protocol, reactor

transports = set()

class Chat(protocol.Protocol):
    def dataReceived(self, data):
        transports.add(self.transport)
        
        if ':' not in data:
            return
        user, msg = data.split(':', 1)
        
        for t in transports:
            if t is not self.transport:
                t.write('{0} says: {1}'.format(user, msg))

class ChatFactory(protocol.Factory):
    def buildProtocol(self, addr):
        return Chat()

reactor.listenTCP(9096, ChatFactory())
reactor.run()

设计原理

下面的操作流程可以帮助你理解上面的代码:

  • 最后一行reactor.run()开启ChatFactory服务器监听9096端口
  • 当服务器收到请求,就调用dataReceived()响应
  • dataReceived()方法就是前面伪代码的实现,会把消息发送给其他用户

每个到客户端的连接构成集合transports。我们无条件的把当前的传递self.transport加入集合。

然后就是算法的实现。最后,聊天室内除了发消息的每个用户都会收到提示,< username>说了<message text>。

注意我们并没有用CONNECT来检查连接的信息。这是按照1980年Jon Postel在TCP说明书里面提出的网络稳健性(network robustness)原则设计的:保守的发送,自由的接收。 另外通过简化代码,我们还获得了更好的兼容性。在未来要发布新版本客户端时,如果我们给协议增加一个新消息,假设叫WHARRGARBL消息,名字看着就很酷。没有崩溃是因为虽然收到来一个格式错误的消息(这是由于版本不匹配),老版本的服务器会直接忽略这些消息继续工作。 这些具体的版本兼容性问题可以通过许多策略来纠正。但是,还有更多来自网络尤其是公网的难题,包括用户恶意攻击拖垮服务器等等。所以,实际中并没有服务器非常稳定这种可能。

服务器测试

直接运行Python文件就可以测试服务器:

python server.py

这个命令的结果不会直接看到。服务器开始运行,等待客户请求。但是,我们还没客户端程序。那怎么测试呢?

这种先有服务器还是先有客户端的经典问题有很多方法可以解决——向服务器发信息,然后显示服务器反馈的信息。

处理字符串协议服务器的一个标准化工具就是Telnet,一般都是命令行,没有图像界面。很多操作系统都带有Telnet。在Windows系统中,打开“控制面板|程序和功能|启动或关闭Windows功能”就可以找到Telnet。

Telnet

Telnet有两个参数:服务器地址和端口。为了连接到Telnet,你需要先启动server.py,然后再打开命令行输入:

telnet 127.0.0.1 9096

另外,你也可以用localhost代替127.0.0.1,在hosts文件中是默认设置。

现在就可以测试服务器了。你可以根据前面设计流程,向服务器发送内容进行实现测试:

CONNECT
User A:Hello, world!

没有出现任何反馈,因为我们没有让服务器向原作者反馈信息。因此,我们需要打开另外一个命令行,然后以一个新的用户登录。就可以看到User A发送的信息了。如下图所示:

servertest

如果你没法儿正常测试Telnet也甭灰心,这不影响咱们app的顺利完成。 如果用Windows的话,给一点小建议:最好给电脑装个Mac OS或Linux,双系统更适合研发工作,推荐使用虚拟机,切换方便。

这样,我们就知道服务器可以正常工作了。现在我们来做客户端系统的GUI。

屏幕管理器

现在我们用一个新概念来设计UI,叫屏幕管理器。用来设计我们的app合适。一共是两个UI状态:

  • 登录界面:包括服务器地址、用户名、登录按钮 uilogin
  • 聊天界面:包括信息显示、信息输入、发送信息按钮和端口服务器按钮 uilogin

从理论上说,我们的app界面就是这样。这种UI分离的设计方法涉及到,对不同UI状态里可见的与隐藏的控件的管理。这样可以快速的组合一堆部件,而不要写任何代码。

Kivy为我们提供ScreenManager来实现UI设计。另外,ScreenManager还提供了屏幕切换的动态过程,以及大量的内置转换方式。可以完全通过Kivy语言来实现,不需要任何Python代码。

下面让我们来实现,首先建立chat.kv文件:

ScreenManager:
    Screen:
        name: 'login'

        BoxLayout:
            # other UI controls -- not shown
            Button:
                text: 'Connect'
                on_press: root.current = 'chatroom'

    Screen:
        name: 'chatroom'

        BoxLayout:
            # other UI controls -- not shown
            Button:
                text: 'Disconnect'
                on_press: root.current = 'login'

这是程序的基本结构:我们建立ScreenManager根部件,并为每个状态建立一个Screen容器。容器里面有上面看到的布局、按钮。后面我们会继续完善。

代码里面看到还包括Screen的按钮。为了切换应用的状态,我们还需要设置ScreenManagercurrent属性。

屏幕切换动作

前面提到,屏幕可以通过切换动作的动态显示。Kivy提供了许多切换功能,在kivy.uix.screenmanager包里面:

Transition class name Visual effect
NoTransition 没有动画,直接显示屏幕
SwapTransition 滑到下一屏幕,用上、下、左(默认)、右。
SwapTransition iOS屏幕切换效果
FadeTransition 褪色方式切换
WipeTransition 用1px的遮挡实现平滑的定向切换
FallOutTransition 将旧屏幕缩小到屏幕中间,渐渐透明,再出现新屏幕
RiseInTransition FallOutTransition完全相反,新屏幕从中间出现,放大直到遮住旧屏幕

.lv文件里面设置这些切换动作时需要注意一旦:切换需要手动导入。

#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition

现在,你就可以配置ScreenManager了。注意这些动作都是Python类的实例,所以后面要加括号:

ScreenManager:
    transition: RiseInTransition()

登录界面布局

登录界面布局和前一章的录音app类似:用一个GridLayout把各个元素按照网格排列。

还没用过的部件就是文本框TextInput。Kivy的文本框和按钮基本完全一样,区别就是可以输入文字。默认情况下是多行显示,因为在聊天app里面多行少见(如微信、QQ),所以要设置multilineFalse

在无键盘设备上运行时,Kivy会调用虚拟键盘,和原生应用一样。下面的代码就是登录界面布局:

Screen:
    name: 'login'
    BoxLayout:
        orientation: 'vertical'
        GridLayout:
            Label:
                text: 'Server:'
            TextInput:
                id: server
                text: '127.0.0.1'
            Label:
                text: 'Nickname:'
            TextInput:
                id: nickname
                text: 'Kivy'
        Button:
            text: 'Connect'
            on_press: root.current = 'chatroom'

这里,我们增加了ServerNickname两个文本框,对应的标签和按钮。按钮的事件handler还有没有任何功能,后面会实现。

可以让单行的TextInput更好看点,我们让文本框里面的文字垂直居中:

<TextInput>:
    multiline: False
    padding: [10, 0.5 * (self.height – self.line_height)]

padding属性设置了左右的边距都是10,上面和下面的边距是文本框高度与一行文本高度之差的一半。下面就是效果图,可前面app的界面类似:

loginscreen

现在我们可以写代码来连接服务器了,不过之前我们先把聊天主界面做出来。这样我们就可以直接在上面测试了。

聊天界面布局

聊天界面布局使用了ScrollView实现对话长度的切换,因为是第一次说这个空间,下面会详细介绍:

<ChatLabel@Label>:
    text_size: (self.width, None) # Step 1
    halign: 'left'
    valign: 'top'
    size_hint: (1, None) # Step 2
    height: self.texture_size[1] # Step 3
ScrollView:
    ChatLabel:
        text: 'Insert very long text with line\nbreaks'

文字满屏之后,滚动条会出现,类似于在Android和iOS里面看到的。具体的设计流程如下:

  1. 我们用text_size属性设置Label子类的宽度,把第二个参数高度设置成None,允许无限长度
  2. size_hint属性第二个参数设置为None,允许无限长度,迫使其高度与它的容器ScrollView独立。但是,它的长度会受到上一层的元素的限制,因此不会滚动。
  3. 设置部件的高度等于texture_size属性高度(注意索引都从0开始,因此第二个元素是texture_size[1])。这就迫使ChatLabel比包含它的ScrollView部件大
  4. ScrollView部件发现它的子部件比它的空间大时,滚动条就出现了。这和手机上看到的一样,桌面系统也支持鼠标滚轮操作。
滚动模式

你还可以为ScrollView设置滚动的效果来模仿原生平台的特点(不过和原生的效果还是有差别)。可以实现的效果如下:

  • ScrollEffect:当触及最底部的时候可以停止滚动,类似于桌面应用常见的功能
  • DampedScrollEffect:这是默认的效果,类似于iOS,非常适合移动设备
  • OpacityScrollEffect:与DampedScrollEffect效果类似,滚动时增加了滚动条的透明度,不会遮挡内容

要使用这些效果,从kivy.effects模块导入效果,然后配置到ScrollView.effect_cls属性,与ScreenManager切换效果类似。本章app不改,就用默认效果,可以自行设置。

把上述内容综合起来,chat.kv文件代码如下:

Screen:
    name: 'chatroom'
    BoxLayout:
        orientation: 'vertical'
        Button:
            text: 'Disconnect'
            on_press: root.current = 'login'
        ScrollView:
            ChatLabel:
                id: chat_logs
                text: 'User says: foo\nUser says: bar'
        BoxLayout:
            height: 90
            orientation: 'horizontal'
            padding: 0
            size_hint: (1, None)
            TextInput:
                id: message
            Button:
                text: 'Send'
                size_hint: (0.3, 1)

最后一行的size_hint属性设置了按钮的水平比例为0.3,默认的是1。这就会让发送按钮比文本框小。

为了把消息的背景色设置成白色的,我们可以这样:

<ScrollView>:
    canvas.before:
        Color:
            rgb: 1, 1, 1
        Rectangle:
            pos: self.pos
            size: self.size

这就在其他元素绘制之前为ScrollView铺上了白色。别忘记了调整一下<ChatLabel>类,把背景色设置成浅色背景:

#:import C kivy.utils.get_color_from_hex

<ChatLabel@Label>:
    color: C('#101010')

效果图如下: Chatroomscreen

这里Disconnect按钮就是断开网络连接。这是下一章的内容,到时候你会发现,用Python实现简单网络程序的难度,与用Kivy建立简单的UI没啥区别。

连接网络

下面我们连接服务器来收发消息,并显示给用户。

首先,我们看一个纯Python实现的聊天客户端,用套接字就可以实现。不过还是推荐用高级工具,如Twisted;如果你对这类知识没有一点儿概念,可能有点小困难,坚持一下就会好,多试几次准明白。

一个简单Python客户端

下面我们将用readline()函数读取用户的消息,然后通过print()函数显示在命令行上。这和Telnet没啥区别——都是命令行界面显示消息——只是我们从底层的套接字开始做起。

这需要一些标准模块:socketsyssys.stdin提供输入文件接口)和select模块等待消息出现。新建一个客户端文件client.py

In [ ]:
import select, socket, sys

这个程序没有其他依赖关系;所有平台的Python都支持。

不过Windows里面的select,由于其代码实现方式不同,不能把文件描述器调整为套接字接受的样式。所以这个客户端就不能运行了,不过这个客户端我们最后也不会用,所以不要担心,如果你用的是Windows。

现在,我们来连接服务然后用CONNECT对接:

In [ ]:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 9096))
s.send('CONNECT')

然后就是等待两类消息,一类是标准输入(用户输入的文字),一类s套接字(服务器发送消息)。等待可以用select.select()实现:

In [ ]:
rlist = (sys.stdin, s)
while True:
    read, write, fail = select.select(rlist, (), ())
    for sock in read:
        if sock == s: # receive message from server
            data = s.recv(4096)
            print(data)
        else: # send message entered by user
            msg = sock.readline()
            s.send(msg)

然后,服务器根据收到的最新数据进行反馈,我们可以把服务器发送的信息显示到屏幕上,或者如果是用户发送的消息就发送到服务器。其实这就是Telnet做的事情,只是缺少错误异常检测部分。

你会发现底层的模块实现客户端并没有我们想象的困难。但是相比高级模块如Twisted,原始套接字代码还是冗长的。但是这里显示了客户端工作的原理,其他任何高级工具都是这里实现的,高级工具也不过是通过底层的套接字实现,然后加上方便使用的API而已。

注意这里我们没有加异常检测部分,因为代码可能增加2-3倍,感兴趣的可以练习一下。 网络是非常容易出错的;比其他IO都脆弱。因此,如果你打算做一个类似于Skype那样的商业软件,你的代码里面将充斥异常检测和测试:比如丢包,防火墙问题等等。不论你的架构设计的多么好,网络服务想获得极高的可靠性很难。

用Twisted实现

纯Python代码写的客户端不太适合Kivy,原因就是主循环部分(while True:)。要让这个循环与Kivy的事件循环协同运作,还得做些事情。

不过,Twisted的优势可以很好的弥补这一点,实现同样的网络模块可以同时作用于服务器和客户端,使得代码更统一。关键在于Twisted可以与Kivy的事件循环协同运作,首先让我们把Twisted相关模块导入:

In [ ]:
from kivy.support import install_twisted_reactor
install_twisted_reactor()

from twisted.internet import reactor, protocol

这段代码要放在main.py文件的最上面。下面我们用Twisted来实现:

ChatClient和ChatClientFactory

用Twisted实现工作量很少,因为这个框架为网络相关的每一件事情都做了很好的接口,这些类通过简单的连接就可以完成工作。

ClientFactory的子类ChatClientFactory可以在初始化阶段储存Kivy app的实例,这样我们就可以向它传递事件。代码如下:

In [ ]:
class ChatClientFactory(protocol.ClientFactory):
    protocol = ChatClient
    
    def __init__(self, app):
        self.app = app

ChatClient类监听Twisted的connectionMadedataReceived事件,然后发送到Kivy app:

In [ ]:
class ChatClient(protocol.Protocol):
    def connectionMade(self):
        self.transport.write('CONNECT')
        self.factory.app.on_connect(self.transport)
        
    def dataReceived(self, data):
        self.factory.app.on_message(data)

注意那个无所不在的CONNECT握手信号。

这和前面的套接字写法很不同,是吧?而且和前面的server.py很像。但是,我们只是把事件传递给aoo对象,并没有处理事件。

加入UI

要看到app的全貌,我们还要把UI也加进来。chat.kv文件如下:

Button: # Connect button, found on login screen
    text: 'Connect'
    on_press: app.connect()
Button: # Disconnect button, on chatroom screen
    text: 'Disconnect'
    on_press: app.disconnect()
TextInput: # Message input, on chatroom screen
    id: message
    on_text_validate: app.send_msg()
Button: # Message send button, on chatroom screen
    text: 'Send'
    on_press: app.send_msg()

注意按钮不会再切换屏幕了,相反它们调用app的方法,类似于ChatClient事件处理。

完成这些之后,我们需要实现Kivy应用类里面的5个方法:两个是Twisted代码中的服务器生成事件(on_connecton_message),三个是用户接口事件(connectdisconnectsend_msg)。这样才能让聊天app真正可以用。

客户端的设计思路

当我们简单写一些程序运行逻辑:从connect()disconnect()

connect()方法里面,我们把ServerNickname参数作为用户输入。Nickname参数被储存到self.nick,Twisted客户端连接到具体的服务器地址:

In [ ]:
class ChatApp(App):
    def connect(self):
        host = self.root.ids.server.text
        self.nick = self.root.ids.nickname.text
        reactor.connectTCP(host, 9096, ChatClientFactory(self))

调用ChatClient.connectionMade()函数,把控件传递到on_connect()方法上。我们将用事件来储存self.conn连接,然后切换屏幕。前面提到过,按钮不再直接切换屏幕,而且通过事件handler实现:

In [ ]:
# From here on these are methods of the ChatApp class
def on_connect(self, conn):
    self.conn = conn
    self.root.current = 'chatroom'

现在主要部分就是收发信息。很简单,就是从TextInput发信息,把self.nick加上,发送给服务器。最后把信息显示出来,并且清空TextInput

In [ ]:
def send_msg(self):
    msg = self.root.ids.message.text
    self.conn.write('%s:%s' % (self.nick, msg))
    self.root.ids.chat_logs.text += ('%s says: %s\n' % (self.nick, msg))
    self.root.ids.message.text = ''

接受消息更简单,因为我们不会保持这些内容,所有就是把消息显示到屏幕上:

In [ ]:
def on_message(self, msg):
    self.root.ids.chat_logs.text += msg + '\n'

最后一个方法就是disconnect():关闭连接,清理所有内容回到初始界面。这样用户就可以重新连接其他服务器了。

In [ ]:
def disconnect(self):
    if self.conn:
        self.conn.loseConnection()
        del self.conn
    self.root.current = 'login'
    self.root.ids.chat_logs.text = ''

这样程序就搞定了。

提示: 测试的时候,server.py文件应该持续运行,但是我们的app就不能终止连接了。最终结果就是app停留在登录界面,不再调用on_connect(),用户也不能到聊天室界面。 还有,在Android上面测试的时候,确定你的服务器IP地址,不是127.0.0.1,只要局域网设备才这样,在Android设备上不一样。可以用ifconfig查询(Windows上是ipconfig)。

客户端交互

前面做的Telnet、两个客户端虽然实现方式不同,却可以通信,因为其底层的原理基本一致。

类似于互联网的处理方式:只要你用HTTP协议,相关的服务器和客户端就可以交互:网页服务器、浏览器、搜索引擎等等。

协议是更高级的API,与语言、系统无关,应该选一个流行的用。并不是每个网络开发者都熟悉微软2007年发布的Silverlight协议,但大家都知道1991年发布的HTTP。

增强视觉体验

现在app已经可以运行了,我们把聊天窗口改善一下。可以用Kivy的BBCode来修饰。

让我们给每个用户加个颜色,这样方便用户区分所有人。我们同样使用扁平化UI的配色方式。

当前用户发送的信息不会从服务器发给自己,是通过客户端代码添加到对话内容里的。所以,我们要把当前用户名加一个固定的颜色。

In [ ]:
colors = ['7F8C8D', 'C0392B', '2C3E50', '8E44AD', '27AE60']

class Chat(protocol.Protocol):
    def connectionMade(self):
        self.color = colors.pop()
        colors.insert(0, self.color)

通过一个无限循序,我们把颜色依次加到用户名上,循环使用。如果你熟悉Python的itertools模块,你可以这么写:

In [ ]:
import itertools
colors = itertools.cycle(('7F8C8D', 'C0392B', '2C3E50', '8E44AD', '27AE60'))
def connectionMade(self):
    self.color = colors.next()
    # next(colors) in Python 3

现在,我们再把颜色添加到用户名上,很简单,就是[b][color]Nickname[/ color][/b]

In [ ]:
for t in transports:
    if t is not self.transport:
        t.write('[b][color={}]{}:[/color][/b] {}'.format(self.color, user, msg))

main.py里面的客户端也同时更新了。我们还要为当前发消息的用户增加一个固定的颜色:

In [ ]:
def send_msg(self):
    msg = self.root.ids.message.text
    self.conn.write('%s:%s' % (self.nick, msg))
    self.root.ids.chat_logs.text += ('[b][color=2980B9]{}:[/color][/b] {}\n'.format(self.nick, msg))

然后,我们把聊天记录部件ChatLabelmarkup属性设置为True

<ChatLabel@Label>:
    markup: True

这样就可以了。

转义字符处理

和HTML一样,用户发送的消息可以会出现转义字符。比如BBCode之类的符号。要解决这个问题,我们可以用Kivy的kivy.utils.escape_markup来解决。但是还不是很完整,我们可以稍微调整一下:

In [ ]:
def esc_markup(msg):
    return (msg.replace('&', '&amp;')
            .replace('[', '&bl;')
            .replace(']', '&br;'))

这样所有的Kivy里面的转义字符转变成HTML字符替代,这样遇到这些字符时就会不会发生转义。在server.py文件里面,相应的代码需要改变:

In [ ]:
t.write('[b][color={}]{}:[/color][/b] {}'
        .format(self.color, user,esc_markup(msg)))

main.py里面,实现是类似的:

In [ ]:
self.root.ids.chat_logs.text += (
    '[b][color=2980B9]{}:[/color][/b] {}\n'
    .format(self.nick, esc_markup(msg)))

现在bug修复了,用户可以安全的发从BBCode消息了。

chatlast

其实这类bug在互联网产品中很常见。类似于跨站脚本(cross-site scripting,XSS),可以造成比修改字体更恐怖的结果。 不要忘了净化所有产品的用户输入,因为用户一不小心用了命令行就麻烦了。

后续任务

这些都只是开始,还有很多必须的事情没做。比如用户名唯一,没有历史记录和离线消息的支持。如果网络质量不好的话,消息总会丢失。

但是更重要的是,这些问题都可以解决,我们已经有了产品原型。在产品开始阶段,原型是非常具有吸引力,先让轮子转起来,就有了动力;如果你因为好玩而编程,这种感觉更明显,因为你看到了一个可以使用的产品了(相反如果只是一块块代码,不能使用,那感觉很糟糕)。

总结

这一章我们看到了CS架构的应用开发(其实就网络编程)其实也不复杂。甚至底层的套接字代码也很容易搞定。

当然,涉及到网络时会遇到很多灰色地带不容易搞定。包括高时延的处理,中断连接的恢复和多节点的数据同步等(尤其是点对点或多主机时,每一个机器只有一部分数据)。

另一个目前比较新的网络问题就是政治方面的,政府已经开始实施互联网管制,包括出于安全的原因(比如,封杀恐怖主义网络资源)到完全无厘头(封杀教育网站像维基百科,主要的新闻网站或视频游戏网站)。这种连接问题会产生很高的间接伤害,如果CDN(content delivery network)挂了,很多使用CDN链接的网站就不能正常显示了。你懂的。

但是,只要踏踏实实的坚持下去,一定可以克服重重困难把优质产品发布给客户。而且,Python丰富的特性可以减轻你的负担,本章的聊天app已经充分体现了这点:很多底层的细节都可以通过Kivy和Twisted轻松搞定。

互联网领域有无限可能,永无止境。下一章我们继续网络相关的尝试,敬请期待。