赞
踩
命令行形式:
- import socket # 导入socket网络通讯套接字
- from threading import Thread # 导入线程操作包
- s = socket.socket() # 实例化socket套接字,唯一,服务端需要保持套接字常开,不能轻易close(),debug教训
- host = socket.gethostname() # 指定要连接到的IP地址
- port = 3 # TCP端口
- Max_Client = 8 # 服务端所能维持的最大连接数
- s.bind((host, port)) # 绑定本地服务端的IP地址及端口
- s.listen(Max_Client) # 设置最大连接数
- class CommunicateHandle: # 服务端的socket通讯处理类
- def __init__(self): # 类初始化函数,绑定类属性以用于全局调用
- self.con = None # 连接句柄
- self.addr = None # 实例化socket套接字时会返回一个socket操作句柄,即上方的con所要存储的变量,还会返回一个客户机的 “IP地址”
- self.name = None # 用户名
- self.c_n = 0 # 当前的已连入客户数
- self.connections = list() # 用于存储客户机的连接句柄
- self.addrs = list() # 用于存储客户机的IP地址
- self.login_name = list() # 用于存储客户机的用户名
-
- def link_accept(self): # 服务端的连接受理函数,用于接收连接及启动连接子线程
- self.con, self.addr = s.accept() # 调用socket套接字实例的accept函数会返回两个对象,接收这两个对象
- if self.c_n >= Max_Client: # 如果已到达服务端所能承载的最大客户连接数
- print("已到达服务端最大允许连入数,新的连入请求已被拒绝")
- self.con.send("聊天室已满,无法加入".encode(encoding='utf-8'))
- self.con.close()
- else: # 服务端未满员情况下
- self.name = self.con.recv(1024).decode(encoding='utf-8')
- self.c_n += 1
- self.connections.append(self.con) # 将新的客户操作连接置入连接堆
- self.addrs.append(self.addr)
- self.login_name.append(self.name) # 将客户的用户名置入用户名堆
- print("用户:{0}加入聊天室".format(self.name))
- self.broadcast("(用户编号: {0} ): 用户{1} 加入聊天室".format(self.addrs.index(self.addr), self.name))
- print("目前客户机数:{0}".format(self.c_n))
- th = Thread(target=self.message_recv, args=(self.con, self.addr, self.name)) # 为对应的连接分配单独的处理子线程
- th.start()
-
- def message_recv(self, con, addr, name): # 服务端的接收消息函数
- while True:
- position = self.addrs.index(addr)
- msg = con.recv(1024).decode(encoding='utf-8')
- if msg == "exit": # 若用户登出则关闭此条连接,释放线程的持续占用,线程执行完成并被回收
- print("{0}退出了聊天室".format(name))
- self.broadcast("{0}退出了聊天室".format(name))
- self.connections[position].send("exit".encode(encoding="utf-8")) # 接收到客户端发来的exit请求,给予回应
- print("已为其发送exit指令")
- self.connections.remove(con) # 将推出的用户级从服务端存储列中移除
- self.addrs.remove(addr)
- self.login_name.remove(name)
- self.c_n -= 1
- break
- else:
- try:
- if msg[0:2] == "tk" and msg != '':
- # 服务端显示聊天室信息
- print("来自: {0} 的用户 {1} {2}:{3}".format(addr, position, self.login_name[position], msg))
- self.broadcast("(ID:{0}) {1}说: {2}".format(position, self.login_name[position], msg[3:]))
- if msg[0:2] != "tk" and msg != '':
- if type(eval(msg[0:2])) == int:
- print("检测到私聊信息")
- IDF = position # 发送方id
- IDT = eval(msg[0:2]) # 接收方id
- # 服务端检视私聊信息
- print("{0} 对 {1} 说: {2}".format(self.login_name[IDF], self.login_name[IDT], msg[3:]))
- self.private_chat(idf=IDF, idt=IDT, mg=msg[2:])
- except NameError:
- print("接收到的消息格式错误NameError")
- self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
- except IndexError:
- print("接收到的消息格式错误IndexError")
- self.connections[position].send("指定ID不存在".encode(encoding='utf-8'))
- con.close()
-
- def broadcast(self, msg): # 广播函数,服务端使用此函数来向所有的客户机群发信息
- for i in range(0, len(self.connections)):
- self.connections[i].send(msg.encode(encoding='utf-8'))
-
- def private_chat(self, idf, idt, mg): # 私聊函数,idF:发送方ID;idT:接收方ID;msg:信息
- self.connections[idf].send("[Private] 你对 {0} 说: {1}".format(self.login_name[idt], mg).encode(encoding='utf-8'))
- try:
- self.connections[idt].send("[Private] {0}对你说: {1}".format(self.login_name[idf], mg).encode(encoding='utf-8'))
- except IndexError:
- print("私聊发送方的指定ID不存在")
-
- if __name__ == '__main__':
- Instance = CommunicateHandle() # 实例化服务端的网络通讯类
- while True: # 服务端持续探询是否有新的客户连接接入,若有则为其分配一个线程进行处理
- Instance.link_accept() # 实例化socket网络接口,打开接入子线程接口
- import socket
- from threading import Thread
- s = socket.socket() # 实例化socket套接字
- host = socket.gethostname() # 指定要连接到的IP地址
- port = 3 # TCP端口
- login = input("请输入你的用户名:")
- s.connect((host, port)) # 连接指定IP地址的指定端口
- s.send(login.encode(encoding='utf-8')) # 向主机发送本机的用户名
- print("已成功连接到聊天室服务端!")
- class CommunicateHandle: # 客户端的socket通讯处理类
- def __init__(self, connection): # 初始化函数,用于绑定类中的全局属性
- self.connection = connection # 承载socket套接字的连接句柄
- self.login = True # 连接状态判断位
-
- def message_recv(self): # 客户端的信息接收函数
- while self.login:
- mi = self.connection.recv(1024).decode(encoding='utf-8')
- print(mi)
- s.close()
-
- def message_send(self): # 客户端的信息 发送函数
- t = Thread(target=self.message_recv, args=()) # 发送前,先启动信息接收线程
- t.start()
- print("提示:直接输入想要发送的信息即可")
- print("退出聊天室:exit;\n公聊:tk 您要发送的信息;\n私聊:ID(1~99) 您要发送的信息;")
- while self.login: # 不断探询用户的输入
- ms = input()
- if ms == "exit":
- s.send(ms.encode(encoding='utf-8'))
- self.login = False
- else:
- s.send(ms.encode(encoding='utf-8'))
-
- Instance = CommunicateHandle(connection=s) # 实例化客户端的网络通讯类
- t1 = Thread(target=Instance.message_send, args=()) # 启用发送线程
- t1.start()
图形化界面形式:
服务端
- import socket # 导入socket网络通讯套接字
- from threading import Thread # 导入线程操作包
- s = socket.socket() # 实例化socket套接字,唯一,服务端需要保持套接字常开,不能轻易close(),debug教训
- host = socket.gethostname() # 指定要连接到的IP地址
- port = 3 # TCP端口,要与客户端保持一致
- Max_Client = 99 # 服务端所能维持的最大连接数
- s.bind((host, port)) # 绑定本地服务端的IP地址及端口
- s.listen(Max_Client) # 设置最大连接数
- class CommunicateHandle: # 服务端的socket通讯处理类
- def __init__(self): # 类初始化函数,绑定类属性以用于全局调用
- self.con = None # 连接句柄
- self.addr = None # 实例化socket套接字时会返回一个socket操作句柄,即上方的con所要存储的变量,还会返回一个客户机的 “IP地址”
- self.name = None # 用户名
- self.c_n = 0 # 当前的已连入客户数
- self.connections = list() # 用于存储客户机的连接句柄
- self.addrs = list() # 用于存储客户机的IP地址
- self.login_name = list() # 用于存储客户机的用户名
-
- def link_accept(self): # 服务端的连接受理函数,用于接收连接及启动连接子线程
- self.con, self.addr = s.accept() # 调用socket套接字实例的accept函数会返回两个对象,接收这两个对象
- if self.c_n >= Max_Client: # 如果已到达服务端所能承载的最大客户连接数
- print("已到达服务端最大允许连入数,新的连入请求已被拒绝")
- self.con.send("聊天室已满,无法加入".encode(encoding='utf-8'))
- self.con.close()
- else: # 服务端未满员情况下
- self.name = self.con.recv(1024).decode(encoding='utf-8')
- self.c_n += 1
- self.connections.append(self.con) # 将新的客户操作连接置入连接堆
- self.addrs.append(self.addr)
- self.login_name.append(self.name) # 将客户的用户名置入用户名堆
- print("用户:{0}加入聊天室".format(self.name))
- self.broadcast("user_list:{0}".format(self.login_name)) # 传递当前在线用户列表
- print("目前客户机数:{0}".format(self.c_n))
- th = Thread(target=self.message_recv, args=(self.con, self.addr, self.name)) # 为对应的连接分配单独的处理子线程
- th.start()
-
- def message_recv(self, con, addr, name): # 服务端的接收消息函数
- while True:
- msg = con.recv(1024).decode(encoding='utf-8')
- if msg == "exit": # 若用户登出则关闭此条连接,释放线程的持续占用,线程执行完成并被回收
- print("{0}退出了聊天室".format(name))
- self.broadcast("{0}退出了聊天室".format(name))
- self.connections.remove(con) # 将推出的用户级从服务端存储列中移除
- self.addrs.remove(addr)
- self.login_name.remove(name)
- self.c_n -= 1
- self.broadcast("user_list:{0}".format(self.login_name)) # 传递当前在线用户列表
- break
- else:
- position = self.addrs.index(addr)
- try:
- if msg[0:2] == "tk" and msg != '':
- # 服务端显示聊天室信息
- print("来自: {0} 的用户 {1} {2}:{3}".format(addr, position, self.login_name[position], msg))
- # 广播聊天信息
- self.broadcast("(ID:{0}) {1}说: {2}".format(position, self.login_name[position], msg[3:]))
- if msg[0:2] != "tk" and msg != '':
- if type(eval(msg[0:2])) == int:
- print("检测到私聊信息")
- IDF = position # 发送方id
- IDT = eval(msg[0:2]) # 接收方id
- # 服务端检视私聊信息
- print("{0} 对 {1} 说: {2}".format(self.login_name[IDF], self.login_name[IDT], msg[3:]))
- self.private_chat(idf=IDF, idt=IDT, mg=msg[2:])
- except NameError:
- print("接收到的消息格式错误NameError")
- self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
- except IndexError:
- print("接收到的消息格式错误IndexError")
- self.connections[position].send("指定ID不存在".encode(encoding='utf-8'))
- except SyntaxError:
- print("接收到的消息格式错误SyntaxError")
- self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
- con.close()
-
- def broadcast(self, msg): # 广播函数,服务端使用此函数来向所有的客户机群发信息
- for i in range(0, len(self.connections)):
- self.connections[i].send(msg.encode(encoding='utf-8'))
-
- def private_chat(self, idf, idt, mg): # 私聊函数,idF:发送方ID;idT:接收方ID;msg:信息
- if idf != idt:
- self.connections[idf].send("[Private] 你对 (ID:{0})用户 {1} 说: {2}".format(idt, self.login_name[idt], mg).encode(encoding='utf-8'))
- try:
- self.connections[idt].send("[Private] (ID:{0})用户 {1} 对你说: {2}".format(idf, self.login_name[idf], mg).encode(encoding='utf-8'))
- except IndexError:
- print("私聊发送方的指定ID不存在")
- else:
- self.connections[idf].send("请勿私聊自己!".encode(encoding="utf-8"))
-
- if __name__ == '__main__':
- Instance = CommunicateHandle() # 实例化服务端的网络通讯类
- while True: # 服务端持续探询是否有新的客户连接接入,若有则为其分配一个线程进行处理
- Instance.link_accept() # 实例化socket网络接口,打开接入子线程接口
客户端
- import socket
- from threading import Thread
- from tkinter import *
- from tkinter import messagebox
- import sys # 用于退出程序
- '''
- 类: LoginGUI
- 作用: 用于搭建客户端的初始化登入界面
- '''
- class LoginGUI(Frame):
- '''
- 函数: __init__
- 参数: master:用于绑定Tkinter的根窗口
- 作用: 初始化Tkinter的必备类,放置正式界面的GUI组件
- 返回值: 无
- '''
- def __init__(self, master):
- super().__init__()
- self.master = master
- self.pack()
- self.s = None
- self.name = None
- self.GUI_set()
- '''
- 函数: GUI_set
- 参数: 无
- 作用: 放置GUI组件
- 返回值: 无
- '''
- def GUI_set(self):
- self.v1, self.v2, self.v3 = StringVar(), StringVar(), StringVar()
- label1 = Label(self, text="用户名:(必填)")
- label1.pack()
- entry1 = Entry(self, textvariable=self.v1)
- entry1.pack()
- label2 = Label(self, text="聊天室IP地址:")
- label2.pack()
- entry2 = Entry(self, textvariable=self.v2)
- entry2.pack()
- label3 = Label(self, text="端口:")
- label3.pack()
- entry3 = Entry(self, textvariable=self.v3)
- entry3.pack()
- button = Button(self, text="进入", command=self.log_in)
- button.pack()
- label4 = Label(self, text="(若为本机测试则上方除用户名外全留空)")
- label4.pack()
- '''
- 函数: log_in
- 参数: 无
- 作用: 登入界面 “进入” 按钮所对应的响应事件,接收并传递用户输入的用户名、IP、端口等值
- 返回值: 无
- '''
- def log_in(self):
- self.s = socket.socket() # 实例化socket套接字
- self.name = self.v1.get() # 获取本机的用户名
- if self.name == '':
- messagebox.showwarning(title="用户名为空", message="请输入一个用户名!")
- else:
- try:
- IP = self.v2.get() # 收取IP值
- pt = self.v3.get() # 收取端口值
- if IP == '' and pt == '':
- host = socket.gethostname() # 指定要连接到的IP地址
- port = 3 # TCP端口,默认置3,服务端应与客户端的端口置同
- elif IP != '' and pt != '': # 若用户输入了指定要连接的IP与端口号,则
- host = IP
- port = eval(pt)
- else:
- raise socket.gaierror # IP及端口号要么全输入,要么全不输入,否则抛出异常
- self.s.connect((host, port)) # 连接指定IP地址的指定端口
- self.s.send(self.name.encode(encoding='utf-8')) # 向主机发送本机的用户名
- self.master.destroy()
-
- # 下方创建正式聊天室界面的GUI界面根窗口
- root2 = Tk()
- root2.title("多人聊天室(局域网)")
- root2.geometry("850x500+500+125")
- Instance = CommunicateHandleAndGUI(connection=self.s, name=self.name, master=root2)
-
- # 实例化客户端的网络通讯类
- t1 = Thread(target=Instance.message_send, args=()) # 启用发送线程
- t1.start()
- root2.mainloop()
- except (NameError, SyntaxError, socket.gaierror):
- messagebox.showerror(title="输入错误!", message="请输入正确的IP地址及端口!")
- '''
- 类: CommunicateHandleAndGUI
- 作用: 正式聊天室界面的GUI组件放置、C/S通讯的架构
- '''
- class CommunicateHandleAndGUI(Frame): # 客户端的socket通讯处理类
- '''
- 函数: __init__
- 参数:
- connection: 用于臣在socket套接字连接句柄
- name: 本客户端所对应的的用户名
- master: 正式聊天室界面的根窗口
- 作用: 绑定输入、输出缓存区全局属性、绑定本客户端用户名、放置正式聊天室界面的GUI组件
- 返回值: 无
- '''
- def __init__(self, connection, name, master): # 初始化函数,用于绑定类中的全局属性
- self.connection = connection # 承载socket套接字的连接句柄
- self.nm = name # 本客户端的ID值
- self.login = True # 连接状态判断位
- self.MS_temp = None
- self.MR_temp = None
- super().__init__() # Frame类的超类初始化函数引用,将Tkinter控件类Frame的初始化各属性映射到本类中来
- self.master = master # 将类外传入的根窗口传递进来
- self.pack(expand=1, fill="both") # 将本类的Frame放置上指定的根窗口上
- self.GUI_set()
-
- '''
- 函数: GUI_set
- 参数: 无
- 作用: 放置正式聊天室GUI界面的组件
- 返回值: 无
- '''
- def GUI_set(self): # GUI控件放置函数
- self.private_to_user_id = StringVar()
- button1 = Button(self, text="公屏发送", command=self.broadcast, bg="Coral")
- button1.place(relx=0.61, rely=0, width=175, height=120)
- button2 = Button(self, text="私聊发送\n(下方输入私聊对象的ID)", command=self.private_chat, bg="Coral")
- button2.place(relx=0.61, rely=0.2, width=175, height=120)
- private_char_information = Frame(bg="Coral") # 存放私聊ID的区域
- private_char_information.place(relx=0.615, rely=0.44, width=170, height=120)
- lb1 = Label(master=private_char_information, text="私聊对象ID:", bg="Coral")
- lb1.pack()
- entry = Entry(master=private_char_information, textvariable=self.private_to_user_id, border=3, bg="Wheat")
- entry.pack()
- button3 = Button(self, text="退出聊天室", command=self.exit, bg="Coral")
- button3.place(relx=0.61, rely=0.66, width=175, height=120)
- lb2 = Label(self, text="made by: lys")
- lb2.place(relx=0.67, rely=0.92)
- self.message_window = Text(self, bg="BlanchedAlmond")
- self.message_window["state"] = "disabled"
- self.message_window.place(relx=0, rely=0, width=525, height=350)
- self.send_area = Text(self, bg="LightSalmon")
- self.send_area.place(relx=0, rely=0.7, width=525, height=150)
- self.user_online = Text(self, bg="BlanchedAlmond")
- self.user_online["state"] = "disabled"
- self.user_online.place(relx=0.815, rely=0, width=155, height=500)
-
- '''
- 函数: broadcast
- 参数: 无
- 作用: 客户端的广播发送函数
- 返回值: 无
- '''
- def broadcast(self): # 广播按钮对应的事件函数
- if self.MS_temp != '\n': # 服了,对空白情况下的Text组件使用get方法返回的初始无状态空白值是一个换行符
- send_data = "tk " + self.MS_temp # 置上广播标志前缀 “tk” 再将信息打包发出去
- self.connection.send(send_data.encode(encoding='utf-8'))
- self.send_area.delete(1.0, END)
-
- '''
- 函数: private_chat
- 参数: 无
- 作用: 客户端的私聊发送函数
- 返回值: 无
- '''
- def private_chat(self): # 私聊按钮对应的事件函数
- if self.private_to_user_id.get() == '':
- messagebox.showwarning(title="错误", message="请输入私聊对象的用户ID!")
- else:
- try:
- ID_Value = eval(self.private_to_user_id.get())
- if len(self.private_to_user_id.get()) == 1:
- send_data = "{0} ".format(ID_Value) + self.MS_temp
- self.connection.send(send_data.encode(encoding='utf-8'))
-
- if len(self.private_to_user_id.get()) == 2: # 可换成else,不过这样写更明了一点
- send_data = "{0}".format(ID_Value) + self.MS_temp
- self.connection.send(send_data.encode(encoding='utf-8'))
- self.send_area.delete(1.0, END)
-
- except (NameError, SyntaxError):
- messagebox.showwarning(title="错误", message="请输入正确形式的ID值号!")
-
- '''
- 函数: exit
- 参数: 无
- 作用: 客户端的退出函数
- 返回值: 无
- '''
- def exit(self):
- self.login = False
- self.connection.send("exit".encode(encoding="utf-8"))
- self.message_window["state"] = "normal"
- self.message_window.insert(END, '\n')
- self.message_window.insert(END, "您已退出聊天室!")
- self.message_window["state"] = "disabled"
- messagebox.showerror(title="已退出!", message="您已退出聊天室!")
- sys.exit()
-
- '''
- 函数: message_recv
- 参数: 无
- 作用: 客户端的信息接收函数
- 返回值: 无
- '''
- def message_recv(self): # 客户端的信息接收函数
- while self.login:
- self.MR_temp = self.connection.recv(1024).decode(encoding='utf-8')
- if self.MR_temp != '':
- if self.MR_temp[0:10] == "user_list:": # 若服务端传来的是用户列表信息
- lst = eval(self.MR_temp[10:]) # 将服务端传来的在线用户字符串转化为一个列表
- self.user_online["state"] = "normal"
- self.user_online.delete(1.0, END)
- self.user_online.insert(1.0, "目前在线的用户有:\n")
- for i in lst:
- if i == self.nm:
- self.user_online.insert(END, "你-> (ID:{0}) {1}".format(lst.index(i), i))
- self.user_online.insert(END, '\n')
- else:
- self.user_online.insert(END, "(ID:{0}) {1}".format(lst.index(i), i))
- self.user_online.insert(END, '\n')
- self.user_online["state"] = "disabled"
-
- else:
- print(self.MR_temp)
- self.message_window["state"] = "normal"
- self.message_window.insert(END, '\n')
- self.message_window.insert(END, self.MR_temp)
- self.message_window["state"] = "disabled"
- self.connection.close()
-
- '''
- 函数: message_send
- 参数: 无
- 作用: 客户端的信息发送函数
- 返回值: 无
- '''
- def message_send(self): # 客户端的信息发送函数
- t = Thread(target=self.message_recv, args=()) # 发送前,先启动信息接收线程
- t.start()
- while self.login: # 不断探询用户的输入
- self.MS_temp = self.send_area.get(1.0, END).strip() # 获取输入框全部文本,结果字符串结尾会自带一个换行符,使用strip函数删去
- root1 = Tk()
- root1.title("聊天室连入")
- LOG = LoginGUI(master=root1)
- root1.geometry("400x200+520+250")
- root1.mainloop()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。