【Python】基于TCP的在线聊天程序

目录

一、基础知识

1、TCP和UDP

2、Socket

3、编写一个简单的TCP服务器程序:

 在上述代码中需注意:

 二、多线程编程

1、线程:

2、编写自己的线程类myTread来创建线程对象:

三、实现代码

客户端:

服务器端:

四、最终效果演示


一、基础知识

1、TCP和UDP

        TCP协议会通过握手建立连接,然后对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。

        许多常用的更高级的协议都是建立在TCP协议基础上的,例如用于浏览器的HTTP协议、发送邮件的SMTP协议等。

        UDP协议同样建立在IP协议之上,但是UDP协议面向无连接的通信协议,不保证数据包的顺利到达,是不可靠传输,所以效率比TCP要高。

2、Socket

        Socket是网络编程的一个抽象概念。Socket是套接字的英文名称,主要是用于网络通信编程。在20世纪80年代初,美国政府的高级研究工程机构(ARPA)给加利福尼亚大学的Berkeley分校提供了资金,让他们在UNIX操作系统下实现TCP/IP协议。在这个项目中,研究人员为TCP/IP网络通信开发了一个API(应用程序接口),这个API称为Socket(套接字)。Socket是TCP/IP网络最为通用的API,任何网络通信都是通过Socket来完成的。

        通常用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型。

        套接字构造函数socket(family,type[,protocol])使用给定的套接字家族、套接字类型、协议编号来创建套接字。

⭐ 其参数如下。

● family:套接字家族,可以是AF_UNIX或者AF_INET、AF_INET6。

● type:套接字类型,可以根据是面向连接的还是非连接的分为SOCK_STREAM和SOCK_DGRAM。

● protocol:一般不填,默认为0。        

相关参数的取值意义如下:

创建TCP Socket:

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

创建UDP Socket:

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

 Socket同时支持数据流Socket和数据报Socket,下图为面向连接支持数据流TCP的时序图。

        由该图可以看出,客户机(Client)与服务器(Server)的关系是不对称的。

        对于TCP/S,服务器首先后动,然后在某一时刻后动客户机与服务器建立连接。服务器与客户机开始都必须调用Socket()建立一个套接字,然后服务器调用Bind()将套接字与一个本机指定端口绑定在一起,再调用Listen()使套接字处于一种被动的准备接收状态,这时客户机建立套接字便可以通过调用Connect()和服务器建立连接,服务器就可以调用Accept()来接收客户机连接。然后继续监听指定端口,并发出阻塞,直到下一个请求出现,从而实现多个客户机连接。在连接建立之后,客户机和服务器之间就可以通过连接发送和接收数据。最后,待数据传送结束,双方调用Close()关闭套接字。

        在Python的Socket模块中Socket对象提供的函数如下图所示:

3、编写一个简单的TCP服务器程序:

        服务器端接收客户端连接,把客户端发过来的字符串加上“Hello”再发回去:

import socket
import threading

def tcplink(sock, addr):
    try:
        print('接收一个来自%s:%s 的连接请求' % addr)
        sock.send(b'Welcome!')
        while True:
            data = sock.recv(1024)
            if not data or data.decode('utf-8') == 'exit':
                break
            sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        sock.close()
        print('来自%s:%s 的连接关闭了.' % addr)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8888))
s.listen(5)
print('等待客户端连接...')

while True:
    try:
        sock, addr = s.accept()
        t = threading.Thread(target=tcplink, args=(sock, addr))
        t.start()
    except Exception as e:
        print(f"接受连接时发生错误: {e}")

 在上述代码中需注意:

        1、在程序中首先创建一个基于IPv4和TCP协议的Socket:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

然后绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。

        2、127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。 端口号需要预先指定。因为这里写的这个服务不是标准服务,所以用8888这个端口号。

        (注意:小于1024的端口号必须要有管理员权限才能绑定。)

# 监听本机8888端口
s.bind((127.0.0.1, 8888))

        3、 接着调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量为5:

s.listen(5)
print("等待客户端连接..")

        4、 接下来,服务器程序通过一个无限循环接受来自客户端的连接,accept()会等待并返回一个客户端的连接:

while True:
    # 接受一个新连接
    sock, addr = s.accept()  # sock是新建的Socket对象,服务器通过它与对应客户端通信,addr是IP地址
    # 创建新线程来处理TCP连接
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()

         5、每个连接都必须创建新线程(或进程)来处理,否则单线程在处理连接的过程中无法接受其他客户端的连接:

def tcplink(sock, addr):
    print("接收一个来自%s:%s的连接请求" % addr)
    sock.send(b'Welcome!')
    # 发给客户端“Welcome!”信息
    while True:
        data = sock.recv(1024)
        # 接收客户端发来的信息
        time.sleep(1)
        # 延时1秒钟
        if not data or data.decode('utf-8') == 'exit':
            # 如果没数据或收到'exit'信息
            break
            # 终止循环
        sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
        # 收到信息加上“Hello”发回
    sock.close()
    # 关闭连接
    print("来自%s:%s的连接关闭了。" % addr)

        6、在连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上“Hello”再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。 如果要测试这个服务器程序,还需要编写一个客户端程序

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8888))
print(s.recv(1024).decode('utf-8'))

for data in [b'LiHua', b'XiaoMing', b'Jack']:
    s.send(data)
    print(s.recv(1024).decode('utf-8'))

s.send(b'exit')
print(s.recv(1024).decode('utf-8'))
s.close()

        7、运行效果如下: 

 服务器端:

客户端:

        8、需要注意的是:客户端程序运行完毕就退出了,而服务器程序会永远运行下去,必须按Ctrl+C组合键退出程序。

        可见,用TCP协议进行Socket编程在Python中十分简单,对于客户端,要主动连接服务器的IP和指定端口,对于服务器,要首先监听指定端口,然后对每一个新的连接创建一个线程或进程来处理。

        通常,服务器程序会无限运行下去。另外还需注意,同一个端口被一个Socket绑定了以后就不能被其他Socket绑定了。 

 二、多线程编程

1、线程:

        线程是操作系统可以调度的最小执行单位,能够执行并发处理。通常是将程序拆分成两个或多个并发运行的线程,即同时执行多个操作。例如,在使用线程的同时监视用户并发输入,并执行后台任务等。

        threading模块提供了Thread类来创建和处理线程,格式如下:

线程对象=threading.Thread(target=线程函数,args=(参数列表),name=线程名,group=线程组)

         第一个参数是函数名,第二个参数args是一个元组,线程名和线程组都可以省略。

⭐ Thread 类还提供了以下方法:

  • run(): 用于表示线程活动的方法。
  • start(): 启动线程活动。
  • join([time]): 可以阻塞进程,直到线程执行完毕。参数time指定超时时间(单位为秒),超过指定时间join就不再阻塞进程了。
  • isAlive(): 返回线程是否活动。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

⭐ threading 模块提供的其他方法如下:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同结果。

2、编写自己的线程类myTread来创建线程对象:

import threading
import time
exitFlag=0

class myThread(threading.Thread):  # 继承父类threading.Thread
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):  # 把要执行的代码写到run()函数里面,线程在创建后会直接运行run()函数
        print("starting " + self.name)
        print_time(self.name, self.counter, 5)
        print("Exiting " + self.name)

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threading.exit()
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()
print("Exiting Main Thread")

运行结果:

三、实现代码

客户端:

# 在线聊天程序客户端

import tkinter
import tkinter.font as tkFont
import socket
import threading
import time
import sys

