这学期要交一个作品,我同学打算做一个聊天软件,一个聊天软件无非就是两个程序:一个客户端,一个服务器。服务器我之前写出来了,不清楚的可以参考一下这篇文章:虚拟茶话会(2):再次实现
下面来开始编写客户端,在编写客户端之前,我们首先来想一下之前那个服务器我是如何进行连接测试的,很明显是命令提示符下使用telnet。但是这学期交的作品必须要有图形界面,所以客户端必须自己实现。我同学在实现这个客户端时总是出现各种问题,而且我看他的代码有些也不懂,主要是因为里面有一个我只是听说还没有用过的模块——telnetlib。我首先是上网查了一下telnetlib模块,结果发现它是用来处理telnet连接的。telnet连接现在已经用得非常少了,因为它是明文传输,极度不安全!但是拿它做课程设计绰绰有余。本来我不想学这个模块,但是为了帮他完成这次的课程设计,就当是多学一个模块吧。今天我就是稍微学一下这个模块,不会写一个GUI客户端,文章最后也只能先写一个cmd客户端,GUI客户端先让他自己尝试,他要不会我就下周末写一个给他参考,如果他在下周末之前弄出来GUI客户端那我就不继续了。
言归正传,我们首先来看一下telnetlib模块到底是什么,这个模块里有哪些东西。首先import telnetlib,然后help(telnetlib)即可,如图所示。
稍微翻译一下NAME下面那句话:telnetlib——TELNET客户端类。然后就是描述和一个例子,这些不用管。直接看例子下面的Note那一段,为了确保连贯性,我把Note那一段和它后面几段放一起。
我稍微翻译一下这几段内容:
注意:read_all方法直到结束才开始读——它只是读一些数据——但是它保证至少读一个字节除非碰到了结束符。
将Telnet对象传递给选择器为了等待直到有更多数据可用是可能的。注意在这种情况下,即使过去在套接字上有数据,read_eager方法可能会返回一个空字节,因为协议可能会把数据吃了。这就是有些情况下需要EOFError来区分“无数据”和“连接关闭”的原因(因为套接字在关闭时似乎在准备读取)。
按如下要求做:
)
然后就是模块中有哪些类,可以发现这个模块中只有一个类——这个类继承自object。下面来看一下这个类的定义以及它的一些方法是如何使用的。
稍微翻译一下这一部分的内容,从class Telnet下面一行开始。
Telnet连接类
这个类的一个实例代表一个指向telnet服务器的连接,这个实例一开始没有连接;open方法必须被用来建立一个连接。当然,主机名和可选择的端口号也可以传递给构造器。
不要尝试重新打开一个已经有连接的实例。
这个类有许多read_打头的方法。注意:当连接的结束位置被读取,它们中有一些会引发EOFError异常,因为有其他原因,它们会返回一个空字符串。请看单个方法的文档字符串。
read_until(expected, [timeout])
一直读,知道出现了被期望的字符串,或者碰见超时(默认没有超时);可以阻塞。
read_all()
读取所有数据直到结束;可以阻塞。
read_some()
至少读一个字节或者读到结束;可以阻塞。
read_very_eager()
读取所有已经排好队(在一个队列里)或者在套接字上的可用数据,没有阻塞。
read_eager()
读取部分已经排好队的数据,或者一些在套接字上的可用数据,没有阻塞。
read_lazy()
读取所有在原始队列中的数据(这些数据需要先处理),没有执行任何套接字的I/O操作。
read_very_lazy()
读取被处理过的数据所在的队列中的所有数据,没有执行任何套接字的I/O操作。(这里也是瞎翻译的
)
read_sb_data()
读取在(SB...SE)序列之间的可用数据,不能阻塞。
set_option_negotiation_callback(callback)
每次一个telnet选项在输入流上被读取,这个callback参数(如果被设置)伴随着如下参数被调用:
callback(telnet socket, command, option)
当没有option参数时,option参数会是chr(0)(就是0的ASCII码——'\0')。
然后,没有其他操作被telnetlib执行。
方法被定义在这里:
__del__(self)
析构器——关闭这个连接。
__enter__(self)
__exit__(self, type, value, traceback)
__init__(self, host=None, port=0, timeout=<object object at 0x00000123DDE7B120>)
构造器。
当被调用时没有参数,创建一个无连接的实例。如果有一个主机名参数,它会连接这个实例,端口号和超时是可选参数。
close(self)
关闭这个连接。
expect(self, list, timeout=None)
一直读,直到和正则表达式列表中的一个元素所匹配。
第一个参数是一个正则表达式列表,可以是被编译过的(re.RegexObject实例),也可以是没有被编译的(字符串)可选的第二个参数是超时,单位秒;默认没有超时。
返回三个元素构成的一个元组:与读取内容所匹配的正则表达式在列表中的第一个索引,被返回的匹配对象,以及包括匹配到的字符串在内的所有读取的数据。
如果结束符被读取并且没有内容之前被读取,引发EOFError异常。否则,当没有匹配时,返回(-1, None, text)在这里text参数是目前被接收到的内容(如果出现了超时,可能是一个空字符串)。
如果一个正则表达式以贪婪匹配(例如'.*')结束,或者如果有不止一个正则表达式可以匹配相同的输入信息,这个结果是不确定的,可能取决于I/O时序。
fileno()
返回被套接字对象内部使用的文件描述符。
fill_rawq(self)
确切的说,通过调用一个名叫recv的系统函数来填充原始队列。如果短时间内没有数据可用,就阻塞。当连接被关闭时,设置Telnet类的实例的eof属性。
get_socket(self)
返回一个被内部使用的套接字对象。
interact(self)
交互功能,模拟一个非常愚蠢的telnet客户端。
listener(self)
关于mt_interact()的帮手——这个函数在另一个线程中执行。
msg(self, msg, *args)
当调试等级大于0时,输出一个调试信息。
如果有额外的参数,它们在使用标准字符串格式运算符的消息中被替代。
mt_interact(self)
interact(self)方法的多线程版本。
open(self, host, port=0, timeout=<object object at 0x0000024C0F5DB750>)
连接到一台主机。
可选的第二个参数是端口号(默认是标准的telnet端口号(端口:23))。
不要尝试重新打开一个已经被连接的实例。
process_rawq(self)
从原始数据队列转移到被处理过的数据所在的队列,当连接被关闭时,设置Telnet类的实例的eof属性,除非在应用间通信的序列中,否则不能阻塞。
rawq_getchar(self)
从原始队列中获取下一个字符。
如果短时间内没有可用数据,就阻塞。当连接被关闭时,引发EOFError异常。
read_all(self)
读取所有数据直到结束;一直阻塞直到连接关闭。
read_eager(self)
毫不犹豫地读取可用数据。
如果连接被关闭,并且没有可用的已经被处理的数据,就引发EOFError异常。在其他情况下,如果没有被处理的可用数据,就返回空字节。除非在应用间通信的序列中,否则不能阻塞。
read_lazy(self)
处理并返回已经在队列中的数据(惰性)。
如果连接被关闭,并且没有可用数据,就引发EOFError异常。在其他情况下,如果没有可用数据,就返回空字节。除非在应用间通信的序列中,否则不能阻塞。
read_sb_data(self)
返回任何在SB...SE序列中的可用数据。
如果没有可用的SB...SE序列,返回空字节。应该仅仅只在看见SB或者SE命令后被调用。当一个新的SB命令被发现,老的不能识别的SB命令将会被抛弃。不能阻塞。
read_some(self)
除非读到了结束符,否则至少读取被处理的数据的一个字节。
如果读到了eof,返回空字节。如果短时间内没有可用数据就阻塞。(有意思,这个函数一会可能要用,因为我想通过阻塞控制GIL锁,此时接收数据行为的雏形已经出现了。)
read_until(self, match, timeout=None)
一直读,直到遇见一个被传入的字符串或者超时。
当没找到match时,可能会返回一个空字符串而不是任何可用的东西。如果连接被关闭并且没有被处理的数据可用,就引发EOFError异常。
read_very_eager(self)
读取可能的所有东西,没有I/O阻塞(急切)。
如果连接被关闭并且没有被处理的数据可用,就引发EOFError异常。在其他情况下如果没有被处理的数据可用就返回空字节。除非在一个应用间通信的序列中,否则不能阻塞。
read_very_lazy(self)
返回任何在被处理过的数据所在的队列中的所有可用数据(非常懒惰)。
如果连接被关闭并且没有可用数据,就引发EOFError异常。在其他情况下如果没有被处理的可用数据,就返回空字节。不能阻塞。
set_debuglevel(self, debuglevel)
设置调试级别。
级别越高,(在标准输出流上)你得到的调试输出信息就越多。
set_option_negotiation_callback(self, callback)
提供一个在每次接收到一个telnet选项后被调用的回调函数。
sock_avail(self)
测试在套接字上是否有数据可用。
write(self, buffer)
向套接字中写入一个字符串,折叠任何应用间通信的字符。
如果连接被阻塞,就可以阻塞。如果连接被关闭,可能会引发OSError异常。(此时发送数据的行为的雏形也出现了)。
(上面的翻译可能会有小错误,尽请谅解!)不用往下继续看了,发送数据的操作和接收数据的操作都已经清楚了。这个客户端使用两个线程,一个负责发送数据,一个接收接收数据。发送数据的操作是一个死循环,如果要发送的数据是"logout "打头的一行字符串,就结束这个操作。接收数据的操作也是一个死循环,如果碰到了结束位置,就结束该操作。最终的代码如图所示。
通过注释,大家理解起来应该不难,我就不做详细的解释了。下面看一下运行结果。运行客户端之前一定要先运行服务器!!!
差不多了,我暂时先给出一个GUI客户端的实现思路,首先是登录界面,登录界面就是三个单行输入和三个提示输入的文本框(主机名,端口号和用户名),一个执行登录的按钮(登陆成功跳转到聊天室,登录失败就弹出信息)。然后就是聊天室界面,聊天室界面有两个文本框,其中一个是用于接受数据的多行只读文本框,还有一个是单行用于发送数据的可编辑文本框。另外还有四个按钮——发送数据,查看聊天室里都有谁,查看谁已登录,退出。
本文分享自 Python机器学习算法说书人 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!