【Python网络编程】多线程聊天软件程序

课程设计的时候制作的多线程聊天软件程序

基于python3.4.3

import socket
import pickle
import threading
import tkinter
import os
import datetime
import time
try:
    import pymysql
except:
    print("can‘t find pymysql")

tcplocalport=8101 #TCP监听端口
tcpconnectport=8101 #TCP连接端口
udplocalport=8201 #广播监听端口
udpconnectport=8201 #广播连接端口
filelocalport=8301 #文件监听接口
fileconnectport=8301 #文件连接端口
localIP=socket.gethostbyname(socket.gethostname()) #本机IP
print("localip:",localIP)
print(‘localtcpport:‘,tcplocalport)

#sockdict字典用于存放本机作为客户端的连接对象
sockdict={}

#filedict字典用于存放文件传输的信息
#filedict[‘IP‘]=[‘filename‘,filesize,fpobj,restsize]
filedict={}
sendfileflag={}

#用于调用软件的图形界面
showdict={}

#转换用表
#a为address,i为listbox的index值,n为name
a2i={}
a2n={}
i2a={}

myname=‘a‘

sqlflag=0 #MYSQL启用标志,若启用MYSQL,则为1

#用于通讯的类,type为通讯指令的类型,name为发送端的名称
#data为正文,target为目标对象,time为发送时的时间
class talkdata:
    def __init__(self,ttype,tname,tdata,ttarget=""):
        self.dtype=ttype
        self.dname=tname
        self.ddata=tdata
        self.dtarget=ttarget
        self.dtime=gettime()

#获取时间的函数
def gettime():
    now = datetime.datetime.now()
    return now.strftime(‘%Y-%m-%d %H:%M:%S‘)

#解析接收到的数据
def solvedata(data_str,address,protocols):
    global sockdict
    global filedict
    global a2n
    global sendfileflag
    data_obj=pickle.loads(data_str)
    if data_obj.dtype=="searchse" and (address[0]!=localIP or data_obj.dname!=myname): #搜索发起类型
        reply=talkdata("searchre",myname,"")
        if sockdict.get(address[0])==None:
            socketconnect(address[0],tcpconnectport)
        send(sockdict[address[0]],reply)
    if data_obj.dtype=="searchre" and (address[0]!=localIP or data_obj.dname!=myname): #搜索回复类型
        a2n[address[0]]=data_obj.dname
    """if data_obj.dtype=="tcpcon":
        if showdict.get(address[0])==None:
            socketconnect(address[0],tcpconnectport)
            threading.Thread(target=
                         createfgui_guif,arg=
                         (sockdict[address[0]],(address[0],tcpconnectport))).start()
    """
    if data_obj.dtype=="str": #聊天内容类型
        adata=data_obj.dtime+"\n"+data_obj.dname+‘:‘+data_obj.ddata
        if showdict.get(address[0])==None:
            a2n[address[0]]=data_obj.dname
            socketconnect(address[0],tcpconnectport)
            threading.Thread(target=
                createfgui_guif,args=
                (sockdict[address[0]],(address[0],tcpconnectport))).start()
            time.sleep(0.5)
        showdict[address[0]].insert(‘end‘,adata)
        print(data_obj.ddata)
    if data_obj.dtype=="filehead": #发送文件头类型
        filename=data_obj.ddata[0]
        filesize=data_obj.ddata[1]
        restsize=filesize
        print("filename:",filename)
        print("filesize:",filesize)

        filetk=tkinter.Tk()
        filetk.withdraw()
        fd=tkinter.filedialog.FileDialog(filetk)
        filesavename=fd.go()
        filetk.destroy()
        if filesavename==None:
            filef=talkdata(‘fileflag‘,myname,"refuse")
        else:
            #fp=open(filename,‘wb‘)
            #fp=open(input("filename:"),‘wb‘)
            fp=open(filesavename,‘wb‘)
            filedict[address[0]]=[filename,filesize,fp,restsize]
            if sockdict.get(address[0])==None:
                socketconnect(address[0],tcpconnectport)
            filef=talkdata(‘fileflag‘,myname,"accept")
        send(sockdict[address[0]],filef)
    if data_obj.dtype=="fileflag": #文件允许接收类型
        if data_obj.ddata=="accept":
            sendfileflag[address[0]]=1
            print("start send file")
        if data_obj.ddata=="refuse":
            sendfileflag[address[0]]=2
    if data_obj.dtype=="udpstr" and (address[0]!=localIP or data_obj.dname!=myname): #广播聊天内容类型
        adata=data_obj.dtime+"\n"+data_obj.dname+‘:‘+data_obj.ddata
        showdict[data_obj.dtarget].insert(‘end‘,adata)
    if sqlflag==1 and protocols==‘tcp‘:
        sqlsavedata(data_obj,address[0])

