本文为计算机网络学习过程中随笔,程序如有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.效果预览

服务器端:

python聊天室代码 用python编写一个聊天程序_服务器


客户端:

python聊天室代码 用python编写一个聊天程序_python_02