class ClientUI():
    local = '127.0.0.1'
    port = 5505
    global clientSock
    flag=False

    def __init__(self):
        self.root = tkinter.Tk()
        self.root.title('Python在线聊天-客户端V1.0')

        # 窗口面板,用4个面板布局
        self.frame = [tkinter.Frame(), tkinter.Frame(), tkinter.Frame(), tkinter.Frame()]

        # 显示消息Text右边的滚动条
        self.chatTextScrollBar = tkinter.Scrollbar(self.frame[0])
        self.chatTextScrollBar.pack(side=tkinter.RIGHT, fill=tkinter.Y)

        # 显示消息Text,并绑定上面的滚动条
        ft = tkFont.Font(family='Fixdsys', size=11)
        self.chatText = tkinter.Listbox(self.frame[0], width=70, height=18, font=ft)
        self.chatText['yscrollcommand'] = self.chatTextScrollBar.set
        self.chatText.pack(expand=1, fill=tkinter.BOTH)
        self.chatTextScrollBar['command'] = self.chatText.yview()

        self.frame[0].pack(expand=1, fill=tkinter.BOTH)

        # 标签,分开消息显示Text和消息输入Text
        label = tkinter.Label(self.frame[1], height=2)
        label.pack(fill=tkinter.BOTH)
        self.frame[1].pack(expand=1, fill=tkinter.BOTH)

        # 输入消息Text的滚动条
        self.inputTextScrollBar = tkinter.Scrollbar(self.frame[2])
        self.inputTextScrollBar.pack(side=tkinter.RIGHT, fill=tkinter.Y)

        # 输入消息Text,并与滚动条绑定
        ft = tkFont.Font(family='Fixdsys', size=11)
        self.inputText = tkinter.Text(self.frame[2], width=70, height=8, font=ft)
        self.inputText['yscrollcommand'] = self.inputTextScrollBar.set
        self.inputText.pack(expand=1, fill=tkinter.BOTH)
        self.inputTextScrollBar['command'] = self.chatText.yview()

        self.frame[2].pack(expand=1, fill=tkinter.BOTH)

        # “发送”按钮
        self.sendButton = tkinter.Button(self.frame[3], text='发送', width=10, command=self.sendMessage)
        self.sendButton.pack(expand=1, side=tkinter.BOTTOM and tkinter.RIGHT, padx=15, pady=8)

        # “关闭”按钮
        self.closeButton = tkinter.Button(self.frame[3], text='关闭', width=10, command=self.close)
        self.closeButton.pack(expand=1, side=tkinter.RIGHT, padx=15, pady=8)

        self.frame[3].pack(expand=1, fill=tkinter.BOTH)

    def receiveMessage(self):
        try:
            # 建立Socket连接
            self.clientSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.clientSock.connect((self.local, self.port))
            self.flag = True
        except:
            self.flag = False
            self.chatText.insert(tkinter.END, '您还未与服务器端建立连接,请检查服务器是否启动')
            return

        self.buffer = 1024
        self.clientSock.send('Y'.encode())  # 向服务器发送字符Y,表示客户端要连接服务器
        while True:
            try:
                if self.flag == True:
                    # 连接建立,接收服务器端消息
                    self.serverMsg = self.clientSock.recv(self.buffer).decode('utf-8')
                    if self.serverMsg == 'Y':
                        self.chatText.insert(tkinter.END, '客户端已经与服务器端建立连接......')
                    elif self.serverMsg == 'N':
                        self.chatText.insert(tkinter.END, '客户端与服务器端建立连接失败......')
                    elif not self.serverMsg:
                        continue
                    else:
                        theTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                        self.chatText.insert(tkinter.END, '服务器端' + theTime + '说:\n')
                        self.chatText.insert(tkinter.END, '' + self.serverMsg)
                else:
                    break
            except EOFError as msg:
                raise msg
                self.clientSock.close()
                break

    def sendMessage(self):
        message = self.inputText.get('1.0', tkinter.END)
        # 格式化当前的时间
        theTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.chatText.insert(tkinter.END, '客户端' + theTime + '说:\n')
        self.chatText.insert(tkinter.END, ' | ' + message + '\n')
        if self.flag == True:
            self.clientSock.send(message.encode())  # 将消息发送到服务器端
        else:
            # Socket连接没有建立,提示用户
            self.chatText.insert(tkinter.END, '您还未与服务器端建立连接,服务器端无法收到您的消息\n')
        # 清空用户在Text中输入的消息
        self.inputText.delete(0.0, tkinter.END)

    def close(self):
        sys.exit()

    def startNewThread(self):
        # 启动一个新线程来接收服务器端的消息
        thread = threading.Thread(target=self.receiveMessage, args=())
        thread.setDaemon(True)
        thread.start()

def main():
            client = ClientUI() 
            client.startNewThread()  # 启动线程接收服务器端的消息
            client.root.mainloop()

if __name__ == '__main__':
        main()

服务器端:

# 在线聊天程序服务器端

import tkinter
import tkinter.font as tkFont
import socket
import threading
import time
import sys

