如何用Python编写一个聊天室

如何用Python编写一个聊天室,第1张

python聊天室(python2.7版本):

暂时先给出两种版本的,tcp+udp

都是分别运行server.py和client.py,就可以进行通讯了。

别外还有websocket版本,这个是有web界面的和基本web服务的,如果需要的话,我会把基本的代码贴一版上来。

TCP版本:

socket-tcp-server.py(服务端):

#-*- encoding:utf-8 -*-

#socket.getaddrinfo(host,  port, family=0, socktype=0, proto=0, flags=0)

#根据给定的参数host/port,相应的转换成一个包含用于创建socket对象的五元组,

#参数host为域名,以字符串形式给出代表一个IPV4/IPV6地址或者None.

#参数port如果字符串形式就代表一个服务名,比如“http”"ftp""email"等,或者为数字,或者为None

#参数family为地主族,可以为AF_INET  ,AF_INET6 ,AF_UNIX.

#参数socktype可以为SOCK_STREAM(TCP)或者SOCK_DGRAM(UDP)

#参数proto通常为0可以直接忽略

#参数flags为AI_*的组合,比如AI_NUMERICHOST,它会影响函数的返回值

#附注:给参数host,port传递None时建立在C基础,通过传递NULL。

#该函数返回一个五元组(family, socktype, proto, canonname, sockaddr),同时第五个参数sockaddr也是一个二元组(address, port)

#更多的方法及链接请访问

# Echo server program

from socket import *

import sys

import threading

from time import ctime

from time import localtime

import traceback

import time

import subprocess

reload(sys)

sys.setdefaultencoding("utf8")

HOST='127.0.0.1'

PORT=8555  #设置侦听端口

BUFSIZ=1024

class TcpServer():

    def __init__(self):

        self.ADDR=(HOST, PORT)

        try:

            self.sock=socket(AF_INET, SOCK_STREAM)

            print '%d is open' % PORT

            self.sock.bind(self.ADDR)

            self.sock.listen(5)

            #设置退出条件

            self.STOP_CHAT=False

            # 所有监听的客户端

            self.clients = {}

            self.thrs = {}

            self.stops = []

        except Exception,e:

            print "%d is down" % PORT

            return False

    def IsOpen(ip, port):

        s = socket(AF_INET, SOCK_STREAM)

        try:

            s.connect((ip, int(port)))

            # s.shutdown(2)

            # 利用shutdown()函数使socket双向数据传输变为单向数据传输。shutdown()需要一个单独的参数,

            # 该参数表示s了如何关闭socket。具体为:0表示禁止将来读;1表示禁止将来写;2表示禁止将来读和写。

            print '%d is open' % port

            return True

        except:

            print '%d is down' % port

            return False

    def listen_client(self):

        while not self.STOP_CHAT:

            print(u'等待接入,侦听端口:%d' % (PORT))

            self.tcpClientSock, self.addr=self.sock.accept()

            print(u'接受连接,客户端地址:',self.addr)

            address = self.addr

            #将建立的client socket链接放到列表self.clients中

            self.clients[address] = self.tcpClientSock

            #分别将每个建立的链接放入进程中,接收且分发消息

            self.thrs[address] = threading.Thread(target=self.readmsg, args=[address])

            self.thrs[address].start()

            time.sleep(0.5)

    def readmsg(self,address):

        #如果地址不存在,则返回False

        if address not in self.clients:

            return False

        #得到发送消息的client socket

        client = self.clients[address]

        while True:

            try:

                #获取到消息内容data

                data=client.recv(BUFSIZ)

            except:

                print(e)

                self.close_client(address)

                break

            if not data:

                break

            #python3使用bytes,所以要进行编码

            #s='%s发送给我的信息是:[%s] %s' %(addr[0],ctime(), data.decode('utf8'))

            #对日期进行一下格式化

            ISOTIMEFORMAT='%Y-%m-%d %X'

            stime=time.strftime(ISOTIMEFORMAT, localtime())

            s=u'%s发送给我的信息是:%s' %(str(address),data.decode('utf8'))

            #将获得的消息分发给链接中的client socket

            for k in self.clients:

                self.clients[k].send(s.encode('utf8'))

                self.clients[k].sendall('sendall:'+s.encode('utf8'))

                print str(k)

            print([stime], ':', data.decode('utf8'))

            #如果输入quit(忽略大小写),则程序退出

            STOP_CHAT=(data.decode('utf8').upper()=="QUIT")

            if STOP_CHAT:

                print "quit"

                self.close_client(address)

                print "already quit"

                break

    def close_client(self,address):

        try:

            client = self.clients.pop(address)

            self.stops.append(address)

            client.close()

            for k in self.clients:

                self.clients[k].send(str(address) + u"已经离开了")

        except:

            pass

        print(str(address)+u'已经退出')

