Python线程项目:UDP客户端即时通讯系统

在解决本篇博客提出的任务之前,我们需要先了解几个问题。

  1. 何为线程?使用线程能够解决什么问题?线程在python编程语言当中的体现形式是什么?
  2. udp协议通信机制的特点是什么?在python编程语言中如何运用这种协议呢?
  3. 如何使用多线程技术实现无限次数发送和接受客户端两方信息呢?

那么,我们依次解决以上问题,然后再进一步分析解决本篇主体:UDP客户端聊天系统

何为线程?使用线程能够解决什么问题?线程在python编程语言当中的体现形式是什么?

任何应用程序在启动时,都会在内存分配一段自己的空间来运行自己的代码,比如这是一片内存,在内存中启动程序,qq,cmd,eclipse,这些每个程序在内存中都分配了自己的空间,而且每个空间都有自己的代码块可以运行,进程代表的时这个应用程序在内存中开辟的空间,代表着正在执行的程序,进程可以简单的理解成就是这片空间,而这片空间当中会有很多的代码,而代码会按照顺序执行,那们需要注意的是,负责进行当中代码执行的内容就叫做线程,线程就意味着是进程负责代码执行的控制单元,其实是有线程在控制着程序的执行,像一条线一样,从头到尾,你也可以把它称之为一条执行情景,或者叫执行路径,所以每一个进程当中都有很多的线程。

线程就是进程当中的执行单元或者执行情景或执行路径,负责进程中程序执行的控制单元。一个进程中至少有一个线程,当一个进程中,线程有多个时,就是多线程,我们可以把这个应用程序称之为多线程应用程序。

多线程解决的问题:可以让多部分代码同时执行。比如说我们在打开电脑时,在qq执行的时候,是不也可以看电影,然后还可以同时玩游戏,这些就是同时执行的,这些就是多线程,而且电影和游戏中都不止是一个线程,但是有同时运行的特点,那么在一个程序中会不会多线程同时执行呢,当然会比如说我们的杀毒软件,电脑清理和电脑杀毒可以同时运行,这就是多线程的操作。

线程的特点:
线程可以被抢占(中断)。
在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。

线程可以分为:
内核线程:由操作系统内核创建和撤销。
用户线程:不需要内核支持而在用户程序中实现的线程。

Python3 线程中常用的两个模块为:
_thread
threading(推荐使用)
Python 2中的thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。

udp协议通信机制的特点是什么?在python编程语言中如何运用这种协议呢?

(1)UDP无需建立连接。因此UDP不会引入建立连接的时延。试想如果DNS运行在TCP之上而不是UDP,则DNS的速度会满很多。HTTP使用TCP而不是UDP,是因为基于文本数据的Web网页来说,可靠性是至关重要的。

(2)无连接状态。TCP需要在端系统中维护连接状态。此连接状态包括接受和发送缓存、拥塞控制参数和确认号和序号的参数。而UDP不维护连接状态,也不跟踪这些参数,因此某些专用应用服务器使用UDP时,一般都能支持更多的活动客户机。

(3)分组首部开销更小。TCP有20字节的的首部开销,而UDP只有8个字节的首部开销。

(4)应用层能够更地控制要发送的数据和发送时间。UDP没有拥塞控制,因此网络中的拥塞也不会影响主机的发送效率。某些实时应用(如直播)要求以稳定的速度发送,能容忍一些数据的丢失,但不允许有较大的时延,而UDP正好可以满足这些应用的需求。

(5)UDP常用于一次性传输比较小数据的网络应用,如DNS、SNMP等,因为对于这些应用,若采用TCP,则将为创建连接、维护和拆除而带来不小的开销。UDP也常用于多媒体应用(如IP电话、实时视频会议、流媒体等),显然,可靠数据传输对于这些应用来说并不是最重要的,但TCP的拥塞控制会导致数据出现较大的延迟,这是它们不可容忍的。

(6)UDP提供尽最大努力的交付,即不保证可靠交付,但并不意味着应用对数据的要求是不可靠的,因此需要维护传输可靠性的工作需要用户在应用层来完成。应用实体可以根据应用需求来灵活设计自己的可靠性机制。

(7)UDP是面向报文的的。发送方UDP对应用层交下来的报文,在添加首部后就交付给IP层,既不合并,也不拆分,而是保留这些报文的边界;接受方UDP对IP层交上来的用户数据报,在去除首部后就原封不动的交付给上层的应用进程,一次交付一个完整的报文,因此报文不可分割,是UDP数据处理的最小单位。