class ServerUI():
    local = '127.0.0.1'
    port = 5505
    global serverSock
    flag=False

    def __init__(self):
        self.root = tkinter.Tk()
        self.root.title('Python在线聊天-服务器端V1.0')

        self.frame = [tkinter.Frame() for _ in range(4)]

        self.chatTextScrollBar = tkinter.Scrollbar(self.frame[0])
        self.chatTextScrollBar.pack(side=tkinter.RIGHT, fill=tkinter.Y)

        ft = tkFont.Font(family='Fixdsys', size=11)
        self.chatText = tkinter.Listbox(self.frame[0], width=70, height=18, font=ft)
        self.chatText['yscrollcommand'] = self.chatTextScrollBar.set

        self.chatText.pack(expand=1, fill=tkinter.BOTH)

        self.chatTextScrollBar['command'] = self.chatText.yview()

        self.frame[0].pack(expand=1, fill=tkinter.BOTH)

        label = tkinter.Label(self.frame[1], height=2)
        label.pack(fill=tkinter.BOTH)

        self.frame[1].pack(expand=1, fill=tkinter.BOTH)

        self.inputTextScrollBar = tkinter.Scrollbar(self.frame[2])
        self.inputTextScrollBar.pack(side=tkinter.RIGHT, fill=tkinter.Y)

        self.inputText = tkinter.Text(self.frame[2], width=70, height=8, font=ft)
        self.inputText['yscrollcommand'] = self.inputTextScrollBar.set

        self.inputText.pack(expand=1, fill=tkinter.BOTH)

        self.inputTextScrollBar['command'] = self.chatText.yview()

        self.frame[2].pack(expand=1, fill=tkinter.BOTH)

        self.sendButton = tkinter.Button(self.frame[3], text='发送', width=10, command=self.sendMessage)
        self.sendButton.pack(expand=1, side=tkinter.BOTTOM, padx=25, pady=5)

        self.closeButton = tkinter.Button(self.frame[3], text='关闭', width=10, command=self.close)
        self.closeButton.pack(expand=1, side=tkinter.RIGHT, padx=25, pady=5)

        self.frame[3].pack(expand=1, fill=tkinter.BOTH)

        self.serverSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.serverSock.bind((self.local, self.port))
        self.serverSock.listen(15)
        self.buffer = 1024
        self.chatText.insert(tkinter.END, '服务器已准备就绪......')
        self.flag = False

    def receiveMessage(self):
        while True:
            self.connection, self.address = self.serverSock.accept()
            self.flag = True
            while True:
                clientMsg = self.connection.recv(self.buffer).decode('utf-8')
                if not clientMsg:
                    continue
                elif clientMsg == 'Y':
                    self.chatText.insert(tkinter.END, '服务器端已经与客户端建立连接......')
                    self.connection.send(b'Y')
                elif clientMsg == 'N':
                    self.chatText.insert(tkinter.END, '服务器端与客户端建立连接失败......')
                    self.connection.send(b'N')
                else:
                    theTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                    self.chatText.insert(tkinter.END, '客户端{}说:\n{}'.format(theTime, clientMsg))

    def sendMessage(self):
        message = self.inputText.get('1.0', tkinter.END)
        theTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.chatText.insert(tkinter.END, '服务器{}说:\n{}'.format(theTime, message))
        if self.flag:
            self.connection.send(message.encode())
        else:
            self.chatText.insert(tkinter.END, '您还未与客户端建立连接,客户端无法接收消息\n')
        self.inputText.delete('1.0', tkinter.END)

    def close(self):
        sys.exit()

    def startNewThread(self):
        thread = threading.Thread(target=self.receiveMessage, args=())
        thread.setDaemon(True)
        thread.start()

def main():
    server = ServerUI()
    server.startNewThread()
    server.root.mainloop()

if __name__ == '__main__':
    main()

四、最终效果演示

 

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个基于TCP聊天程序的示例代码: 服务端代码: ```python import socket # 创建socket对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定IP地址和端口号 server_socket.bind(('127.0.0.1', 9999)) # 监听连接 server_socket.listen(5) print('等待客户端连接...') while True: # 接受客户端连接 client_socket, client_address = server_socket.accept() print('客户端已连接,地址为:', client_address) while True: # 接收客户端发送的消息 data = client_socket.recv(1024).decode('utf-8') if not data: # 客户端已断开连接 print('客户端已断开连接') break print('收到客户端消息:', data) # 发送消息给客户端 message = input('请输入回复消息:') client_socket.send(message.encode('utf-8')) # 关闭客户端连接 client_socket.close() ``` 客户端代码: ```python import socket # 创建socket对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务端 client_socket.connect(('127.0.0.1', 9999)) print('已连接到服务器') while True: # 发送消息给服务端 message = input('请输入消息:') client_socket.send(message.encode('utf-8')) # 接收服务端发送的消息 data = client_socket.recv(1024).decode('utf-8') print('收到服务端消息:', data) # 关闭连接 client_socket.close() ``` 使用方法: 1. 先运行服务端代码,等待客户端连接。 2. 运行客户端代码,输入连接信息。 3. 客户端输入消息,服务端接收并回复。 4. 当客户端想要断开连接时,可以输入空消息,服务端会自动关闭连接。 注意事项: 1. 这只是一个示例代码,可能存在一些不完善的地方,需要根据实际情况进行修改。 2. 在实际使用中,应该考虑到并发连接和异常处理等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值