美文网首页网络编程魔法Python 爬虫 web 数据分析 机器学习 人工智能
一步一步教你做聊天软件(Python实现+非阻塞)

一步一步教你做聊天软件(Python实现+非阻塞)

作者: Python雁横 | 来源:发表于2018-01-28 22:03 被阅读37次

    首先,我们需要知道实现怎么样的聊天:

    1、不是单工或者半双工

    2、我可以发消息,也可以不发消息,并且不影响我收消息

    3、我的消息不会发给自己,我的消息可以发给其他所有人

    4、暂时没有GUI,只要会做了,弄一个GUI界面是很简单的,我过两天有时间再弄一个

    知道这几点要求后,还需要知道实现的方法:

    1、基本的socket知识(客户端是用单纯的socket做的)

    2、基本的socketserver知识(服务器是用socketserver做的,也可以使用socket+select做),socketserver里面的TCPServer,ThreadingMaxIn两个模块

    3、使用简单的线程知识(threaing),这个在客户端获取输入时要求不能阻塞了程序而使用。

    这里面几个模块要想全部理解有点难,但仅仅是用一下,还是很简单的,(比如:笔者也有大量不会的地方,很多地方只是会用,特别是threading模块)

    下面,分步来完成这个软件:

    第一步:服务端,可以接受到来自客户端发送的消息

    from socketserver import TCPServer,ThreadingMixIn,StreamRequestHandler

    class Server(ThreadingMixIn,TCPServer):

          pass

    class Handler(StreamRequestHandler):

        def handle(self):

            self.request.setblocking(0)        #设置为非阻塞模式

            addr = self.request.getpeername()#获取客户端的地址(host,port)

          print("连接的客户端地址:%s"%(addr,))

            while 1:            #这一块是用来接收消息的

                      try:

                              data = self.request.recv(1024)

                print(data.decode())

            except BlockingIOError:

                pass

    server = Server(('127.0.0.1',13333),Handler)#启动服务器

    server.serve_forever()#将服务器挂起,检查是否有事件发生(客户端请求连接)

    在简书里面空格打得不是很方便,所以缩进有问题,但是最好不要直接复制粘贴,最好自己手打一遍。

    在这段代码中,Server继承了ThreadingMixIn和TCPServer两个类,继承TCPServer是因为我们用的是tcp连接,当有客户端连接,就会触发Handler(),继承ThreadingMixIn是为了能够连接多个客户端,如果不继承这个的话,就只能连接一个客户端(底层的原理我也不是很清楚)。

    self.request这个可以类比为s = socket.socket(),我觉得可能socket和socketserver两个都是继承来自一个更底层的套接字模块(我不确定),所以他们的方法也几乎相同,self.request.recv()等同于s.recv()。

    self.request.setblocking(0)        #设置为非阻塞模式,看网上很多都没讲清楚怎么用这个,我也是前几天看着一个很好的博客才懂了这个,这个参数为0设置为非阻塞模式之后,收发消息会变成非阻塞模式的,并且会报BlockingIOError的错,所以需要异常处理。参数如果为正数好像是设置阻塞时间(没有验证过)。

    第二步:差不多了,开始写客户端的代码

    import socket,threading

    s = socket.socket()

    host = socket.gethostbyname("127.0.0.1")#我不是很请楚这个函数的作用,它的返回值还是"127.0.0.1",没变化,也可以不要这个方法,直接写地址就行

    port =13333

    s.connect((host,port))#连接服务器

    id =1#这是本来准备用来给一个编号的,不过后来想想还是让服务器给编号比较好

    def get_input():#获得输入,这是一个子线程

            while 1:

                    data =input("》》")

                    s.send(data.encode())

    while 1:

            threading_input = threading.Thread(target=get_input)

            threading_input.start()#开始子线程

            print(s.recv(1024).decode(encoding="utf-8",errors='ignore'))

    这里需要说一下的就是threading_input = threading.Thread(target=get_input),target的参数是你需要执行的子线程的函数名。

    另外需要注意的就是编码问题最好设置为强制编码,不报错。

    待会你可以创建两个客户端连接服务器,然后他们就可以相互通信,不过现在显然是不可以的,服务器并没有想着要发送信息,那么就先来。

    第三步:让服务器开始发信息

    其实知道了前面的知识,后面的你自己尝试就可以完成,我直接把最终的代码贴出来,并讲述其中需要注意的地方

    from socketserverimport TCPServer,ThreadingMixIn,StreamRequestHandler

    data_list = []#这里是要点1

    class Server(ThreadingMixIn,TCPServer):

            pass

    class Handler(StreamRequestHandler):

            def handle(self):

            #设置为非阻塞模式

            self.request.setblocking(0)

            addr =self.request.getpeername()

            select.append(addr)

            print("连接的客户端地址:%s"%(addr,))

    while 1:

            #这一块是用来接收消息的

            try:

                    data =self.request.recv(1024)

                    data = (data,addr)

                    data_list.append(data)

            except:

                    pass

                #下面一块都是用来发消息的

            for iin data_list:

                    if addr == i[1]:

                            continue

                    try:

                            self.request.sendall(i[0])

                            data_list.remove(i)#这一句话涉及了共有变量的修改,会报错

                    except BlockingIOError:

                            continue

                    except ValueError:#这里是要点2

                            continue

    server = Server(('127.0.0.1',13333),Handler)

    server.serve_forever()

    要点一:线程中只有全局变量是共享的,而且可以相互通信,那么我就利用全局变量来实现将消息发给所有的客户端(除了自己,一个简单的条件判断而已)

    要点二:这个也是有变量共享引起的,讲到这个,我就必须讲一下线程,线程和进程的关系大家应该都知道,不知道可以百度,相对应进程,线程最大的优点就是可以相互通信,但是一个缺点是,在Python中,(下面我谈一下我的理解,虽然看了很多资料,但是我还是不怎么确定),线程在Python中也不是完全同步进行的,是通过时间轮询执行的(个人看法,网上很多大神讲过这个,我说的好像有点问题,不过这只是现在的理解),在极短的时间内,进行着切换,那么现在有一个问题了,一个共享变量很有可能我取到了,但是当我执行下一句代码的时候,这个被另一个线程处理掉了。(这就是我所与到的问题,也有可能是线程通过多核同步进行也会遇到这样的问题,所以我说我不确定。标准的方法是设定锁,不过锁的话有点麻烦,我直接用异常处理来解决了)

    说完了,第四步就是设置一个GUI界面,并且再完善一下就可以用来多人聊天啦~@^@~

    相关文章

      网友评论

        本文标题:一步一步教你做聊天软件(Python实现+非阻塞)

        本文链接:https://www.haomeiwen.com/subject/otnzaxtx.html