首先,我们需要知道实现怎么样的聊天:
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界面,并且再完善一下就可以用来多人聊天啦~@^@~
网友评论