if __name__ == '__main__':

    tserver = TcpServer()

    tserver.listen_client()

    

 ——————————华丽的分割线——————————       

    

 socket-tcp-client.py (客户端):

 

#-*- encoding:utf-8 -*-

from socket import *

import sys

import threading

import time

reload(sys)

sys.setdefaultencoding("utf8")

#测试,连接本机

HOST='127.0.0.1'

#设置侦听端口

PORT=8555

BUFSIZ=1024

class TcpClient:

    ADDR=(HOST, PORT)

    def __init__(self):

        self.HOST = HOST

        self.PORT = PORT

        self.BUFSIZ = BUFSIZ

        #创建socket连接

        self.client = socket(AF_INET, SOCK_STREAM)

        self.client.connect(self.ADDR)

        #起一个线程,监听接收的信息

        self.trecv = threading.Thread(target=self.recvmsg)

        self.trecv.start()

    def sendmsg(self):

        #循环发送聊天消息,如果socket连接存在则一直循环,发送quit时关闭链接

        while self.client.connect_ex(self.ADDR):

            data=raw_input('>:')

            if not data:

                break

            self.client.send(data.encode('utf8'))

            print(u'发送信息到%s:%s' %(self.HOST,data))

            if data.upper()=="QUIT":

                self.client.close()

                print u"已关闭"

                break

    def recvmsg(self):

        #接收消息,如果链接一直存在,则持续监听接收消息

        try:

            while self.client.connect_ex(self.ADDR):

                data=self.client.recv(self.BUFSIZ)

                print(u'从%s收到信息:%s' %(self.HOST,data.decode('utf8')))

        except Exception,e:

            print str(e)

if __name__ == '__main__':

    client=TcpClient()

    client.sendmsg()

UDP版本:

socket-udp-server.py

# -*- coding:utf8 -*-

import sys

import time

import traceback

import threading

reload(sys)

sys.setdefaultencoding('utf-8')

import socket

import traceback

HOST = "127.0.0.1"

PORT = 9555

CHECK_PERIOD = 20

CHECK_TIMEOUT = 15

class UdpServer(object):

    def __init__(self):

        self.clients = []

        self.beats = {}

        self.ADDR = (HOST,PORT)

        try:

            self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

            self.sock.bind(self.ADDR)       # 绑定同一个域名下的所有机器

            self.beattrs = threading.Thread(target=self.checkheartbeat)

            self.beattrs.start()

        except Exception,e:

            traceback.print_exc()

            return False

    def listen_client(self):

        while True:

            time.sleep(0.5)

            print "hohohohohoo"

            try:

                recvData,address = self.sock.recvfrom(2048)

                if not recvData:

                    self.close_client(address)

                    break

                if address in self.clients:

                    senddata = u"%s发送给我的信息是:%s" %(str(address),recvData.decode('utf8'))

                    if recvData.upper() == "QUIT":

                        self.close_client(address)

                    if recvData == "HEARTBEAT":

                        self.heartbeat(address)

                        continue

                else:

                    self.clients.append(address)

                    senddata = u"%s发送给我的信息是:%s" %(str(address),u'进入了聊天室')

                for c in self.clients:

                    try:

                        self.sock.sendto(senddata,c)

                    except Exception,e:

                        print str(e)

                        self.close_client(c)

            except Exception,e:

                # traceback.print_exc()

                print str(e)

                pass

    def heartbeat(self,address):

        self.beats[address] = time.time()

    def checkheartbeat(self):

        while True:

            print "checkheartbeat"

            print self.beats

            try:

                for c in self.clients:

                    print time.time()

                    print self.beats[c]

                    if self.beats[c] + CHECK_TIMEOUT <time.time():

                        print u"%s心跳超时,连接已经断开" %str(c)

                        self.close_client(c)

                    else:

                        print u"checkp%s,没有断开" %str(c)

            except Exception,e:

                traceback.print_exc()

                print str(e)

                pass

            time.sleep(CHECK_PERIOD)

    def close_client(self,address):

        try:

            if address in self.clients:

                self.clients.remove(address)

                if self.beats.has_key(address):

                    del self.beats[address]

                print self.clients

            for c in self.clients:

                self.sock.sendto(u'%s已经离开了' % str(address),c)

            print(str(address)+u'已经退出')

        except Exception,e:

            print str(e)

            raise

