欢迎关注 “小白玩转Python”,发现更多 “有趣”

本篇文章分享如何用相当简洁的 Python 代码制作一个简单的聊天应用程序。更重要的是,我已经实现了没有任何第三方依赖的代码!

首先,我创建了一个聊天服务器,通过它可以接收来自希望进行通信的客户机的传入请求。为此,我使用了很好的 ole’sockets 和一些多线程。使用像 Twisted 和 SocketServer 这样的框架是一种选择,但是对于像我们这样简单的软件来说,功能似乎有点太庞大了。

服务器

以下是我们如何开始我们的服务器脚本(对于这个应用程序,只有两个脚本:一个用于服务器,另一个用于客户端)):

#!/usr/bin/env python3
"""Server for multithreaded (asynchronous) chat application."""
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread

为此我们将使用 TCP sockets,因此我们使用 AF_INET 和 SOCK_STREAM 标志。我们通过 UDP sockets 使用它们,因为它们更像电话通讯,在通信开始之前接收者必须批准传入的连接,而 UDP sockets 更像是邮件类型(任何人都可以向任何地址为他/她知道的接收者发送邮件) ,所以它们在通信开始之前并不真正需要建立连接。显然,TCP 比 UDP 更适合我们的目的,因此我们使用它们。

在导入之后,我们设置了一些常量供以后使用:

clients = {}
addresses = {}
HOST = ''
PORT = 33000
BUFSIZ = 1024
ADDR = (HOST, PORT)
SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR)

现在,我们将服务分解为接受新的连接、广播消息和处理特定的客户端。让我们从接受新的连接开始:

def accept_incoming_connections():"""Sets up handling for incoming clients."""while True:client, client_address = SERVER.accept()print("%s:%s has connected." % client_address)client.send(bytes("Greetings from the cave!"+"Now type your name and press enter!", "utf8"))addresses[client] = client_addressThread(target=handle_client, args=(client,)).start()

这只是一个永远等待传入连接的循环,一旦得到一个连接,它就记录连接(打印一些连接细节)并向连接的客户端发送一条欢迎消息。然后它将客户端的地址存储在字典 addresses 中,然后启动该客户端的处理线程。当然,我们还没有为此定义目标函数 handle_client() ,但是我们是这样做的:

def handle_client(client):  # Takes client socket as argument."""Handles a single client connection."""name = client.recv(BUFSIZ).decode("utf8")welcome = 'Welcome %s! If you ever want to quit, type {quit} to exit.' % nameclient.send(bytes(welcome, "utf8"))msg = "%s has joined the chat!" % namebroadcast(bytes(msg, "utf8"))clients[client] = namewhile True:msg = client.recv(BUFSIZ)if msg != bytes("{quit}", "utf8"):broadcast(msg, name+": ")else:client.send(bytes("{quit}", "utf8"))client.close()del clients[client]broadcast(bytes("%s has left the chat." % name, "utf8"))break

当然,在我们给新客户发送欢迎信息后,它会回复一个他/她想用来进一步交流的名字。在 handle_client()函数中,我们要做的第一个任务是保存这个名称,然后根据进一步的指示向客户机发送另一条消息。在这之后是通信的主循环:在这里我们接收来自客户端的进一步消息,如果消息中没有包含退出指令,我们只需将消息广播给其他连接的客户端(我们将在稍后定义广播方法)。如果我们确实遇到带有退出指令的消息(例如,客户端发送{ quit }) ,我们将相同的消息回发给客户端(它在客户端触发关闭操作) ,然后关闭它的连接套接字。然后,我们通过删除客户端的条目来进行一些清理,最后给这个特定的人已经离开会话的其他联系人一个响应。

现在来看看我们的 broadcast()函数:

def broadcast(msg, prefix=""):  # prefix is for name identification."""Broadcasts a message to all the clients."""for sock in clients:sock.send(bytes(prefix, "utf8")+msg)

这几乎是不言自明的,它只是将 msg 发送给所有连接的客户机,并在必要时添加一个可选的 prefix。我们确实在 handle_client() 函数中为 broadcast() 传递了一个 prefix,我们这样做是为了让人们能够准确地看到特定消息的发送者是谁。

这就是我们服务器所需的全部功能。最后,我们放入一些代码来启动我们的服务器并侦听传入的连接:

if __name__ == "__main__":SERVER.listen(5)  # Listens for 5 connections at max.print("Waiting for connection...")ACCEPT_THREAD = Thread(target=accept_incoming_connections)ACCEPT_THREAD.start()  # Starts the infinite loop.ACCEPT_THREAD.join()SERVER.close()

我们 join() ACCEPT_THREAD,这样主脚本就会等待它完成,而不会跳到下一行,下一行将关闭服务器。

客户端

这是更有趣的,因为我们将编写一个 GUI!我们使用 Tkinter,Python 的“batteries included”GUI 构建工具来实现我们的目的。让我们先做一些导入:

#!/usr/bin/env python3
"""Script for Tkinter GUI chat client."""
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
import tkinter

