简介
本次程序设计均使用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个用户。
"""classServer():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)classClient():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编码信息解码的解码器。
"""defwencoding(addr,data):
res = addr[0]
res +='|'
res +=str(addr[1])
res +='|'
res += data
return res
defw_oriencoding(data):
res ="1|2|"
res += data
return res
defwdecoding(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)ifnot 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")defstop_thread(thread):
_async_raise(thread.ident, SystemExit)
界面设计ServerUI类
没什么好说的,ServerUI类使用tkinter进行了UI界面的设计,可以调整UI界面中按钮、输入框、文本显示框及文本的位置与格式。
classServerUI(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类的实现
先看下这个类的定义以及初始化函数👇。
classApplication_ServerUI(ServerUI):def__init__(self):
ServerUI.__init__(self)
实际上,ServerUI是Application_ServerUI类的子类👇。
在ServerUI中给出了使用tkinter设计UI布局的定义,而在Application_ServerUI中,我则给出了每个定义下的实现(如,某个按钮应该与哪些行为绑定。可以在ServerUI中预设接口,然后在Application_ServerUI中定义函数来实现)。
完整实现如下:
classApplication_ServerUI(ServerUI):def__init__(self):
ServerUI.__init__(self)defconnection_accepted(self):whileTrue:
client_soc, addr = self.server_soc.s.accept()
self.text.insert(END,'[{}]/连接客户端的IP地址:{}/端口:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],))
self.server_soc.socs.append((client_soc,addr))
client_thread = Thread(target=self.recv_msg, args=([(client_soc, addr)]))
client_thread.start()defrecv_msg(self,soc):
client_soc = soc[0]
addr = soc[1]whileTrue:try:
data = client_soc.recv(1024)except:
self.text.insert(END,'[{}]/客户端IP地址:{}/端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],"已断开连接。"))break
data = data.decode('utf-8')try:
dest_addr, data = wdecoding(data)except:continueif dest_addr[0]!="BROADCASTING"and dest_addr != self.host_ip:#不是广播,而是私聊
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n发送数据为:{}\n接收端IP地址:{}/端口:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data,dest_addr[0],dest_addr[1]))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr, data]))
send2dest_threading.start()elif dest_addr == self.host_ip:
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n{}\n服务器已接收。\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr,"服务器已接收"]))
send2dest_threading.start()elif dest_addr[0]=="BROADCASTING":
self.text.insert(END,'[{}]\n[广播]发方IP地址:{}/发方端口:{}\n发送数据为:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],data))
broadcasting_threading = Thread(target=self.broadcasting,args=([soc, data]))
broadcasting_threading.start()defbroadcasting(self,ori_soc,data):#服务端进行广播
ori_addr = ori_soc[1]for soc_addr inenumerate(self.server_soc.socs):#遍历当前每个与服务端建立TCP连接的客户端套接字
soc = soc_addr[1][0]
addr = soc_addr[1][1]
sd_data = wencoding(ori_addr, data)if addr != ori_addr:try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]defsend2dest(self,ori_soc,dest_addr,data):#单独转发,根据发方的目的地址
ori_socket = ori_soc[0]
ori_addr = ori_soc[1]
found =Falsefor soc_addr inenumerate(self.server_soc.socs):
soc = soc_addr[1][0]
addr = soc_addr[1][1]if addr == dest_addr:
sd_data = wencoding(ori_addr, data)try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]
found =Truebreakifnot found:
self.text.insert(END,'[{}]发方IP地址:{}/发方端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ori_addr[0], ori_addr[1],"收方未连接。"))
send_msg = w_oriencoding("{}".format("收方未连接。"))
ori_socket.send(send_msg.encode("utf-8"))defconnection_accepted_threading(self):
self.t = Thread(target=self.connection_accepted,args=())
self.t.setDaemon(True)
self.t.start()defon_or_off(self):ifnot self.isOn:iflen(self.port.get())==0:
messagebox.showerror(title="未输入端口",message="未输入端口")else:
port = self.port.get()
port =int(port)
self.server_soc = Server(self.host_ip,port)
self.isOn =True
messagebox.showinfo(title="服务器已上线", message="服务器已上线!")
self.text.insert(END,"[{}]/服务器已上线,IP地址为".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+ self.server_soc.ip +",等待客户端连接。\n")
self.connection_accepted_threading()else:if self.t.is_alive():
stop_thread(self.t)
self.isOn =False
self.server_soc.s.close()
messagebox.showinfo(title="服务器已下线", message="服务器已下线!")
self.text.insert(END,"[{}]/服务器已下线。".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+"\n")
self.GUI.destroy()
由于以上代码片段涉及多线程、线程之间的交互以及线程开始的先后顺序,因此我按照线程开始的先后捋顺一下代码。
①首先,当我们设置好端口号,点击ServerUI界面中的“上线”按钮时,服务器上线。在ServerUI类中我们绑定这个按钮的行为是
self.on_or_offf
,因此先来看这个函数:
defon_or_off(self):ifnot self.isOn:#实际上,此处设置为异常处理(try && except)更合理iflen(self.port.get())==0:#检查端口是否填写。
messagebox.showerror(title="未输入端口",message="未输入端口")else:#正常填写,则上线。
port = self.port.get()
port =int(port)
self.server_soc = Server(self.host_ip,port)#初始化Server类,即Socket.py中定义的Server类
self.isOn =True
messagebox.showinfo(title="服务器已上线", message="服务器已上线!")
self.text.insert(END,"[{}]/服务器已上线,IP地址为".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+ self.server_soc.ip +",等待客户端连接。\n")
self.connection_accepted_threading()#服务器上线,开始监听连接请求else:if self.t.is_alive():
stop_thread(self.t)
self.isOn =False
self.server_soc.s.close()
messagebox.showinfo(title="服务器已下线", message="服务器已下线!")
self.text.insert(END,"[{}]/服务器已下线。".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+"\n")
self.GUI.destroy()
👆服务器上线后,开始监听连接请求,通过
self.connection_accepted_threading()
这个线程。因此我们来关注这个线程及其对应的函数。
②监听连接请求:
其线程如下,对应的函数为
self.connection_accepted()
。
defconnection_accepted_threading(self):
self.t = Thread(target=self.connection_accepted,args=())
self.t.setDaemon(True)
self.t.start()
因此我们再来看这个函数。
defconnection_accepted(self):whileTrue:
client_soc, addr = self.server_soc.s.accept()
self.text.insert(END,'[{}]/连接客户端的IP地址:{}/端口:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],))
self.server_soc.socs.append((client_soc,addr))
client_thread = Thread(target=self.recv_msg_threading, args=([(client_soc,addr)]))
client_thread.start()
显然,需要一直监听连接请求,因此
server_soc.s.accept()
设置在永真循环中。涉及到了永真循环,必然需要将这个循环开设在一个线程中,否则将导致程序阻塞。接收到了请求后,将其拆包为客户端的socket和地址addr,将客户端套接字append到服务器的socs序列中,以让服务器得知当前在线的用户有哪些;之后,由于客户端连接成功,服务器开始对它发送的消息进行监听与转发,因此开启client_thread进程对这个用户进行持续监听。client_thread对应的函数是recv_msg_threading,我们来看这个函数。
③接受客户端信息:
defrecv_msg(self,soc):"首先👇,将soc拆包为用户套接字和地址。client_soc = soc[0]"
addr = soc[1]whileTrue:try:"如果正常接收到了信息:"
data = client_soc.recv(1024)except:
self.text.insert(END,'[{}]/客户端IP地址:{}/端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],"已断开连接。"))break"👇将收到的信息通过utf-8解码(当然,发送前也需要utf-8编码)。"
data = data.decode('utf-8')try:"👇前缀拆包。"
dest_addr, data = wdecoding(data)except:continueif dest_addr[0]!="BROADCASTING"and dest_addr != self.host_ip:#不是广播,而是私聊
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n发送数据为:{}\n接收端IP地址:{}/端口:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data,dest_addr[0],dest_addr[1]))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr, data]))
send2dest_threading.start()elif dest_addr == self.host_ip:
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n{}\n服务器已接收。\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr,"服务器已接收"]))
send2dest_threading.start()elif dest_addr[0]=="BROADCASTING":
self.text.insert(END,'[{}]\n[广播]发方IP地址:{}/发方端口:{}\n发送数据为:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],data))
broadcasting_threading = Thread(target=self.broadcasting,args=([soc, data]))
broadcasting_threading.start()
👆显然,发送的信息可以是发送给目标用户或服务器的,也可以是广播的。如果是前者,使用send2dest线程发送给目标,否则使用broadcasting线程广播(我们不希望发送消息这一行为对主进程产生阻塞)。
④私聊信息的转发:
defsend2dest(self,ori_soc,dest_addr,data):#单独转发,根据发方的目的地址"ori_socket和addr是发方地址,让收方知道发方是谁。"
ori_socket = ori_soc[0]
ori_addr = ori_soc[1]
found =False"从soc_addr序列中寻找收方。"for soc_addr inenumerate(self.server_soc.socs):"""
由于使用了enumerate,因此soc_addr是一个序列索引元组,其soc_addr[0]是索引
号,而soc_addr[1]才是socs序列对应索引号位置的当前连接到服务器的套接字信息。
"""
soc = soc_addr[1][0]
addr = soc_addr[1][1]"👇判断是否找到,找到则发送,否则提示发方收方未连接。"if addr == dest_addr:
sd_data = wencoding(ori_addr, data)try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]
found =Truebreakifnot found:
self.text.insert(END,'[{}]发方IP地址:{}/发方端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ori_addr[0], ori_addr[1],"收方未连接。"))
send_msg = w_oriencoding("{}".format("收方未连接。"))
ori_socket.send(send_msg.encode("utf-8"))
⑤广播:
defbroadcasting(self,ori_soc,data):#服务端进行广播
ori_addr = ori_soc[1]for soc_addr inenumerate(self.server_soc.socs):#遍历当前每个与服务端建立TCP连接的客户端套接字
soc = soc_addr[1][0]
addr = soc_addr[1][1]
sd_data = wencoding(ori_addr, data)if addr != ori_addr:try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]
👆基本上与私发流程相同,不过需要通过for循环遍历每个连接到服务器端的套接字,以发送信息。
启动程序
在main函数中使用
Application_ServerUI()
即可实例化一个服务器类👇。
if __name__ =='__main__':
Application_ServerUI()
完整的ServerUI
from tkinter import*from tkinter import messagebox
from Socketer import*from threading import Thread
from tkinter import scrolledtext
import datetime
import inspect
import ctypes
def_async_raise(tid, exctype):"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)ifnot 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")defstop_thread(thread):
_async_raise(thread.ident, SystemExit)classServerUI(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()classApplication_ServerUI(ServerUI):def__init__(self):
ServerUI.__init__(self)defconnection_accepted(self):whileTrue:
client_soc, addr = self.server_soc.s.accept()
self.text.insert(END,'[{}]/连接客户端的IP地址:{}/端口:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],))
self.server_soc.socs.append((client_soc,addr))
client_thread = Thread(target=self.recv_msg, args=([(client_soc, addr)]))
client_thread.start()defrecv_msg(self,soc):
client_soc = soc[0]
addr = soc[1]whileTrue:try:
data = client_soc.recv(1024)except:
self.text.insert(END,'[{}]/客户端IP地址:{}/端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],"已断开连接。"))break
data = data.decode('utf-8')try:
dest_addr, data = wdecoding(data)except:continueif dest_addr[0]!="BROADCASTING"and dest_addr != self.host_ip:#不是广播,而是私聊
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n发送数据为:{}\n接收端IP地址:{}/端口:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data,dest_addr[0],dest_addr[1]))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr, data]))
send2dest_threading.start()elif dest_addr == self.host_ip:
self.text.insert(END,'[{}]\n发方IP地址:{}/发方端口:{}\n{}\n服务器已接收。\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],
data))
send2dest_threading = Thread(target=self.send2dest, args=([soc,dest_addr,"服务器已接收"]))
send2dest_threading.start()elif dest_addr[0]=="BROADCASTING":
self.text.insert(END,'[{}]\n[广播]发方IP地址:{}/发方端口:{}\n发送数据为:{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), addr[0], addr[1],data))
broadcasting_threading = Thread(target=self.broadcasting,args=([soc, data]))
broadcasting_threading.start()defbroadcasting(self,ori_soc,data):#服务端进行广播
ori_addr = ori_soc[1]for soc_addr inenumerate(self.server_soc.socs):#遍历当前每个与服务端建立TCP连接的客户端套接字
soc = soc_addr[1][0]
addr = soc_addr[1][1]
sd_data = wencoding(ori_addr, data)if addr != ori_addr:try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]defsend2dest(self,ori_soc,dest_addr,data):#单独转发,根据发方的目的地址
ori_socket = ori_soc[0]
ori_addr = ori_soc[1]
found =Falsefor soc_addr inenumerate(self.server_soc.socs):
soc = soc_addr[1][0]
addr = soc_addr[1][1]if addr == dest_addr:
sd_data = wencoding(ori_addr, data)try:
soc.send(sd_data.encode('utf-8'))except:
soc.close()del self.server_soc.socs[soc_addr[0]]
found =Truebreakifnot found:
self.text.insert(END,'[{}]发方IP地址:{}/发方端口:{},{}\n'.format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ori_addr[0], ori_addr[1],"收方未连接。"))
send_msg = w_oriencoding("{}".format("收方未连接。"))
ori_socket.send(send_msg.encode("utf-8"))defconnection_accepted_threading(self):
self.t = Thread(target=self.connection_accepted,args=())
self.t.setDaemon(True)
self.t.start()defon_or_off(self):ifnot self.isOn:iflen(self.port.get())==0:
messagebox.showerror(title="未输入端口",message="未输入端口")else:
port = self.port.get()
port =int(port)
self.server_soc = Server(self.host_ip,port)
self.isOn =True
messagebox.showinfo(title="服务器已上线", message="服务器已上线!")
self.text.insert(END,"[{}]/服务器已上线,IP地址为".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+ self.server_soc.ip +",等待客户端连接。\n")
self.connection_accepted_threading()else:if self.t.is_alive():
stop_thread(self.t)
self.isOn =False
self.server_soc.s.close()
messagebox.showinfo(title="服务器已下线", message="服务器已下线!")
self.text.insert(END,"[{}]/服务器已下线。".format(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+"\n")
self.GUI.destroy()if __name__ =='__main__':
Application_ServerUI()
以上便是ServerUI设计的全部内容,稍后对ClientUI客户端的设计进行详解。
版权归原作者 perSistence92 所有, 如有侵权,请联系我们删除。