本文为计算机网络学习过程中随笔,程序如有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 tkinter import *
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.效果预览
服务器端:
客户端: