第一个程序——构建一个ServerUI

简介

本次程序设计均使用python实现,使用sql server对聊天室用户的数据进行存储。通过python socket套接字编程,实现了在线聊天室的功能,并使用python tkinter进行UI界面的设计。

思路

由计算机网络的基础知识易知,两个主机之间通信的协议可以是TCP,也可以是UDP。其中TCP需要通过三次握手在两台主机之间建立连接,然后两台主机之间才可以通信;而UDP则是无连接的。显然,对于这样一个简单的程序,使用TCP和UDP作为通信协议均可,本次程序设计选择的是TCP。

  • 首先面对的问题是,我们想要编写的程序,是一个在线聊天室,用户可以在聊天室内广播消息(即群聊),也可以向聊天室中的某人私发消息(即好友小窗聊天)。
  • 那么显然,只通过单纯的TCP连接是无法满足我们的需求的:因为我们是想让客户端与服务器之间构建TCP连接,主机发出请求,服务器接到请求后和它建立连接,客户明确知道这个服务器是在线聊天室,而服务器却无法得知用户是哪位用户,只能通过服务器socket.accept()接受连接请求后,得知客户端的套接字信息和IP地址及端口号。
    在这里插入图片描述
  • 因此,为了解决这个问题,我们因此了本程序在设计时最关键的思路:以服务器作为中继端,它不仅能够接受信息,还能够转发信息。当客户端向服务器发送信息时,把这条信息编码,为它加上我们人为自定义的前缀。我设计的前缀是目标前缀,即“在服务器中广播/向服务器中另一个用户私发信息”( 私发时,前缀即为能唯一标识目标用户的信息;群发时则是BROADCASTING )
    在这里插入图片描述
    这样,服务器变成了一个后台,在用户视图下(ClientUI),用户可以看到其他用户群发的信息,也能够看到谁在给它私发信息。而对于服务器,它的任务是向用户转发信息 (显然,服务器知道每个连接到它的用户的IP和端口,由于用户在发送信息时,其信息被编码,目标地址作为前缀被编码进入了发给服务器的信息,而服务器端解码,将地址和信息拆包,服务器拿到地址后,如果这个地址是群发地址,则将信息再次编码<将发送方的地址编码进这条信息,以让接收方得知是谁在群发消息>,发送给所有用户;否则,服务器将地址编码<将发送方的地址编码进这条信息,以让接收方得知是谁在向它私发消息>,将信息转发给目标用户) ,并在服务器端显示聊天日志(方便维护与程序测试)。 在这里插入图片描述
    👆客户端广播信息“嗡嗡嗡”;
    在这里插入图片描述
    👆客户端广播“嗡嗡嗡”,在服务器端也能看见这条广播信息,并得知是谁在发送广播信息。如果两个客户进行私聊,服务器端仍然可以对私发信息进行监听。
    在这里插入图片描述
    👆现在在两个客户端之间私发消息,由“猪头”向“电棍”发送“喂喂喂”。
    在这里插入图片描述
    👆“电棍”收到消息。
    在这里插入图片描述
    👆由“猪头”发送给“狗狗”,“电棍”收不到消息。同时由于“狗狗”没上线,服务器会反馈。
    在这里插入图片描述
    👆服务器会监听每条聊天记录。

以上便是本次程序设计中对于服务器ServerUI的设计思路。

ServerUI及其相关源码

socket.py

程序清单中的socket.py,定义了两个类及若干程序中必要的编码解码函数。其中包括客户端的Client以及服务器的Server。

import socket
"""
	①Server类中初始化了套接字socket;
	②开设序列socs,用于存放(append)主动连接至服务器端的客户端套接字信息;
	③ip和port为服务器的ip地址和端口,由于本次程序是在本机上进行仿真操作的,因此ip
	默认设置为自检环回地址127.0.0.1,端口可以自定义;
	④bind指将ip和端口与套接字s绑定;
	⑤listen设置为128,即最多接入128个用户。
"""
class Server():
    def __init__(self,ip,port):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socs = []
        self.ip = ip
        self.port = port
        # self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.s.bind((self.ip, self.port))
        self.s.listen(128)

class Client():
    def __init__(self,ip,port):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.ip = ip
        self.port = port
        self.s.bind((self.ip, self.port))

"""
	👇三个编码解码函数
	①wencoding是将目标地址信息编码进发送信息的编码器函数;
	②w_oriencoding (Short for "w Original encoding") 是用于“当发送方向用户私发信息,
	而该用户不在线”时的编码器。
	③wdecoding是将wencoding编码信息解码的解码器。
"""

def wencoding(addr,data):
    res = addr[0]
    res += '|'
    res += str(addr[1])
    res += '|'
    res += data
    return res
def w_oriencoding(data):
    res = "1|2|"
    res += data
    return res

def wdecoding(data):
    data = tuple(data.split('|'))
    addr = (data[0], int(data[1]))
    data = data[2]
    return addr, data

ServerUI

调用库

from tkinter import *
from tkinter import messagebox
from Socketer import *
from threading import Thread
from tkinter import scrolledtext
import datetime
"""
	inspect和ctypes用于终止进程。
"""
import inspect
import ctypes

终止进程函数_async_raise(tid, exctype) && stop_thread(thread)

当服务器下线时,由于程序仍然没有结束,因为我们可以让服务器重新上线,因此在程序中我们需要终止服务器相关进程(显然需要终止的至少有连接监听进程)。

def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)

界面设计ServerUI类

没什么好说的,ServerUI类使用tkinter进行了UI界面的设计,可以调整UI界面中按钮、输入框、文本显示框及文本的位置与格式。

class ServerUI(object):
    GUI = None
    server_soc = None
    port = None
    text = None
    isOn = False
    host_ip = "127.0.0.1"
    def __init__(self):
        self.GUI = Tk()
        self.GUI.title("Server")
        self.GUI.geometry('500x460')
        self.GUI.wm_resizable(False,False)
        Label(self.GUI, text='服务端IP地址:' + self.host_ip,font=(20)).place(relx=.5, y=15, anchor="center")
        Label(self.GUI, text='服务端端口号:' ,font=(20)).place(relx=.3, y=50, anchor="center")
        self.port = Entry(self.GUI, width=10)
        self.port.place(relx=.5, y=50, anchor="center")
        Button(self.GUI,width=10,text='上线/下线',command=self.on_or_off).place(relx=.7, y=50, anchor="center")
        self.text = scrolledtext.ScrolledText(self.GUI, width=78, height=22, font=('Times New Roman', 10))
        self.text.place(relx=.5, y=240, anchor="center")
        btn = Button(self.GUI, text='清空', font=('黑体', 14), height=1, command=lambda: self.text.delete("1.0", "end"))
        btn.place(relx=.5, y=440, anchor="center")
        self.GUI.mainloop()

在这里插入图片描述
实际上,以上程序控制的是这个👆UI界面的布局。

重头戏:Application_ServerUI类的实现

先看下这个类的定义以及初始化函数👇。

class Application_ServerUI(ServerUI):
    def __init__(self):
        ServerUI.__init__(self)

实际上,ServerUI是Application_ServerUI类的子类👇。
在这里插入图片描述
在这里插入图片描述
在ServerUI中给出了使用tkinter设计UI布局的定义,而在Application_ServerUI中,我则给出了每个定义下的实现(如,某个按钮应该与哪些行为绑定。可以在ServerUI中预设接口,然后在Application_ServerUI中定义函数来实现)。

完整实现如下:

class Application_ServerUI(ServerUI):
    def __init__(self):
        ServerUI.__init__(self)

    def connection_accepted(self):
        while True:
            client_soc, addr = self.server_soc.s.accept()
            self
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值