if __name__ == "__main__":

    udpServer = UdpServer()

    udpServer.listen_client()

    

——————————华丽的分割线——————————    

socket-udp-client.py:

# -*- coding:utf8 -*-

import sys

import threading

import time

reload(sys)

sys.setdefaultencoding('utf-8')

import socket

HOST = "127.0.0.1"

PORT = 9555

#BEAT_PORT = 43278

BEAT_PERIOD = 5

class UdpClient(object):

    def __init__(self):

        self.clientsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

        self.HOST = HOST

        self.ADDR = (HOST,PORT)

        self.clientsock.sendto(u'请求建立链接',self.ADDR)

        self.recvtrs = threading.Thread(target=self.recvmsg)

        self.recvtrs.start()

        self.hearttrs = threading.Thread(target=self.heartbeat)

        self.hearttrs.start()

    def sendmsg(self):

        while True:

            data = raw_input(">:")

            if not data:

                break

            self.clientsock.sendto(data.encode('utf-8'),self.ADDR)

            if data.upper() == 'QUIT':

                self.clientsock.close()

                break

    def heartbeat(self):

        while True:

            self.clientsock.sendto('HEARTBEAT',self.ADDR)

            time.sleep(BEAT_PERIOD)

    def recvmsg(self):

        while True:

            recvData,addr = self.clientsock.recvfrom(1024)

            if not recvData:

                break

            print(u'从%s收到信息:%s' %(self.HOST,recvData.decode('utf8')))

if __name__ == "__main__":

    udpClient = UdpClient()

    udpClient.sendmsg()

socket(family,type[,protocal]) 使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。

有效的端口号: 0~ 65535

但是小于1024的端口号基本上都预留给了 *** 作系统

POSIX兼容系统(如Linux、Mac OS X等),在/etc/services文件中找到这些预留端口与的列表

面向连接的通信提供序列化、可靠的和不重复的数据交付,而没有记录边界。意味着每条消息都可以拆分多个片段,并且每个消息片段都能到达目的地,然后将它们按顺序组合在一起,最后将完整的信息传递给等待的应用程序。

实现方式(TCP):

传输控制协议(TCP), 创建TCP必须使用SOCK_STREAM作为套接字类型

因为这些套接字(AF_INET)的网络版本使用因特网协议(IP)来搜寻网络中的IP,

所以整个系统通常结合这两种协议(TCP/IP)来进行网络间数据通信。

数据报类型的套接字, 即在通信开始之前并不需要建议连接,当然也无法保证它的顺序性、可靠性或重复性

实现方式(UDP)

用户数据包协议(UDP), 创建UDP必须使用SOCK_DGRAM (datagram)作为套接字类型

它也使用因特网来寻找网络中主机,所以是UDP和IP的组合名字UDP/IP

注意点:

1)TCP发送数据时,已建立好TCP连接,所以不需要指定地址。UDP是面向无连接的,每次发送要指定是发给谁。

2)服务端与客户端不能直接发送列表,元组,字典。需要字符串化repr(data)。

TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

什么时候应该使用TCP : 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输.

UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击……

UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP ……

首先放出一个 TCP/IP 的程序,这里是单线程服务器与客户端,在多线程一节会放上多线程的TCP/IP服务程序。

这里将服务端和客户端放到同一个程序当中,方便对比服务端与客户端的不同。

