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

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

python 写 im聊天 python 聊天程序_java

服务器

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

#!/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_address
        Thread(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.' % name
    client.send(bytes(welcome, "utf8"))
    msg = "%s has joined the chat!" % name
    broadcast(bytes(msg, "utf8"))
    clients[client] = name
     while 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 (在询问主机地址之后) ,类似于下面的屏幕截图:

python 写 im聊天 python 聊天程序_socket_02

客户端 GUI

python 写 im聊天 python 聊天程序_socket_03

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

·  END  ·

HAPPY LIFE