本文为计算机网络学习过程中随笔,程序如有bug或设计不当之处还请指正。

<>1.服务器端程序

<>1.1基本思想

主线程:一个while True循环,每次接受一个TCP连接,为每个客户单独创建一个线程。

该部分代码:
import threading from socket import * IP = '**.**.**.**'#改为服务器的内网IP serverPort
= 12000#服务器端要开放该端口 MAX_CONNECTION = 5 serverSocket = socket(AF_INET, SOCK_STREAM
) serverSocket.bind((IP,serverPort))#创建套接字 serverSocket.listen(MAX_CONNECTION)
print('服务器已就绪') cnt = 0#当前在线人数 textList = [] socketList = [] class myThread(
threading.Thread): #一会完善 while True: connectionSocket, addr = serverSocket.
accept()#每次接收一个连接 try: if len(textList) == 0:#当前是第一个用户进入,没有历史记录 connectionSocket
.send('NO_MESSAGE'.encode())#发送一段标识字符串 else:#发送历史记录,把textList中的字符串用换行符拼接
connectionSocket.send('\n'.join(textList).encode()) cnt = cnt + 1 #给用户创建新进程
newThread= myThread(connectionSocket) newThread.setDaemon(True) newThread.start(
) print('连接成功!当前连接人数:' + str(cnt)) except Exception as e: print(repr(e))
<>1.2用户进程

每个用户的套接字要放到一个线程之间共享的socketList中,以便服务器把新接受到的消息发送给每一个用户。
class myThread(threading.Thread): def __init__(self,connection):#线程初始化
threading.Thread.__init__(self) self.connection = connection#保存连接的套接字 socketList
.append(connection)#把该套接字放到list中 def run(self): try: global cnt for sock in
socketList:#有新用户加入 if sock == self.connection: sock.send(('您已加入,当前%d人'%(cnt)).
encode()) else:#给其他线程的用户发送消息 sock.send(('有新用户加入,当前%d人'%(cnt)).encode()) while
True: sentence = self.connection.recv(2048).decode()#不断接收来自该用户的消息 textList.
append(sentence)#把该消息放到list中 for sock in socketList:#给所有用户发送新消息 sock.send(('\n'
+ sentence).encode()) except Exception as e:#用户强制断开连接(关闭窗口) print(timemark() +
repr(e)) cnt = cnt - 1#在线人数-- print('连接异常断开!当前人数:' + str(cnt)) socketList.remove
(self.connection)#移除该套接字 for sock in socketList:#发送消息 sock.send(('有用户退出,当前%d人'%(
cnt)).encode())
<>1.3服务器端总代码
import os import time import threading from socket import * IP = '**.**.**.**'
#内网IP serverPort = 12000 MAX_CONNECTION = 5 serverSocket = socket(AF_INET,
SOCK_STREAM) serverSocket.bind((IP,serverPort)) serverSocket.listen(
MAX_CONNECTION) print('服务器已就绪') cnt = 0 textList = [] socketList = [] def
timemark():#产生当前的时间字符串 timestamp = int(time.time()) timestr = time.strftime(
'%Y-%m-%d %H:%M:%S',time.localtime(timestamp)) return '[' + timestr + ']' class
myThread(threading.Thread): def __init__(self,connection): threading.Thread.
__init__(self) self.connection = connection socketList.append(connection) def
run(self): try: global cnt for sock in socketList: if sock == self.connection:
sock.send(('您已加入,当前%d人'%(cnt)).encode()) else: sock.send(('有新用户加入,当前%d人'%(cnt)).
encode()) while True: sentence = self.connection.recv(2048).decode() textList.
append(sentence) for sock in socketList: sock.send(('\n' + sentence).encode())
except Exception as e: print(timemark() + repr(e)) cnt = cnt - 1 print(
'连接异常断开!当前人数:' + str(cnt)) socketList.remove(self.connection) for sock in
socketList: sock.send(('有用户退出,当前%d人'%(cnt)).encode()) while True:
connectionSocket, addr = serverSocket.accept() try: if len(textList) == 0:
connectionSocket.send('NO_MESSAGE'.encode()) else: connectionSocket.send('\n'.
join(textList).encode()) cnt = cnt + 1 newThread = myThread(connectionSocket)
newThread.setDaemon(True) newThread.start() print('连接成功!!当前连接人数:' + str(cnt))
except Exception as e: print(timemark() + repr(e))
<>2.客户端程序

