网络编程魔法Python 爬虫 web 数据分析 机器学习 人工智能

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

2018-01-28  本文已影响37人  Python雁横

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

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界面,并且再完善一下就可以用来多人聊天啦~@^@~

上一篇 下一篇

猜你喜欢

热点阅读