#连接MYSQL
def sqlinit():
    global sqlcon,sqlcur,sqlflag
    sqlcon=pymysql.connect(host=‘localhost‘,user=‘root‘,passwd=‘‘,port=3306)
    sqlcur=sqlcon.cursor()
    try :
        sqlcon.select_db(‘talk‘)
    except :
        sqlcur.execute(‘create database if not exists talk‘)
        sqlcon.select_db(‘talk‘)
    sqlcon.commit()
    sqlflag=1
    print(‘connect sql‘)

#创建表
def sqlcreattable(ip):
    global sqlcon,sqlcur
    daip=‘‘
    for i in ip:
        if i==‘.‘:
            i=‘_‘
        daip=daip+i
    sqlcur.execute("""create table if not exists %s
    (type varchar(20),
    name varchar(20),
    data varchar(500),
    time datetime)""" % daip)
    sqlcon.commit()

#将数据加入数据库
def sqlsavedata(data_obj,ip):
    global sqlcon,sqlcur
    daip=‘‘
    for i in ip:
        if i==‘.‘:
            i=‘_‘
        daip=daip+i
    value="insert into %s values(‘%s‘,‘%s‘,‘%s‘,‘%s‘)" % (daip,data_obj.dtype,data_obj.dname,data_obj.ddata,data_obj.dtime)
    #print(value)
    sqlcur.execute(value)
    sqlcon.commit()

#读取数据库中的数据
def sqlloaddata(ip):
    global sqlcon,sqlcur
    daip=‘‘
    for i in ip:
        if i==‘.‘:
            i=‘_‘
        daip=daip+i
    sqlcur.execute(‘select * from %s‘ % daip)
    a=sqlcur.fetchall()
    sqlcon.commit()
    return a

#发送聊天
def send(socketobj,data_obj):
    data_pickle=pickle.dumps(data_obj)
    try :
        socketobj.send(data_pickle)
    except ConnectionResetError :
        address=socketobj.getpeername()
        socketconnect(address[0],address[1])
        sockdict[address[0]].send(data_pickle)

#广播聊天对象
def sendudp(data_obj):
    data_str=pickle.dumps(data_obj)
    addr=(‘<broadcast>‘,udpconnectport)
    udps=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udps.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
    udps.sendto(data_str,addr)

#发送文件
def sendfile(socketobj,filename):
    fp=open(filename,‘rb‘)
    filesize=os.stat(filename).st_size
    fhead=talkdata("filehead",myname,(filename,filesize))
    address=socketobj.getpeername()
    send(socketobj,fhead)
    sendfileflag[address[0]]=0
    while sendfileflag[address[0]]==0:
        pass
    if sendfileflag[address[0]]==2:
        return 0
    socketfile=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    socketfile.connect((address[0],fileconnectport))

    while 1:
        filedata=fp.read(1024)
        if not filedata:
            break
        socketfile.send(filedata)
    print("send over")

#接收TCP信息
def rec(connect,address):
    while 1:
        #try:
            data_str=connect.recv(1024)
            if not data_str:
                print("leave")
                break
            print(‘Receive TCP data from‘,address)
            solvedata(data_str,address,‘tcp‘)
        #except:
         #   print(‘Receive TCP data error\n‘)
          #  break

