赞
踩
本内容为python课作业而做的简要笔记。
我使用的工具为python3,pycharm专业版(试用30天)
链接: 菜鸟教程(Python 网络编程)
链接: Python 手册 (网络编程的基本概念)
也许刚接触 “套接字”的人会直接懵了,又翻译为“插座”,但是这个名字叫什么其实并不重要,现阶段不用去纠结它的中文意思。
socket.socket([family[, type[, proto]]])
family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
(AF_INET 表示 IPv4,AF_UNIX 表示 IPv6,既你要使用的IP类型)
type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。
(SOCK_STREAM 为 TCP协议,SOCK_DGRAM 为 UDP协议)
protocol: 一般不填默认为 0。
Socket就是网络中每个主机进程之间交互信息的网络协议
该Tkinter模块(“Tk接口”)是Tk GUI工具包的标准Python接口。Tk和Tkinter在大多数Unix平台以及Windows系统上均可用。(Tk本身不是Python的一部分;它保存在ActiveState中。)
链接: tkinter视频学习
链接: Python 手册(Tkinter)
链接: Python 官网( Interfaces with Tk)
import socket import threading import time # 创建TCP Socket, 类型为服务器之间网络通信,流式Socket mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定服务器端的IP和端口 mySocket.bind((socket.gethostbyname('localhost'), 10000)) # 开始监听TCP传入连接,并设置操作系统可以挂起的最大连接数量 mySocket.listen(5) print('服务器已启动 ', socket.gethostbyname('localhost'), '正在连接 ...') # 创建字典,用于存储客户端的用户 mydict = dict() # 创建字典,用于存储用户名和密码 userDict = {'aaa': '123123', 'bbb': '123123', 'ccc': '123123', 'ddd': '123123', 'eee': '123123'} # 创建列表,用于存储客户端的连接 mylist = list() # 创建列表,用户单独存储用户名 userList = list() """ 000: 用户发送的信息 100: 所有用户名的列表 110: 聊天窗口关闭 200: 系统发送到聊天窗口的信息 300: 返回到客户端的弹窗信息 400: 注册信息 401: 注册成功 402: 注册失败 500: 用户登录(ID和密码) 999: 跳过(收到提示) """ # 把 信息 发送给所有人(包括自己) def chatMsgToAllOne(chatMsg): for c in mylist: try: # 向客户端发送消息 c.send(chatMsg.encode("utf-8")) except: pass # 保持与客户端连接的子线程的处理逻辑 def subThreadProcess(myconnection, connNumber, username): # connNumber为标记符 global mydict, mylist # 接收客户端消息 print('客户端连接标记符:', connNumber, ' 昵称:', username) chatMsgToAllOne('200*系统提示:' + username + '已经进入聊天室,赶快和他(她)打招呼吧*') while True: try: # 接收客户端消息 recvedMsg = str(myconnection.recv(1024).decode("utf-8")).rstrip().lstrip() prefix = recvedMsg[0:3] # 信息标记符 recvedMsg = recvedMsg[3:] # 可见信息 if prefix == '000': # 000表示用户输入框的信息 chatMsgToAllOne('000' + mydict[connNumber] + ':' + recvedMsg) elif prefix == '110': userList.remove(recvedMsg) # 用户名删除 mylist.remove(myconnection) # 客户端链接删除 chatMsgToAllOne('100' + str(userList)) # 重新发送用户列表 except (OSError, ConnectionResetError): try: mylist.remove(myconnection) except: pass print(mydict[connNumber], '已存在, ', len(mylist), ' 人员保存!') chatMsgToAllOne('200*系统提示:' + mydict[connNumber] + ' 已经离开聊天室*') mydict.pop(connNumber, '没有找到key') myconnection.close() return def recvThreadProcess(): global mydict, userList while True: # 接受TCP连接并返回(connection,address),其中connection是新的Socket对象,可以用来接收和发送数据,address是连接客户端的地址。 connection, address = mySocket.accept() # 阻塞,等待消息 print("server connection:", connection) print('新的连接访问', connection.getsockname(), '标记符:' + str(connection.fileno())) try: # 接收客户端消息 buf = connection.recv(1024).decode("utf-8") if buf == '1': # 向客户端发送消息 connection.send('连接成功, 欢迎来到聊天室!'.encode("utf-8")) # (只发给自己,不发给他人) mylist.append(connection) # 用户地址信息 while True: clientInfo = connection.recv(1024).decode("utf-8") # print("clientInfo1:", clientInfo) cLprefix = clientInfo[0:3] clientInfo = clientInfo[3:] # print("clientInfo1:", clientInfo, "clprefix:", cLprefix) if cLprefix == '400': # 注册信息 clientInfo = eval(clientInfo) if clientInfo[0] in userDict: print("no") chatMsgToAllOne('402'+'用户名存在') else: print("yes") userDict[clientInfo[0]] = clientInfo[1] chatMsgToAllOne('401'+'注册成功') print("userDict[", clientInfo[0], "]=", userDict[clientInfo[0]]) elif cLprefix == '500': # 用户登录信息 clientInfo = eval(clientInfo) if clientInfo[0] in userDict: # 校验用户名 tempDict = clientInfo[1] if tempDict[str(clientInfo[0])] == userDict[str(clientInfo[0])]: # 检验密码 chatMsgToAllOne('999') time.sleep(0.5) mydict[connection.fileno()] = clientInfo[0] # 按 标记符 存放 用户名 userList.append(clientInfo[0]) # 把更新后的 mylist 发送给所有用户 chatMsgToAllOne('100' + str(userList)) time.sleep(1) # 为当前连接创建一个新的子线程来保持通信 myThread = threading.Thread(target=subThreadProcess, args=(connection, connection.fileno(), clientInfo[0])) # .fileno()为标记符 myThread.setDaemon(True) myThread.start() break else: chatMsgToAllOne('300') # 300——客户端弹窗信息 print("用户名或密码错误") else: chatMsgToAllOne('300') # 300——客户端弹窗信息 print("用户名或密码错误") """ 查看线程数量""" # count = len(threading.enumerate()) # print("当前线程的数量:", count) else: # 向客户端发送消息 connection.send('200连接失败, 请离开!'.encode("utf-8")) connection.close() except: pass # 类型判断 def typeCheck(string): if string[0] == '[' and string[-1] == ']': return 'list' elif string[0] == '{' and string[-1] == '}': return 'dict' elif string[0] == '(' and string[-1] == ')': if isinstance(string, tuple): return 'tuple' else: return 'string' else: return 'string' if __name__ == '__main__': recvThreadProcess()
1)在thread()方法的第4行,accept()返回一个元组类型,其中connection是新的Socket对象(与发送信息的客户端所创建的Socket不是同一个Socket)。
2)connection中存储着客户端的IP地址和端口,每个新connection唯一指向一个客户端(网络主机进程),用mylist列表存储各个客户端的Socket。
3)由connection.recv()接收客户端的发来的信息(服务端连接了多少个客户端,服务端就开了多少个subThreadProcess()线程,每个子线程唯一对应一个客户端)
4)在chatMsgToAllOne()方法中发送信息给所有客户端
1)在Client.py中,客户端用socket.send()发送输入的信息给服务器,服务器在subThreadProcess()方法中接收到某个客户端发来的信息,紧接着调用chatMsgToAllOne()方法把信息发给所有已连接的客户端(mylist),再在客户端通过socket.recv()来接收服务器信息,随之打印出来。
2)不管是服务器还是客户端,接收信息的socket.recv()方法都在死循环中,用线程将它们分隔开来就可以做其他的事情了
1)可以模仿网络通信协议给信息打包,到指定主机再一一解包。
2)这里我采用简单的标记符的方式给信息分门别类。规定发送的任何信息前三个字符必须在000-999区间,用户发送的信息默认会在首部添加‘000’标记符,标记符由服务器客户端一同进行处理,其他客户端收到的信息不含有标记符,并且用户发送的信息不会干扰到标记符的正常处理。
import socket import threading from tkinter import * import time import tkinter.messagebox as messagebox import sys fon = ("宋体", 18) usersList = {} # 所有用户名列表 usersDict = {} # 用户名:密码 myName = "" # 用户名 # 注册信息 tempUandP = [] """ 000: 用户发送的信息 100: 所有用户名的列表 110: 聊天窗口关闭 200: 系统发送到聊天窗口的信息 300: 返回到客户端的弹窗信息 400: 注册信息 401: 注册成功 402: 注册失败 500: 用户登录(ID和密码) 999: 跳过(收到提示) """ """聊天窗口800x600""" class Application(): def __init__(self): # 创建容器 # 顶部——标签 self.titleTop = Label(root, text="聊天群", fg="black", font=fon) self.titleTop.pack(side='top', fill='both') # 底部——输入框 self.f1 = Frame(root, width=10, height=100) self.f1.pack(side="bottom", fill='x') # 中部左——聊天信息显示界面 self.listboxLeft = Listbox(root, width=55, height=16, font=fon, yscrollcommand="true") self.listboxLeft.pack(side='left', fill='y') # 中部右——用户列表 self.fRight = Frame(root, width=140) self.fRight.pack(side='right', fill="both", expand="no") self.f_labelTop = Label(self.fRight, text="用户列表", fg="black", font=fon, padx=20) self.f_labelTop.pack(side='top',fill='y') self.f_listboxBottom = Listbox(self.fRight, font=fon) self.f_listboxBottom.pack(side='top', fill="both", expand="yes") # 底部——输入框 self.textBottom = Text(self.f1, width=54, height=3, font=fon, yscrollcommand="true", padx=5) self.textBottom.pack(side="left", fill='both') # 底部——确认按钮 self.buttonR = Button(self.f1, width=18, text="发送", font=fon, command=self.sendThreadProcess) self.buttonR.pack(side='right', fill='y') # 设置聊天窗口顶部标签 self.titleTop['text'] = "五邑聊天群(" + str(myName) + ")" self.thead() # 向服务器端发送消息的处理逻辑 def sendThreadProcess(self): try: inputText = self.textBottom.get(1.0, END) sock.send(('000'+inputText).encode("utf-8")) # 000表示用户发送输入框的信息 self.textBottom.delete(0.0, END) except ConnectionAbortedError: print('服务端已经关闭这个连接!') except ConnectionResetError: print('服务器已关闭!') # 向服务器端接收消息的处理逻辑 def recvThreadProcess(self): global usersList while True: try: self.otherMsg = sock.recv(1024).decode("utf-8") otherMsg_prefix = self.otherMsg[0:3] # 前缀 信息标识符 self.otherMsg = self.otherMsg[3:] # 信息 print('prefix:', otherMsg_prefix) if otherMsg_prefix == '000': if len(self.otherMsg.rstrip().lstrip()) > len(myName) + 1: # 空格不发送 .rstrip()去除尾空格 .lstrip()去除首空格 # 消息放入显示面板 self.listboxLeft.insert(END, self.otherMsg) elif otherMsg_prefix == '100': # 100表示用户列表 usersList = eval(self.otherMsg.rstrip().lstrip()) # 添加用户到元组列表 self.f_listboxBottom.delete(0, END) # 清除用户列表 for i in range(len(usersList)): # 显示在线人员 self.f_listboxBottom.insert(END, usersList[i]) elif otherMsg_prefix == '200': self.listboxLeft.insert(END, self.otherMsg) else: pass except ConnectionAbortedError: print('服务端已经关闭这个连接!') break except ConnectionResetError: print('服务器已关闭!') break # 关闭窗口 并更新聊天界面的“用户列表” def closeWin(self): print("110"+myName) sock.send(("110"+myName).encode("utf-8")) root.destroy() # 创建发送和接收消息的子线程 def thead(self): recvThread = threading.Thread(target=self.recvThreadProcess, daemon=True) recvThread.start() """登录窗口400x300""" class loginWin(Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.pack() self.createWidget() def createWidget(self): self.frame01 = Frame(login, width=400, height=100) self.frame02 = Frame(login, width=400, height=100) self.frame03 = Frame(login, width=400, height=100) self.frame04 = Frame(login, width=400, height=100) self.frame01.pack() self.frame02.pack() self.frame03.pack() self.frame04.pack() self.tittle01 = Label(self.frame01, text="欢迎登录五邑", font=fon) self.label01 = Label(self.frame02, text="用户名:", font=fon) self.label02 = Label(self.frame03, text="密 码:", font=fon) self.entry01 = Entry(self.frame02, width=16, font=fon, bg='white', xscrollcommand='true') self.entry02 = Entry(self.frame03, width=16, font=fon, bg='white', xscrollcommand='true', show="*") self.B_login = Button(self.frame04, width=10, height=1, text="登录", font=fon, command=self.logins) self.B_regist = Button(self.frame04, width=10, height=1, text="注册", font=fon, command=regist) self.tittle01.pack(pady=20) self.label01.pack(side='left', fill='both', padx=20, pady=20) self.label02.pack(side='left', fill='both', padx=20, pady=20) self.entry01.pack(side='right', fill='x', pady=20) self.entry02.pack(side='right', fill='x', pady=20) self.B_login.pack(side='left', fill='both', padx=20, pady=20) self.B_regist.pack(side='right', fill='both', padx=20, pady=20) def logins(self): global myName, usersDict myName = self.entry01.get() # 输入用户名 usersDict[str(myName)] = self.entry02.get() # dict存放用户名和密码 # 向服务器发送 登录窗口 用户名 和 密码 sock.send(('500'+str([myName, usersDict])).encode("utf-8")) info = sock.recv(1024).decode("utf-8") if info == '300': messagebox.showinfo("错误", "用户名或密码错误") else: login.destroy() def closeWin(self): login.destroy() sys.exit() # 程序关闭 """注册窗口400x300""" class registers(Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.place() self.createWidget() def createWidget(self): self.lable01 = Label(reg, text="用 户 名:", font=fon) self.lable02 = Label(reg, text="密 码:", font=fon) self.lable03 = Label(reg, text="确认密码:", font=fon) self.entry01 = Entry(reg, font=fon) self.entry02 = Entry(reg, font=fon, show="·") self.entry03 = Entry(reg, font=fon, show="·") self.b1 = Button(reg, text='注册', font=fon, command=self.regButton) self.b2 = Button(reg, text='取消', font=fon, command=self.cancel) self.lable01.place(x=10, y=25) self.lable02.place(x=10, y=85) self.lable03.place(x=10, y=145) self.entry01.place(x=130, y=25) self.entry02.place(x=130, y=85) self.entry03.place(x=130, y=145) self.b1.place(x=80, y=210) self.b2.place(x=280, y=210) def regButton(self): global tempUandP # 获取注册信息 try: if len(self.entry01.get()) >= 1: tempUandP.append(self.entry01.get()) # 用户名 if len(self.entry02.get()) >= 6: tempUandP.append(self.entry02.get()) # 密码 if len(self.entry03.get()) >= 6: tempUandP.append(self.entry03.get()) # 重复密码 if tempUandP[1] == tempUandP[2]: # 向服务器发送 注册窗口 用户名、密码和重复密码 sock.send(('400'+str(tempUandP)).encode("utf-8")) info = sock.recv(1024).decode("utf-8") info_prefix = info[0:3] info = info[3:] if info_prefix == '401': # 成功 messagebox.showinfo("注册", "注册成功") reg.destroy() elif info_prefix == '402': # 失败 messagebox.showinfo("错误", info) raise_above_all(reg) else: messagebox.showinfo("错误", "密码不相同") except: pass def cancel(self): reg.destroy() """创建Socket""" def createSocket(): global sock # 创建TCP Socket, 类型为服务器之间网络通信,流式Socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 通过IP和端口号连接服务器端Socket, 类型为服务器之间网络通信,流式Socket sock.connect((socket.gethostbyname('localhost'), 10000)) # 向服务器发送连接请求 sock.send(b'1') # 从服务器接收到的消息 print(sock.recv(1024).decode("utf-8")) # 窗口显示在最前 def raise_above_all(win): win.attributes('-topmost', 1) win.attributes('-topmost', 0) # 注册窗口 def regist(): global reg reg = Tk() reg.title("用户注册") reg.geometry("400x300+700+300") reg.resizable(0, 0) app3 = registers(master=reg) reg.mainloop() # 登录窗口 def loginStart(): global login login = Tk() login.title("用户登录") login.geometry("400x300+700+300") login.resizable(0, 0) # 禁止窗口大小改变 app2 = loginWin(master=login) login.protocol("WM_DELETE_WINDOW", app2.closeWin) raise_above_all(login) login.mainloop() # 聊天窗口 def rootStart(): global root root = Tk() root.title("聊天窗口") root.geometry("800x600+500+100") root.resizable(0, 0) # 禁止窗口大小改变 app1 = Application() root.protocol("WM_DELETE_WINDOW", app1.closeWin) raise_above_all(root) root.mainloop() if __name__ == '__main__': createSocket() loginStart() rootStart()
有待改进……
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。