TCP/IP是因特网的通信协议,其参考OSI模型,也采用了分层的方式,对每一层制定了相应的标准。

网际协议(IP)是为全世界通过互联网连接的计算机赋予统一地址系统的机制,它使得数据包能够从互联网的一端发送至另一端,如 130.207.244.244,为了便于记忆,常用主机名代替IP地址,例如 baidu.com。

UDP (User Datagram Protocol,用户数据报协议) 解决了上述第一个问题,通过端口号来实现了多路复用(用不同的端口区分不同的应用程序)但是使用UDP协议的网络程序需要自己处理丢包、重包和包的乱序问题。

TCP (Transmission Control Protocol,传输控制协议) 解决了上述两个问题,同样使用端口号实现了复用。

TCP 实现可靠连接的方法:

socket通信模型及 TCP 通信过程如下两张图。

[图片上传失败...(image-6d947d-1610703914730)]

[图片上传失败...(image-30b472-1610703914730)]

socket.getaddrinfo(host, port, family, socktype, proto, flags)

返回: [(family, socktype, proto, cannonname, sockaddr), ] 由元组组成的列表。

family:表示socket使用的协议簇, AF_UNIX : 1, AF_INET: 2, AF_INET6 : 10。 0 表示不指定。

socktype: socket 的类型, SOCK_STREAM : 1, SOCK_DGRAM : 2, SOCK_RAW : 3

proto: 协议, 套接字所用的协议,如果不指定, 则为 0。 IPPROTO_TCP : 6, IPPRTOTO_UDP : 17

flags:标记,限制返回内容。 AI_ADDRCONFIG 把计算机无法连接的所有地址都过滤掉(如果一个机构既有IPv4,又有IPv6,而主机只有IPv4,则会把 IPv6过滤掉)

AI _V4MAPPED, 如果本机只有IPv6,服务却只有IPv4,这个标记会将 IPv4地址重新编码为可实际使用的IPv6地址。

AI_CANONNAME,返回规范主机名:cannonname。

getaddrinfo(None, 'smtp', 0, socket.SOCK_STREAM, 0, socket.AP_PASSIVE)

getaddrinfo('ftp.kernel.org', 'ftp', 0, 'socket.SOCK_STREAM, 0, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)

利用已经通信的套接字名提供给getaddrinfo

mysock = server_sock.accept()

addr, port = mysock.getpeername()

getaddrinfo(addr, port, mysock.family, mysock.type, mysock.proto, socket.AI_CANONNAME)

TCP 数据发送模式:

由于 TCP 是发送流式数据,并且会自动分割发送的数据包,而且在 recv 的时候会阻塞进程,直到接收到数据为止,因此会出现死锁现象,及通信双方都在等待接收数据导致无法响应,或者都在发送数据导致缓存区溢出。所以就有了封帧(framing)的问题,即如何分割消息,使得接收方能够识别消息的开始与结束。

关于封帧,需要考虑的问题是, 接收方何时最终停止调用recv才是安全的?整个消息或数据何时才能完整无缺的传达?何时才能将接收到的消息作为一个整体来解析或处理。

适用UDP的场景:

由于TCP每次连接与断开都需要有三次握手,若有大量连接,则会产生大量的开销,在客户端与服务器之间不存在长时间连接的情况下,适用UDP更为合适,尤其是客户端太多的时候。

第二种情况: 当丢包现象发生时,如果应用程序有比简单地重传数据聪明得多的方法的话,那么就不适用TCP了。例如,如果正在进行音频通话,如果有1s的数据由于丢包而丢失了,那么只是简单地不断重新发送这1s的数据直至其成功传达是无济于事的。反之,客户端应该从传达的数据包中任意选择一些组合成一段音频(为了解决这一问题,一个智能的音频协议会用前一段音频的高度压缩版本作为数据包的开始部分,同样将其后继音频压缩,作为数据包的结束部分),然后继续进行后续 *** 作,就好像没有发生丢包一样。如果使用TCP,那么这是不可能的,因为TCP会固执地重传丢失的信息,即使这些信息早已过时无用也不例外。UDP数据报通常是互联网实时多媒体流的基础。

参考资料:


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12202267.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存