#接收文件
def recfile(connect,address):
    while 1:
        #try:
            filedata=connect.recv(1024)
            if not filedata:
                print("leave")
                break
            restsize=filedict[address[0]][3]
            print("Receive file data from",address,"\nRestsize:",restsize)
            fp=filedict[address[0]][2]
            fp.write(filedata)
            restsize=restsize-len(filedata)
            filedict[address[0]][3]=restsize
            if restsize<=0:
                fp.close()
                print("Receive file successful")
        #except:
         #   print(‘Receive file data error\n‘)
          #  break    

#接收广播信息
def socketudplisten(sockobj):
    while 1:
        data_str,address=sockobj.recvfrom(1024)
        if not data_str:
            pring("no udpdata")
        if sqlflag==1 :
            sqlcreattable(address[0])
        solvedata(data_str,address,‘udp‘)

#文件接收端口监听
def filelisten(sockobj):
    threfdict={}
    while 1:
        connect,address=sockobj.accept()
        print("File accept ",address)
        threfdict[address[0]]=threading.Thread(target=recfile,args=(connect,address))
        threfdict[address[0]].start()   

#TCP端口监听
def socketlisten(sockobj):
    global sqlflag
    thredict={}
    while 1:
        connect,address=sockobj.accept()
        print("Accept ",address)
        if sqlflag==1 :
            sqlcreattable(address[0])
        thredict[address[0]]=threading.Thread(target=rec,args=(connect,address))
        thredict[address[0]].start()

#连接TCP
def socketconnect(connectIP,connectport):
    global sockdict
    try:
        sockse=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sockse.connect((connectIP,connectport))
        print("connect")
        sockdict[connectIP]=sockse
        if sqlflag==1 :
            sqlcreattable(connectIP)
    except:
        print("tcp connect error")

#登录按钮执行的功能
def login_guif():
    global myname
    if entryname.get()!=‘‘:
        myname=entryname.get()
    listgui.title(myname)
    listgui.deiconify()
    logingui.withdraw()

#ip连接对话框的连接按钮执行的功能
def getip_guif():
    global i2a,a2i,a2n
    #连接对话框
    tcpcongui=tkinter.Toplevel()
    framefip=tkinter.Frame(tcpcongui)
    framefip.pack()
    labelip=tkinter.Label(tcpcongui,text=‘IP地址‘)
    labelip.pack()
    entryip=tkinter.Entry(tcpcongui)
    entryip.pack()
    tkinter.Button(tcpcongui,text="连接",command=lambda :inputip_guif(tcpcongui,entryip)).pack()

    select=lbfriends.curselection()
    a=()
    if select==a:
        connectip=""
    else:
        connectip=i2a[select[0]]
    entryip.insert(0,connectip)

#发送文件按钮执行的功能
def sendfile_guif(socketobj,tkobj):
    fd=tkinter.filedialog.FileDialog(tkobj)
    filename=fd.go()
    threading.Thread(target=sendfile,args=(socketobj,filename)).start()

#创建私聊聊天窗口
def createfgui_guif(socketobj,address):
    global showdict
    global a2n
    if a2n.get(address[0])==None:
        name=address[0]
    else:
        name=a2n[address[0]]
    tl=tkinter.Tk()
    tl.title(name)
    framereceive=tkinter.Frame(tl)
    framereceive.pack()
    scrollbarreceive=tkinter.Scrollbar(framereceive)
    scrollbarreceive.pack(fill=‘y‘,side=‘right‘)
    showtext=tkinter.Text(framereceive,height=20,
                      width=60,yscrollcommand=scrollbarreceive.set)
    showtext.pack(side=‘left‘)

    framesend=tkinter.Frame(tl)
    framesend.pack()
    scrollbarsend=tkinter.Scrollbar(framesend)
    scrollbarsend.pack(fill=‘y‘,side=‘right‘)
    sendtext=tkinter.Text(framesend,height=4,
                      width=60,yscrollcommand=scrollbarsend.set)
    sendtext.pack(side=‘left‘)

    framebutton=tkinter.Frame(tl)
    framebutton.pack()
    tkinter.Button(framebutton,text=‘发送信息‘,command=lambda:send_guif(socketobj,showtext,sendtext)).pack(side=‘right‘)
    tkinter.Button(framebutton,text=‘发送文件‘,command=lambda:sendfile_guif(socketobj,tl)).pack(side=‘left‘)
    tkinter.Button(framebutton,text=‘聊天记录‘,command=lambda:loaddata_guif(address[0])).pack(side=‘left‘)

    showdict[address[0]]=showtext
    tl.mainloop()

#创建广播聊天窗口
def createggui_guif(target):
    global showdict
    tl=tkinter.Tk()
    tl.title(target)
    framereceive=tkinter.Frame(tl)
    framereceive.pack()
    scrollbarreceive=tkinter.Scrollbar(framereceive)
    scrollbarreceive.pack(fill=‘y‘,side=‘right‘)
    showtext=tkinter.Text(framereceive,height=20,
                      width=60,yscrollcommand=scrollbarreceive.set)
    showtext.pack(side=‘left‘)

    framesend=tkinter.Frame(tl)
    framesend.pack()
    scrollbarsend=tkinter.Scrollbar(framesend)
    scrollbarsend.pack(fill=‘y‘,side=‘right‘)
    sendtext=tkinter.Text(framesend,height=4,
                      width=60,yscrollcommand=scrollbarsend.set)
    sendtext.pack(side=‘left‘)

    framebutton=tkinter.Frame(tl)
    framebutton.pack()
    tkinter.Button(framebutton,text=‘发送信息‘,command=lambda:sendudp_guif(target,showtext,sendtext)).pack(side=‘right‘)

    showdict[target]=showtext
    tl.mainloop()

#聊天记录按钮执行的功能
def loaddata_guif(ip):
    sqldata=sqlloaddata(ip)
    datagui=tkinter.Tk()
    scrollbarreceive=tkinter.Scrollbar(datagui)
    scrollbarreceive.pack(fill=‘y‘,side=‘right‘)
    showtext=tkinter.Text(datagui,height=20,
                      width=60,yscrollcommand=scrollbarreceive.set)
    showtext.pack(side=‘left‘)
    for tadata in sqldata:
        adata="%s %s \n%s:%s" % (tadata[3],tadata[0],tadata[1],tadata[2])
        showtext.insert(‘end‘,adata)

#私聊窗口中发送信息按钮执行的功能
def send_guif(socketobj,showtext,sendtext):
    data=sendtext.get(‘0.0‘,‘end‘)
    adata=gettime()+‘\n‘+myname+‘:‘+data
    sendtext.delete(‘0.0‘,‘end‘)
    showtext.insert(‘end‘,adata)
    data_obj=talkdata("str",myname,data)
    if sqlflag==1:
        address=socketobj.getpeername()
        sqlsavedata(data_obj,address[0])
    send(socketobj,data_obj)

#群聊窗口中发送信息按钮执行的功能
def sendudp_guif(target,showtext,sendtext):
    data=sendtext.get(‘0.0‘,‘end‘)
    adata=gettime()+‘\n‘+myname+‘:‘+data
    sendtext.delete(‘0.0‘,‘end‘)
    showtext.insert(‘end‘,adata)
    data_obj=talkdata("udpstr",myname,data,target)
    sendudp(data_obj)

#IP对话框中连接按钮执行的功能
def inputip_guif(gui,entryip):
    global sockdict
    gui.withdraw()
    connectip=entryip.get()
    if connectip=="":
        connectip="127.0.0.1"
    socketconnect(connectip,tcpconnectport)
    threading.Thread(target=
                     createfgui_guif,args=
                     (sockdict[connectip],(connectip,tcpconnectport))).start()

#我的群组中连接按钮执行的功能,暂未加入
def getgroup_guif():
    target=lbgroups.selection_get()
    threading.Thread(target=
                     createggui_guif,args=(target,)).start()