现在我们将编写处理消息发送和接收的函数:

def receive():"""Handles receiving of messages."""while True:try:msg = client_socket.recv(BUFSIZ).decode("utf8")msg_list.insert(tkinter.END, msg)except OSError:  # Possibly client has left the chat.break

为什么又是一个无限循环?因为我们将不确定地接收信息,并且与我们如何以及何时发送信息无关。我们不希望这是一个对讲机聊天应用程序,只能发送或接收在一个时间; 我们希望接收消息时,我们可以,并发送他们当我们想。循环中的功能非常简单; recv()是阻塞部分。它会停止执行,直到它接收到一条消息,当它接收到消息时,我们继续前进,并将消息附加到 msg list 中。我们将很快定义 msg_list,它基本上是 Tkinter 的一个特性,用于在屏幕上显示消息列表。

接下来,我们定义 send() 函数:

def send(event=None):  # event is passed by binders."""Handles sending of messages."""msg = my_msg.get()my_msg.set("")  # Clears input field.client_socket.send(bytes(msg, "utf8"))if msg == "{quit}":client_socket.close()top.quit()

我们使用 event 作为参数,因为当图形用户界面上的 send 按钮被按下时,Tkinter 会隐式地传递事件。my_msg 是 GUI 上的输入字段,因此我们提取要发送给我们的消息 g msg = my_msg.get()。之后,我们清除输入字段,然后将消息发送到服务器,正如我们之前看到的,服务器将此消息广播到所有客户机(如果它不是退出消息)。如果是退出消息,我们关闭 socket,然后关闭 GUI 应用程序(通过 top.close())

我们定义了另一个函数,当我们选择关闭 GUI 窗口时,它将被调用。这是一个类似于关闭前清理的函数,应该在 GUI 关闭前关闭 socket 连接:

def on_closing(event=None):"""This function is to be called when the window is closed."""my_msg.set("{quit}")send()

这将输入字段设置为{ quit } ,然后调用 send() ,这样可以按预期工作。现在我们开始在主命名空间(即任何函数之外)构建 GUI。我们首先定义顶级小部件并设置它的标题:

top = tkinter.Tk()
top.title("Chatter")

然后我们创建一个框架来保存消息列表。接下来,我们创建一个字符串变量,主要用于存储我们从输入字段获得的值(我们将很快定义这个值)。我们将该变量设置为“在这里键入您的消息”提示用户写下他们的信息。然后,我们创建一个滚动条来滚动这个消息框。下面是代码:

messages_frame = tkinter.Frame(top)
my_msg = tkinter.StringVar()  # For the messages to be sent.
my_msg.set("Type your messages here.")
scrollbar = tkinter.Scrollbar(messages_frame)  # To navigate through past messages.

现在我们定义消息列表,它将存储在 messages_frame 中,然后将我们创建的所有内容(在适当的位置)打包:

msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
msg_list.pack()
messages_frame.pack()

然后,我们创建输入字段,让用户输入他们的消息,并将其绑定到上面定义的字符串变量。我们还将其绑定到 send()函数,以便每当用户按下 return 键时,消息就被发送到服务器。接下来,如果用户希望通过单击发送消息,我们将创建 send 按钮。同样,我们将单击此按钮绑定到 send()函数。同时我们也包装我们刚刚创建的所有东西。此外,不要忘记使用 on_closing() 的清理函数,当用户希望关闭 GUI 窗口时,应该调用这个函数。我们使用 top 的 protocol 方法来实现。下面是所有这些的代码:

entry_field = tkinter.Entry(top, textvariable=my_msg)
entry_field.bind("<Return>", send)
entry_field.pack()
send_button = tkinter.Button(top, text="Send", command=send)
send_button.pack()
top.protocol("WM_DELETE_WINDOW", on_closing)

现在差不多完成了。我们还没有编写连接到服务器的代码。为此,我们必须向用户询问服务器的地址。我只需要使用 input()就可以做到这一点,因此在 GUI 开始之前,用户会收到一些命令行提示,询问主机地址。这可能有点不方便,您可以为此添加图形用户界面。下面是我的代码:

HOST = input('Enter host: ')
PORT = input('Enter port: ')
if not PORT:PORT = 33000  # Default value.
else:PORT = int(PORT)
BUFSIZ = 1024
ADDR = (HOST, PORT)
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

一旦我们得到地址并创建一个 socket 连接到它,我们就开始线程接收消息,然后我们的 GUI 应用程序的主循环:

receive_thread = Thread(target=receive)
receive_thread.start()
tkinter.mainloop()  # Starts GUI execution.

这样!我们已经编写了聊天应用程序。

演示

在多台计算机上进行测试感觉很棒。当然,您可以在同一台机器上运行服务器和客户机进行测试(在客户端中使用127.0.0.1 for HOST) ,但是看到不同计算机之间实时进行通信感觉非常棒。服务器脚本将记录访问它的 IP 地址,客户端脚本将生成一个 GUI (在询问主机地址之后) ,类似于下面的屏幕截图:

客户端 GUI

连接到同一服务器的另一个客户端

·  END  ·

HAPPY LIFE

使用 Python 编写一个聊天小程序相关推荐

  1. python socket能做什么_用python写一个聊天小程序!和女朋友的专属聊天工具!

    原标题:用python写一个聊天小程序!和女朋友的专属聊天工具! 1.UDP简介 Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP).UDP为应用程序提供了无需建立就可 ...

  2. python用程序说爱你_用python写一个聊天小程序!和女朋友的专属聊天工具!

    1.UDP简介 Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP).UDP为应用程序提供了无需建立就可以发送封装的IP数据包的方法. Internet的传输层有两个协议 ...

  3. 用python写一个聊天小程序!和女朋友的专属聊天工具!

    1.UDP简介 Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP).UDP为应用程序提供了无需建立就可以发送封装的IP数据包的方法. PS:如有需要Python学习资料 ...

  4. 用python编写一个爱心小程序

    无聊时写了一个爱心小程序,自己编写了一个相对简单点的,当然还可以添加其它的内容,比如文字什么的. 最终得到的图案 代码如下: import turtle import random p = turtl ...

  5. python写小程序-用python编写一个闹钟小程序

    今天看到了一个闹钟的视频,于是想到了不如自己也编写一个.让我们先来理一下思路,界面用tkinter编辑,主要是其他的GUI也没用过,datetime获取本地时间...... 1.好的,思路理完了,让我 ...

  6. python编程100个小程序-用python编写一个闹钟小程序

    今天看到了一个闹钟的视频,于是想到了不如自己也编写一个.让我们先来理一下思路,界面用tkinter编辑,主要是其他的GUI也没用过,datetime获取本地时间...... 1.好的,思路理完了,让我 ...

  7. python做好的程序如何变成小程序-使用python编写简单的小程序编译成exe跑在win10上...

    每天的工作其实很无聊,早知道应该去IT公司闯荡的.最近的工作内容是每逢一个整点,从早7点到晚11点,去查一次客流数据,整理到表格中,上交给素未蒙面的上线,由他呈交领导查阅. 人的精力毕竟是有限的,所以 ...

  8. java 获取文件所在的文件夹_带你0基础编写一个Java小程序,领略Java程序从编写到编译再到运行的全流程...

    在学习Java之前我们需要先认识下什么是计算机语言?计算机语言又有哪些分类?在了解这些后对我们理解学习帮助是很大的. 要知道计算机语言是人与计算机之间进行信息交流沟通的一种特殊语言,又分为机械语言.汇 ...

  9. 请用python编写一个斗地主小游戏,并给出完整代码

    很高兴为您服务!这是一个使用Python编写的斗地主小游戏的完整代码:# 导入必备模块 import random# 定义一副牌 poker = ['A', 2, 3, 4, 5, 6, 7, 8, ...

  10. Python 开发一个间谍小程序

    作者:寂夜云 https://www.cnblogs.com/lonenysky/p/12341074.html 这次我们使用 Python 来打造一款间谍程序,程序中会用到许多知识点,大致分为四块: ...

最新文章

  1. 记住:用户不是傻*,她是你的老婆大人
  2. C# 模拟鼠标移动与点击
  3. anaconda matplotlib 输出动画_Python+Matplotlib 制作排序算法的动画
  4. 【Leetcode | 48】226. 翻转二叉树
  5. Flutter加载Html并实现与JS 的双向调用
  6. Linux 下编译、安装、配置 QT
  7. linux设备驱动 注册 命令6,Linux设备驱动程序学习----6.模块的参数
  8. 解读设计模式----迭代器模式(Iterator Pattern),谁才是迭代高手
  9. hi模板文件报乱码问题
  10. B+树 mysql
  11. python-os库函数一些用法记录
  12. 35岁逃离北上广,40岁失业送外卖,中年人的“体面”在于投资自己
  13. UML10种图例之包图
  14. EDA实验(3)计数器设计
  15. 史上最详细How to Use Time Information Effectively Combining with Time Shift Module for Lipreading文章记录
  16. matlab画图五角星标记,Matlab---画图线型、符号及颜色
  17. 工作后,同学关系渐渐疏远了,心里莫名有些失落,怎么办?
  18. 添加项目到debug调试
  19. 天地图墨卡托在线地址
  20. 虚拟森林火场生成及蔓延模拟

热门文章

  1. Linux启动系统时不启动防火墙,Linux系统启动并配置防火墙的方法
  2. 新书出版 |《数据库程序员面试笔试宝典》
  3. RubyonRails on linux配置
  4. 张小龙《微信背后的产品观》
  5. 字体编码—Unicode16进制编码转字符
  6. 百度云链接后的html,百度云链接失效,这样就能找回!
  7. 使用Pytorch来拟合函数
  8. 使用Magoshare for Mac无法打开恢复的文件或扫描后找不到丢失的文件怎么办?
  9. 高中数学建模优秀论文_高中数学建模优秀论文
  10. 创客匠人打造在线课堂,助力内容变现