在python中使用udp协议发送信息,首先导入socket模块内容

  1. 获取udp客户端对象:client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
  2. 绑定当前的ip地址与客户端的端口号:client.bind(('ip地址',发送端端口号))
  3. 接受或者发送信息 client.sendto('发送内容',('接受端ip地址',接收端端口号))     data,addr = client.recvfrom(1024) 

如何使用多线程技术实现无限次数发送和接受客户端两方信息呢?

基于上面udp通信在python当中的体现,完成代码如下:

import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
client.bind(('127.0.0.1',8765))
while True:
    #接受键盘的输入
    content = input()
    #发送给另外一个客户端
    client.sendto(content.encode('utf-8'),('127.0.0.1',4567))
    #接受
    recvdata,addr = client.recvfrom(1024)
    print(addr,'==',recvdata.decode('utf-8'))

print('------------------------------------------------')
#接受端的代码
client2 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
client2.bind(('127.0.0.1',4567))
while True:
    data,addr = client2.recvfrom(1024)
    print(addr,'--',data.decode('utf-8'))
    con = input()
    client2.sendto(con.encode('utf-8'),('127.0.0.1',8765))

观察以上代码发现:
只是包含了python语言在udp协议当中发送和接受数据的过程,但是有问题。我们发现client1 必须每一次发送出去,才能接受,client2必须接受到之后,才能发送。这说明这两个功能的执行不是并行关系,是依赖关系。
那么我们对于以上的内容做一个升级,不希望接受是依赖于发送的,两个功能在客户端之后可以并行发生。
所以我们就需要使用多条路径来完成。就可以使用多线程技术。因为发送是一条路径,接受是一条路径,所以需要写两个线程。

好的,了解了线程和UDP通信基础,我们将两者结合,完成使用多线程技术实现UDP无限聊天系统的功能。

实现代码如下:首先将发送和接收功能所在的线程编写出来。

import threading,sys
'''
分析:线程当中哪些变量是需要传递的,哪些变量可以在线程当中之间创建?
发现client=socket.socket() 这个对象除了用于当前功能,还用于接收功能,所以不要创建,
要传递,通过构造方法传递
'''
class SendThread(threading.Thread):
    def __init__(self, name, socket_client,ip,port):
        threading.Thread.__init__(self, name=name)
        self.socket = socket_client
        self.ip = ip
        self.port = port
    # 描述线程任务功能的方法
    def run(self):
        while True:
            # 接受键盘的输入
            content = input()
            # 发送给另外一个客户端
            self.socket.sendto(content.encode('utf-8'), (self.ip, self.port))
            if content.lower()=='bye':
                sys.exit(0)     #结束当前线程


import threading,sys
class ReceiverThread(threading.Thread):
    def __init__(self,name,socket):
        threading.Thread.__init__(self,name=name)
        self.socket = socket
    def run(self):
            while True:
                recvdata,fromaddr = self.socket.recvfrom(1024)
                print(fromaddr,'说:',recvdata.decode('utf-8'))

让这两个线程同时在客户端中开启,就可以实现客户端交互通信的效果。本篇博客这里提供两个客户端代码,大家可以通过修改IP地址移植代码,在不同的计算机当中,控制台输入信息,也可实现即时通讯的效果。

import socket
from send_thread_udp import SendThread
from receive_thread_udp import ReceiverThread
if __name__=='__main__':
    client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    client.bind(('127.0.0.1',9000))
    st = SendThread('小明发',client,'127.0.0.1',8700)
    rt = ReceiverThread('小明接',client)

    #启动线程
    st.start()
    rt.setDaemon(True)   #设置守护线程   后台线程
    rt.start()

第二个客户端代码相似,只需要改变发送和接受对应的端口号。

import socket
from send_thread_udp import SendThread
from receive_thread_udp import ReceiverThread
if __name__=='__main__':
    client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    client.bind(('127.0.0.1',8700))
    st = SendThread('小鸿发',client,'127.0.0.1',9000)
    rt = ReceiverThread('小鸿接',client)

    #启动线程
    st.start()
    rt.setDaemon(True)
    rt.start()

以上就是使用多线程技术,符合UDP网络协议的即时通讯功能。运行一下,就能得到以下结果:

效果图

运行最后的异常原因是另一客户端被我们关闭了,发送信息无法传达,故提示异常,并不造成影响。感谢您的阅读! 

猜你喜欢

转载自blog.csdn.net/u012156341/article/details/89496494