#我的好友中刷新按钮执行的功能
def refreshf_guif():
    global a2n
    global a2i
    global i2a
    a2n.clear()
    a2i.clear()
    i2a.clear()
    lbfriends.delete(0,‘end‘)
    data=talkdata("searchse",myname,(localIP,tcplocalport))
    sendudp(data)
    time.sleep(1)
    i=0
    for key in a2n:
        lbfriends.insert(i,a2n[key])
        a2i[key]=i
        i2a[i]=key
        i=i+1

#我的群组中刷新按钮执行的功能
def refreshg_guif():
    pass

#登录界面
logingui=tkinter.Tk()
logingui.title(‘登录‘)

framelogin=tkinter.Frame(logingui)
framelogin.pack()
labelname=tkinter.Label(framelogin,text=‘名称‘)
labelname.pack(side=‘left‘)
entryname=tkinter.Entry(framelogin)
entryname.pack(side=‘right‘)

frameloginbutton=tkinter.Frame(logingui)
frameloginbutton.pack()
tkinter.Button(frameloginbutton,text="  登录  ",command=login_guif).pack(side=‘right‘)
tkinter.Button(frameloginbutton,text="MYSQL",command=sqlinit).pack(side=‘left‘)

#列表界面
#listgui=tkinter.Toplevel()
listgui=tkinter.Tk()
listgui.withdraw()

framelistfriends=tkinter.Frame(listgui)
framelistfriends.pack(side=‘left‘)
lablefriends=tkinter.Label(framelistfriends,text=‘我的好友‘)
lablefriends.pack(side=‘top‘)
lbfriends=tkinter.Listbox(framelistfriends)
lbfriends.pack(side=‘top‘)
framlistfbutton=tkinter.Frame(framelistfriends)
framlistfbutton.pack()
tkinter.Button(framlistfbutton,text="刷新",command=refreshf_guif).pack(side=‘left‘)
tkinter.Button(framlistfbutton,text="连接",command=getip_guif).pack(side=‘right‘)

framelistgroups=tkinter.Frame(listgui)
framelistgroups.pack(side=‘right‘)
lablegroups=tkinter.Label(framelistgroups,text=‘我的群组‘)
lablegroups.pack(side=‘top‘)
lbgroups=tkinter.Listbox(framelistgroups)
lbgroups.pack(side=‘top‘)
lbgroups.insert(0,"广播")
framlistgbutton=tkinter.Frame(framelistgroups)
framlistgbutton.pack()
tkinter.Button(framlistgbutton,text="刷新",command=refreshg_guif).pack(side=‘left‘)
tkinter.Button(framlistgbutton,text="连接",command=getgroup_guif).pack(side=‘right‘)

#TCP套接字初始化
sockre=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sockre.bind((‘‘,tcplocalport))
sockre.listen(30)
print(‘TCP listen‘)

#接收文件初始化
sockref=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sockref.bind((‘‘,filelocalport))
sockref.listen(30)

#UDP套接字初始化
sockreudp=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sockreudp.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sockreudp.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
sockreudp.bind((‘‘,udplocalport))

#创建线程监听端口
thredlisten=threading.Thread(target=socketlisten,args=(sockre,))
thredlisten.start()

threfilelisten=threading.Thread(target=filelisten,args=(sockref,))
threfilelisten.start()

threudplisten=threading.Thread(target=socketudplisten,args=(sockreudp,))
threudplisten.start()
时间: 2024-10-14 07:01:33

【Python网络编程】多线程聊天软件程序的相关文章

[python] 网络编程之套接字Socket、TCP和UDP通信实例

很早以前研究过C#和C++的网络通信,参考我的文章: C#网络编程之Tcp实现客户端和服务器聊天 C#网络编程之套接字编程基础知识 C#网络编程之使用Socket类Send.Receive方法的同步通讯 Python网络编程也类似.同时最近找工作笔试面试考察Socket套接字.TCP\UDP区别比较多,所以这篇文章主要精简了<Python核心编程(第二版)>第16章内容.内容包括:服务器和客户端架构.套接字Socket.TCP\UDP通信实例和常见笔试考题. 最后希望文章对你有所帮助,如果有不