客户端用tkinter库编写。

<>2.1Tkinter初始化
from tkinter import * root = Tk() root.title('聊天室') text = Text(root,width =
100,height = 20)#上面的文本 scroll = Scrollbar(root) scroll.pack(side = RIGHT,fill =
Y) scroll.config(command = text.yview)#滚动条 text.config(yscrollcommand = scroll.
set) text.pack() entry = Entry(root,bd = 5,width = 70)#聊天内容 entry.pack(side =
LEFT) entry2 = Entry(root,bd = 5,width = 20)#用户名 #...省略中间代码 def send(): if
entry2.get().replace(' ','') == '':#用户名为空 text.config(state = 'normal')
#一般状态下要把text的状态设为disabled,防止用户把文字删除,要改变时再设为normal text.insert(END,'请输入用户名!\n')
text.see(END) text.config(state = 'disabled') return if entry.get() == '':
#发送内容为空 text.config(state = 'normal') text.insert(END,'发送内容不能为空!\n') text.see(
END)#把滚动条拖到最后 text.config(state = 'disabled') else: clientSocket.send((timemark(
) + ' ' + get_mac_address() + ' - ' + entry2.get() + '\n ' + entry.get()).encode
())#clientSocket声明暂未给出 entry.delete(0,'end')#把发送内容删掉 button = Button(root,text =
'SEND',command = send)#发送按钮 button.pack(side = RIGHT) entry2.pack(side = RIGHT)
#... root.mainloop()#启动tkinter的主循环
<>2.2用于接收文字的线程

由于用户的发送和接收消息是不同步的,在接收消息时线程会阻塞,所以另开启一个线程进行接受文字。
class myThread(threading.Thread): def __init__(self): threading.Thread.__init__
(self) def run(self): while True:#与服务器端接收消息过程类似 msg = clientSocket.recv(2048)
text.config(state = 'normal') text.insert(END,msg.decode() + '\n') text.see(END)
text.config(state = 'disabled') textThread = myThread() textThread.start()
<>2.3客户端总代码
import os import uuid import time import threading from socket import * from
tkinterimport * serverName = '**.**.**.**'#服务器外网IP serverPort = 12000 root = Tk(
) root.title('聊天室') text = Text(root,width = 100,height = 20) scroll = Scrollbar
(root) scroll.pack(side = RIGHT,fill = Y) scroll.config(command = text.yview)
text.config(yscrollcommand = scroll.set) text.pack() entry = Entry(root,bd = 5,
width= 70) entry.pack(side = LEFT) entry2 = Entry(root,bd = 5,width = 20)
clientSocket= socket(AF_INET, SOCK_STREAM) print('正在连接...') clientSocket.connect
((serverName,serverPort)) print('连接成功!') msg = clientSocket.recv(2048)#先接收历史记录
if msg.decode() != 'NO_MESSAGE':#有历史记录时 text.insert(END,msg.decode() +
'\n----以上是历史记录----\n') text.see(END) def get_mac_address():
#由于用户名可以随时换,用MAC地址作为标识了 mac = uuid.UUID(int = uuid.getnode()).hex[-12:] return
':'.join([mac[e:e+2] for e in range(0,11,2)]) def timemark(): timestamp = int(
time.time()) timestr = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(
timestamp)) return '[' + timestr + ']' def send(): if entry2.get().replace(' ',
'') == '': text.config(state = 'normal') text.insert(END,'请输入用户名!\n') text.see(
END) text.config(state = 'disabled') return if entry.get() == '': text.config(
state= 'normal') text.insert(END,'发送内容不能为空!\n') text.see(END) text.config(state
= 'disabled') else: clientSocket.send((timemark() + ' ' + get_mac_address() + '
- ' + entry2.get() + '\n ' + entry.get()).encode()) entry.delete(0,'end') button
= Button(root,text = 'SEND',command = send) button.pack(side = RIGHT) entry2.
pack(side = RIGHT) class myThread(threading.Thread): def __init__(self):
threading.Thread.__init__(self) def run(self): while True: msg = clientSocket.
recv(2048) text.config(state = 'normal') text.insert(END,msg.decode() + '\n')
text.see(END) text.config(state = 'disabled') textThread = myThread() textThread
.start() root.mainloop()
<>3.效果预览

服务器端:

客户端:

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信