基于Python编写一个简单的服务注册发现服务器

2023-05-16 20:05:21 简单 编写 发现

我们都知道有很多的非常著名的注册服务器,例如: ConsulZooKeeperetcd,甚至借助于Redis完成服务注册发现。但是本篇文章我们将使用python Socket写一个非常简单的服务注册发现服务器。

本篇文章所依赖的环境为:

案例展示

项目地址:

gitee: gitee.com/pdudo/Golearn/blob/master/Python/reGISterCenter/registerSer.py

该服务注册服务器,没有使用第三方库,可以复制到本地,使用python registerSer.py启动即可,启动成功后,会输出“注册服务器已经启动...”的信息。

该案例使用的协议是TCP协议,为了防止粘包,客户端在发送消息的时候,使用CRLF进行区分,服务器是按照读取到CRLF进行分割的,服务数据,均采用JSON格式,共支持注册服务、查询服务、销毁服务。

对应的json为:

注册服务:

{"type":"register","key":"redis","values":"123456@127.0.0.1:6379","timeout":"30"}

注册和更新服务都是用的上述报文,其中type 是告诉服务器需要做什么,register代表注册服务,keyvalues是注册服务的名称和注册服务的值,最后的timeout是存活时间,单位是秒,若超过这个时间没有更新服务,则自动销毁该服务。

如果客户端发送的信息没有携带time,则注册存活时间为30s

查询服务:

{"type":"query","key":"redis"}

{"type":"query","key":"all"}

查询服务使用的typequery,查询的key若是all则是查询全部,其他则是查询值为key的服务信息。

销毁服务:

{"type":"destruction","key":"redis"}

销毁服务的typedestructionkey为需要销毁服务的名称。

目前还暂不支持登录验证。

案例测试

由于该注册服务报文采用的是使用CRLF进行分割,所以可以直接使用telnet进行模拟注册,为了方便,我们还是使用python来写这个测试,代码如下:

上述代码,我们需要自己指定\r\n进行分割,我们指定了4个注册,以及等待5s再进行查询,最后再销毁了一个服务。

运行结果如下:

可以看到上述结果,第一个输出是我们注册的消息,而后我们等待了5秒,目的是为了等redis服务因为超时被自动销毁,而后再进行查询,就没有redis2的注册信息了,接着我们销毁了Mysql1的服务,整个测试完毕。

如何来写一个注册发现服务器

我们想要写一个注册服务器,我们至少需要知道以下几个知识点:

  • 服务注册发现核心是啥?
  • 应用层数据报文应该如何定义。
  • 如何从tcp数据流中按条获取记录。

服务器发现核心是啥

python中,我们可以通过定义一个字典,来存储所有注册信息,例如:

{
        "redis": {
        "value": "123456@127.0.0.1:6379",
        "timeout":30,
        "lastUpdateTime": time.time()
        },
        "redis1": {....}
}

我们只需要维护好这个字典,就可以做到最基本的服务注册和服务发现了。

报文如何定义

该服务,我们使用的是json来定义的数据报文,按照type区分,分别为:

  • register: 注册或更新服务。
  • query: 查询服务。
  • destruction销毁服务。

json中,以keyvalue来确定服务名称和服务值,还有一个timeout来定义超时销毁时间。

例如:

{"type":"register","key":"redis1","values":"123456@127.0.0.1:6379","timeout":"30"}
{"type":"query","key":"all"}
{"type":"destruction","key":"mysql1"}

如何从tcp流中获取记录

如何从tcp中读取需要的记录呢?

这个点其实核心是如何解决tcp粘包,我们都知道,tcp socket也被称之为数据流,就是因为一旦三次握手之后,数据就像水龙头一样源源不断流动,我们不知道哪儿是头,哪儿是尾巴。

所以需要指定一个结束符,告知程序,读到哪儿,一条记录就读完了,这里我们每条信息都发送一个\r\n来作为结束符。

编写注册发现服务器

如何从tcp流中读取数据

要解决粘包,有2种方法,第一种是定义特殊字段分隔符,例如: \r\n当读到\r\n的时候,就判断一条消息结束了。

例如,如图:

我们发送了一条记录,hello world如何判断结束了呢? 在最后面加一个\r\n,当程序读到的时候,就判断这条消息接收完了,就可以了。

还有一种方法是,提前计算发送的字节数,然后记录到行首,每次读取的时候,先读取行首的个数,而后更具个数再读取数据,例如:

我们提前计算hello world字符串占用多少个字节,而后再在行首分n个字节来存储个数,例如我们使用2个字节来存储,存储数据为10。

我们这篇服务发现使用的是第一种方法。

那要如何判断\r\n呢?可以参考如下代码:

data = b""
while True:
    data += client.recv(1)
    if data.endswith(b"\r\n"):
        break
data = data[:len(data)-2]

上述代码中,我们先定义了一个空的byte,变量名为data,而后每次读取1个字节,再判断是否以\r\n结尾,如果是以\r\n结尾的话,就跳出循环,跳出循环后,我们需要删除掉\r\n所以会有data = data[:len(data)-2]这个语句,这样的话,我们就成功拿到了记录。

数据包如何区分类型

上个段落,我们已经拿到了一条数据包,那么我们这个服务有3种操作,分别是 查询、注册、销毁,如何客户端发上来的请求呢? 其实如之前所述,我们可以通过type来定义,操作流程为:

  • 解析客户端发上来的json信息。
  • 判断其type类型。

整合为语句的话,可以理解为这样:

上述代码,我们先使用json.loads序列化为json格式,而后根据type信息区分是注册服务、销毁服务还是其查询服务。这里注意的是,若json字符串都没有type或者key类型,则证明该数据包有问题,直接拒绝掉即可。如果不是上述三种服务类型的话,那也证明包有问题,直接拒绝即可。

服务注册

服务注册,需要获取客户端传上来的keyvalues信息,若没有的话,则证明该包有问题,而后判断其timeout是否携带,若没有携带的话,则定义为默认值30s,而后将该值入早已定义好的字典类型中即可,代码如下:

这里,需要注意的是,我们再更新服务信息的时候,会将lastUpdateTime更新到目前的时间戳,方便后续查询服务的时候做惰性删除。

服务销毁

服务销毁,核心点是删除字典中的记录即可,为了避免抛错,还是要检查客户端传上来的json字符串是否携带key,虽然在前面检查过,这个再检查一下,还是有必要的。

销毁服务,就直接删除字典的该记录就可以,如果传入的值,在该字典中没有记录的话,默认为销毁完毕。

代码如下:

服务查询

服务查询,客户端发上来的还是以key为主,若key的值为all的话,则查询所有服务,若不为all的话,则查询单个服务注册的信息,这里在查询的时候,是将服务超时销毁一起做了的,采用的是惰性删除,当查询到该值的是,它会比对超时时间,若已经超时了,则直接删除该对象,代码如下:

上述查询到key后,会做一次超时检测,若超时了,则删除该记录的值,若没有超出时间,再返回信息。

总结

该篇文章,介绍了如何使用python socket写一个服务注册发现,该项目还是有很多不完美的地方,比如: 没有对字典进行加、内存数据没有落地等等。但是可以将它理解为一个初始的demo

该篇文章由于篇幅和时间关系,所以写的更多的是思路,应该如何如何才能完成这个项目,而没有对详细的语法进行梳理,比如 如何开一个多线程来服务多个客户端等,这个后面有时间再详细介绍。

到此这篇关于基于Python编写一个简单的服务注册发现服务器的文章就介绍到这了,更多相关Python服务注册发现服务器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章