python网络编程基础(线程与进程、并行与并发、同步与异步)

python网络编程基础(线程与进程.并行与并发.同步与异步) 目录 线程与进程 并行与并发 同步与异步 线程与进程 进程 前言 进程的出现是为了更好的利用CPU资源使到并发成为可能. 假设有两个任务A和B,当A遇到IO操作,CPU默默的等待任务A读取完操作再去执行任务B,这样无疑是对CPU资源的极大的浪费.聪明的老大们就在想若在任务A读取数据时,让任务B执行,当任务A读取完数据后,再切换到任务A执行.注意关键字切换,自然是切换,那么这就涉及到了状态的保存,状态的恢复,加上任务A与任务B所需要的

Python 网络编程(二)

Python 网络编程 上一篇博客介绍了socket的基本概念以及实现了简单的TCP和UDP的客户端.服务器程序,本篇博客主要对socket编程进行更深入的讲解 一.简化版ssh实现 这是一个极其简单的仿ssh的socket程序,实现的功能为客户端发送命令,服务端接收到客户端的命令,然后在服务器上通过subrocess模块执行命令,如果命令执行有误,输出内容为空,则返回"command error"的语句给客户端,否则将命令执行的结果返回给客户端 服务端 1 2 3 4 5 6 7 8

python 网络编程:socket

在学习socket之前,我们先复习下相关的网络知识. OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层.OSI七层模型是由国际标准化组织ISO定义的网络的基本结构,不仅包括一些概念和结构,还包括一系列的协议. TCP/IP四层模型:既然有OSI七层模型,为什么我们还要定义一个TCP/IP的四层模型呢,那是因为OSI七层模型对应面过于宽泛,很多概念实现不了,也没必要实现,因此,实际生产中广泛应用的是TCP/IP四层结构,他们的对应关系如下表: TCP/IP OSI 应用层

python并发编程&amp;多线程(一)

本篇理论居多,实际操作见:  python并发编程&多线程(二) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线 流水线的工作需要电源,电源就相当于cpu 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 多线程(即多个控制线程)的概念

Python网络编程04/recv原理/高大上版解决粘包方式

目录 Python网络编程04/recv原理/高大上版解决粘包方式 1.昨日内容回顾 2.recv工作原理 3.高大上版解决粘包方式(自定制报头) 3.1 解决思路: 3.2 服务端 3.3客户端 4.基于UDP协议的socket通信 4.1服务端 4.2客户端 Python网络编程04/recv原理/高大上版解决粘包方式 1.昨日内容回顾 1. 通信循环 2. 链接通信循环 3. 远程执行命令: subprocess.Popen() # bytes: 网络传输, 文件存储时. 4. 粘包现象

[Python网络编程] DNS缓存解决方案

记得以前写爬虫的时候为了防止dns多次查询,是直接修改/etc/hosts文件的,最近看到一个优美的解决方案,修改后记录如下: import socket _dnscache={} def _setDNSCache(): """ Makes a cached version of socket._getaddrinfo to avoid subsequent DNS requests. """ def _getaddrinfo(*args, **

Python 网络编程

Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发. 什么是 Socket? Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯. socket()函数 Pyt

python 网络编程(五)---DNS域名系统

1.域名系统定义 DNS计算机域名系统由域名服务器和域名解析器组成.通常输入的是网址就是一个域名. 2.域名查询 查询方式包括: 1)正向查询:由域名查找对应的IP(如:119.75.218.77">www.baidu.com->119.75.218.77 ) 2)反向查询:由IP查找域名(如:119.75.218.77 –> www.baidu.com) 查询方式包括: 1)递归查询:当DNS服务器接收到客户端的查询请求时,会做出相应的反应(本地DNS服务器查询.其他服务器查