WEEK11:redis缓存、rabbitMQ队列
- RabbitMQ 消息队列
python调用需要安装pika模块
实现最简单的队列通信,消息代理就是一个中间软件模块,把消息从一个软件服务传递到另外一个软件服务上去。
- 简易消费者生产者模型
- 消费者
1 import pika 2 3 connection=pika.BlockingConnection(pika.ConnectionParameters('localhost')) 4 #声明一个管道 5 channel = connection.channel() 6 # 声明队列 7 channel.queue_declare(queue='hello',durable=True)#durable=True使队列持久化 8 # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange. 9 channel.basic_publish(exchange='', #定义
routing_key='hello',
body='Hello World!',
properties=pike.BasicProperties(delivery_mode=2,abc='alex'))#delivery_mode=2使消息持久化 10 print(" [x] Sent 'Hello World!'") 11 connection.close() - 生产者
1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) 4 channel = connection.channel() 5 #如果确认这个队列声明过了,则可以不在此声明。如果不确定消费者或者生产者先运行,那么可以在两者中都进行声明。 6 channel.queue_declare(queue='hello',durable=True)#durable=True使队列持久化 7 8 def callback(ch, method, properties, body): 9 #ch是管道内存对象地址 10 #method是包含给接收消息者等属性 11 print(ch,method,properties) 12 print(" [x] Received %r" % body)
#客户端对服务端的消息进行手动确认
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)#实现广播效果,消息公平分发 13 #消费消息 14 channel.basic_consume(callback,#如果收到消息,就调用callback函数来处理消息 15 queue='hello')#从哪一个队列里接收消息17 18 print(' [*] Waiting for messages. To exit press CTRL+C') 19 #开始接收消息 20 channel.start_consuming()
- 消费者
- 五种队列(https://www.cnblogs.com/ysocean/p/9251884.html)
- 简单队列
一个生产者对应一个消费者。注意这里消费者有自动确认消息和手动确认消息两种模式 - work模式(竞争消费者模式)
一个生产者对应多个消费之,但只能有一个消费者获得消息。效率高的消费者消费消息多,可以用来进行负载均衡。 - 发布订阅模式
一个消费者将消息首先发送到交换器,交换器绑定到多个队列,然后被监听该队列的消费者所接受并消费。X表示交换器,在RabbitMQ中,交换器主要有四种类型:direct、fanout、topic、headers,这里的交换器是 fanout。 - 路由模式
生产者将消息发送到direct交换器,在绑定队列和交换器的时候有一个路由key,生产者发送的消息会指定一个路由key,那么消息只会发送到相应key相同的队列,接着监听该队列的消费者消费消息。也就是让消费者有选择的接收消息。 - 主题模式
上面的路由模式是根据路由key进行完整的匹配(完全相等才发送消息),这里的通配符模式通俗的来讲就是模糊匹配。
符号“#”表示匹配一个或多个词,符号“*”表示匹配一个词。
- 简单队列
- 四种交换机
交换器分为四种,分别是:direct、fanout、topic和 headers。前面三种分别对应路由模式、发布订阅模式和通配符模式,headers 交换器允许匹配 AMQP 消息的 header 而非路由键,除此之外,header 交换器和 direct 交换器完全一致,但是性能却差很多,因此基本上不会用到该交换器。
- direct
如果路由键完全匹配的话,消息才会被投放到相应的队列。(通过routingKey和exchange决定的那个唯一的queue可以接收消息)
-
生产者
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 7 channel.exchange_declare(exchange='direct_logs', 8 type='direct') 9 10 severity = sys.argv[1] if len(sys.argv) > 1 else 'info' 11 message = ' '.join(sys.argv[2:]) or 'Hello World!' 12 channel.basic_publish(exchange='direct_logs', 13 routing_key=severity, 14 body=message) 15 print(" [x] Sent %r:%r" % (severity, message)) 16 connection.close()
-
消费者
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 channel.exchange_declare(exchange='direct_logs', 7 type='direct') 8 9 result = channel.queue_declare(exclusive=True) 10 queue_name = result.method.queue 11 12 severities = sys.argv[1:] 13 if not severities: 14 sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) 15 sys.exit(1) 16 17 for severity in severities: 18 channel.queue_bind(exchange='direct_logs', 19 queue=queue_name, 20 routing_key=severity) 21 22 print(' [*] Waiting for logs. To exit press CTRL+C') 23 24 25 def callback(ch, method, properties, body): 26 print(" [x] %r:%r" % (method.routing_key, body)) 27 28 29 channel.basic_consume(callback, 30 queue=queue_name, 31 no_ack=True) 32 33 channel.start_consuming()
-
- fanout
当发送一条消息到fanout交换器上时,它会把消息投放到所有附加在此交换器上的队列。(所有绑定到此exchange的队列都可以接收消息(消费者类似收音机,生产者类似电台))
- 生产者
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 channel.exchange_declare(exchange='logs',#转发器 8 type='fanout')#设置模式 9 10 message = ' '.join(sys.argv[1:]) or "info: Hello World!" 11 channel.basic_publish(exchange='logs', 12 routing_key='', 13 body=message) 14 print(" [x] Sent %r" % message) 15 connection.close()
- 消费者
1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 4 channel = connection.channel() 5 6 channel.exchange_declare(exchange='logs',#转发器 7 type='fanout') 8 #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 9 result = channel.queue_declare(exclusive=True) # exclusive排他,唯一的 10 11 queue_name = result.method.queue #获取队列名字 12 13 channel.queue_bind(exchange='logs',queue=queue_name) #将队列绑定到名为logs的exchange 14 15 print(' [*] Waiting for logs. To exit press CTRL+C') 16 17 def callback(ch, method, properties, body): 18 print(" [x] %r" % body) 19 20 channel.basic_consume(callback,queue=queue_name,no_ack=True) 21 channel.start_consuming()
- 生产者
- topic
设置模糊的绑定方式,“*”操作符将“.”视为分隔符,匹配单个字符;“#”操作符没有分块的概念,它将任意“.”均视为关键字的匹配部分,能够匹配多个字符。(所有符合routingKey(此时可以是一个表达式)的bind的queue可以接收消息)
-
生产者
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 channel.exchange_declare(exchange='topic_logs', 7 type='topic') 8 9 routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' 10 message = ' '.join(sys.argv[2:]) or 'Hello World!' 11 channel.basic_publish(exchange='topic_logs', 12 routing_key=routing_key, 13 body=message) 14 print(" [x] Sent %r:%r" % (routing_key, message)) 15 connection.close()
-
消费者
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 channel.exchange_declare(exchange='topic_logs', 7 type='topic') 8 9 result = channel.queue_declare(exclusive=True) 10 queue_name = result.method.queue 11 12 binding_keys = sys.argv[1:] 13 # #号 代表收所有消息 14 # "kern.*" 代表收以kern开头的消息 15 # "*.critical" 代表收所有critical结尾的消息 16 # "kern.*" "*.critical" 同时收以上两者 17 if not binding_keys: 18 sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) 19 sys.exit(1) 20 21 for binding_key in binding_keys: 22 channel.queue_bind(exchange='topic_logs', 23 queue=queue_name, 24 routing_key=binding_key) 25 26 print(' [*] Waiting for logs. To exit press CTRL+C') 27 28 29 def callback(ch, method, properties, body): 30 print(" [x] %r:%r" % (method.routing_key, body)) 31 32 33 channel.basic_consume(callback, 34 queue=queue_name, 35 no_ack=True) 36 37 channel.start_consuming()
-
- direct
- 简易消费者生产者模型
- redis缓存
- 使用yum安装好redis之后,使用redis-cli进入
- 存储
set name alex
set age 22
set name jack ex 2 #只存活2s的时间 - 查询目前所有的键
keys * - 查询值
get name
get age
- 存储
- python调用
- 连接
1 import redis 2 #>>>>>>>>>>>>>>>>>>>>>连接方法1 3 #连接 4 r = redis.Redis(host='10.211.55.4', port=6379) 5 #存储值 6 r.set('foo', 'Bar') 7 #获取值 8 print(r.get('foo')) 9 10 #>>>>>>>>>>>>>>>>>>>>>连接方法2 11 #创建连接池 12 pool=redis.ConnectionPool(host='10.211.55.4', port=6379) 13 #连接 14 r=redis.Redis(connection_pool=pool) 15 #存储值 16 r.set('foo', 'Bar') 17 #获取值 18 print(r.get('foo'))
- 操作
- string操作
redis中的String在在内存中按照一个name对应一个value来存储在Redis中设置值,不存在则创建,存在则修改
set(name, value, ex=None, px=None, nx=False, xx=False)
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行
xx,如果设置为True,则只有name存在时,当前set操作才执行
- setnx(name, value)
设置值,只有name不存在时,执行设置操作(添加)
- setex(name, value, time)
time,过期时间(数字秒 或 timedelta对象)
- psetex(name, time_ms, value)
time_ms,过期时间(数字毫秒 或 timedelta对象)
- mset(*args, **kwargs)
批量设置值
mset(k1=
'v1'
, k2=
'v2'
)或者
mget({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
- get(name)
获取值
- mget(keys, *args)
批量获取
mget(
'ylr'
,
'wupeiqi'
)或者
r.mget([
'ylr'
,
'wupeiqi'
])
- getset(name, value)
设置新值并获取返回原来的值(这个需要之前存在)
- getrange(name, start, end)
获取子序列(根据字节获取,非字符),可以对字符串进行切片
start,起始位置(字节);
end,结束位置(字节)
- setrange(name, offset, value)
修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
offset,字符串的索引,字节(一个汉字三个字节)
value,要设置的值
- setbit(name, offset, value)
对name对应值的二进制表示的位进行操作
offset,位的索引(将值变换成二进制后再进行索引)
value,值只能是 1 或 0
- getbit(name, offset)
获取name对应的值的二进制表示中的某位的值 (0或1)
- bitcount(name, start=None, end=None)
获取name对应的值的二进制表示中 1 的个数
start,位起始位置;
end,位结束位置
- strlen(name)
返回name对应值的字节长度(一个汉字3个字节)
- incr(self, name, amount=1)
自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增
amount,自增数(必须是整数)
- incrbyfloat(self, name, amount=1.0)
自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增
amount,自增数(浮点型)
- decr(self, name, amount=1)
自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减
amount,自减数(整数)
- append(key, value)
在redis name对应的值后面追加内容
value, 要追加的字符串
- hash操作
hash表现形式上有些像pyhton中的dict,可以存储一组关联性较强的数据- hset(name, key, value)
name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
key,name对应的hash中的key;
value,name对应的hash中的value
hsetnx(name, key, value),当name对应的hash中不存在当前key时则创建(相当于添加)
- hmset(name, mapping)
在name对应的hash中批量设置键值对
mapping,字典,如:{'k1':'v1', 'k2': 'v2'},例如
hmset('xx', {'k1':'v1', 'k2': 'v2'})
- hget(name,key)
在name对应的hash中获取根据key获取value
- hmget(name, keys, *args)
在name对应的hash中获取多个key的值
keys,要获取key集合,如:['k1', 'k2', 'k3'];
*args,要获取的key,如:k1,k2,k3,例如
r.hmget('xx', ['k1', 'k2'])或者
r.hmget('xx', 'k1', 'k2')
- hgetall(name)
获取name对应
hash
的所有键值
- hlen(name)
获取name对应的hash中键值对的个数
- hkeys(name)
获取name对应的hash中所有的key的值
- hvals(name)
获取name对应的hash中所有的value的值
- exists(name, key)
检查name对应的hash是否存在当前传入的key
- hdel(name,*keys)
将name对应的hash中指定key的键值对删除
- hincrby(name, key, amount=1)
自增name对应的hash中的指定key的值,不存在则创建key=amount
- hincrbyfloat(name, key, amount=1.0)
自增name对应的hash中的指定key的值,不存在则创建key=amount
- hscan(name, cursor=0, match=None, count=None)
增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而防止内存溢出
cursor,游标(基于游标分批取获取数据);
match,匹配指定key,默认None 表示所有的key;
count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
- hscan_iter(name, match=None, count=None)
利用yield封装hscan创建生成器,实现分批去redis中获取数据
match,匹配指定key,默认None 表示所有的key;
count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
- hset(name, key, value)
- list操作
List操作,redis中的List在在内存中按照一个name对应一个List来存储- lpush(name,values)
在name对应的list中添加元素,每个新的元素都添加到列表的最左边
r.lpush('oo', 11,22,33),
保存顺序为: 33,22,11
rpush(name, values) 表示从右向左操作
- lpushx(name,value)
在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
rpushx(name, value) 表示从右向左操作
- llen(name)
name对应的list元素的个数
- linsert(name, where, refvalue, value))
在name对应的列表的某一个值前或后插入一个新值
where,BEFORE或AFTER;
refvalue,标杆值,即:在它前后插入数据;
value,要插入的数据
- lset(name, index, value)
对name对应的list中的某一个索引位置重新赋值
index,list的索引位置;
value,要设置的值
- lrem(name, value, num)
在name对应的list中删除指定的值
value,要删除的值;
num=0,删除列表中所有的指定值;
num=2,从前到后,删除2个;
num=-2,从后向前,删除2个
- lpop(name)
在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
rpop(name) 表示从右向左操作
- lindex(name, index)
在name对应的列表中根据索引获取列表元素
- lrange(name, start, end)
在name对应的列表分片获取数据
- ltrim(name, start, end)
在name对应的列表中移除没有在start-end索引之间的值
- rpoplpush(src, dst)
从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
- blpop(keys, timeout)
将多个列表排列,按照从左到右去pop对应列表的元素
timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
brpop(keys, timeout),从右向左获取数据
- brpoplpush(src, dst, timeout=0)
从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
- lpush(name,values)
- set操作
Set操作,Set集合就是不允许重复的列表- sadd(name,values)
name对应的集合中添加元素
- scard(name)
获取name对应的集合中元素个数
- sdiff(keys, *args)
在第一个name对应的集合中且不在其他name对应的集合的元素集合
- sdiffstore(dest, keys, *args)
获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中
- sinter(keys, *args)
获取多一个name对应集合的并集
- sinterstore(dest, keys, *args)
获取多一个name对应集合的并集,再讲其加入到dest对应的集合中
- sismember(name, value)
检查value是否是name对应的集合的成员
- smembers(name)
获取name对应的集合的所有成员
- smove(src, dst, value)
将某个成员从一个集合中移动到另外一个集合
- spop(name)
从集合的右侧(尾部)移除一个成员,并将其返回
- srandmember(name, numbers)
从name对应的集合中随机获取 numbers 个元素
- srem(name, values)
在name对应的集合中删除某些值
- sunion(keys, *args)
获取多一个name对应的集合的并集
- sunionstore(dest,keys, *args)
获取多一个name对应的集合的并集,并将结果保存到dest对应的集合中
- sscan(name, cursor=0, match=None, count=None)或sscan_iter(name, match=None, count=None)
同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。 - zadd(name, *args, **kwargs)
在name对应的有序集合中添加元素,例如:zadd('zz', 'n1', 1, 'n2', 2)或
zadd('zz', n1=11, n2=22)
- zcard(name)
获取name对应的有序集合元素的数量
- zcount(name, min, max)
获取name对应的有序集合中分数 在 [min,max] 之间的个数
- zincrby(name, value, amount)
自增name对应的有序集合的 name 对应的分数
- zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
按照索引范围获取name对应的有序集合的元素
start,有序集合索引起始位置(非分数);
end,有序集合索引结束位置(非分数);
desc,排序规则,默认按照分数从小到大排序;
withscores,是否获取元素的分数,默认只获取元素的值;
score_cast_func,对分数进行数据转换的函数
从大到小排序:
zrevrange(name, start, end, withscores=False, score_cast_func=float)
按照分数范围获取name对应的有序集合的元素:
zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
从大到小排序:
zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
- zrank(name, value)
获取某个值在 name对应的有序集合中的排行(从 0 开始)
zrevrank(name, value),从大到小排序
- zrem(name, values)
删除name对应的有序集合中值是values的成员
- zremrangebyrank(name, min, max)
根据排行范围删除
- zremrangebyscore(name, min, max)
根据分数范围删除
- zscore(name, value)
获取name对应有序集合中 value 对应的分数
- zinterstore(dest, keys, aggregate=None)
获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作
aggregate的值为: SUM MIN MAX
- zunionstore(dest, keys, aggregate=None)
获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作
- zscan(name, cursor=0, match=None, count=None, score_cast_func=float)或zscan_iter(name, match=None, count=None,score_cast_func=float)
同字符串相似,相较于字符串新增score_cast_func,用来对分数进行操作
- sadd(name,values)
- 其他常用操作
- delete(*names)
根据删除redis中的任意数据类型
- exists(name)
检测redis的name是否存在
- keys(pattern='*')
根据模型获取redis的name
KEYS * 匹配数据库中所有 key;
KEYS h?llo 匹配 hello , hallo 和 hxllo 等;
KEYS h*llo 匹配 hllo 和 heeeeello 等;
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
- expire(name ,time)
为某个redis的某个name设置超时时间
- rename(src, dst)
对redis的name重命名为
- move(name, db)
将redis的某个值移动到指定的db下
- randomkey()
随机获取一个redis的name(不删除)
- type(name)
获取name对应值的类型
- scan(cursor=0, match=None, count=None)或scan_iter(match=None, count=None)
同字符串操作,用于增量迭代获取key
- select 2
切换到数据库2,默认只有16(0~15)张表 - get db_number 1
切换到数据库1
- delete(*names)
- string操作
- 管道
redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作
1 import redis,time 2 #连接redis 3 pool = redis.ConnectionPool(host='10.211.55.4', port=6379) 4 r = redis.Redis(connection_pool=pool) 5 6 #启动管道 7 pipe = r.pipeline(
transaction
=
True
) #或者pipe=r.pipeline() 8 9 #执行命令,两条命令一起执行 10 pipe.set('name', 'alex')
time.sleep(30) 11 pipe.set('role', 'sb') 12 13 pipe.execute() - 发布订阅
- Demo
1 #redishelper.py 2 import redis 3 4 class RedisHelper: 5 def __init__(self): 6 self.__conn = redis.Redis(host='10.211.55.4') 7 self.chan_sub = 'fm104.5' 8 self.chan_pub = 'fm104.5' 9 10 def public(self, msg):#发布者 11 self.__conn.publish(self.chan_pub, msg) 12 return True 13 14 def subscribe(self):#订阅者 15 pub = self.__conn.pubsub()#打开收音机 16 pub.subscribe(self.chan_sub)#调频道 17 pub.parse_response()#准备接收 18 return pub
- 订阅者
1 #订阅者.py 2 from redishelper import RedisHelper 3 4 obj = RedisHelper() 5 redis_sub = obj.subscribe() 6 7 while True: 8 msg = redis_sub.parse_response() 9 print(msg)
- 发布者
1 #发布者.py 2 from redishelper import RedisHelper 3 obj = RedisHelper() 4 obj.public('hello')
- Demo
- 连接
- 使用yum安装好redis之后,使用redis-cli进入
- 基于RabbitMQ的RPC实现
- 服务端
1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 4 channel = connection.channel() 5 channel.queue_declare(queue='rpc_queue') 6 7 def fib(n): #求斐波那契数列 8 if n == 0: 9 return 0 10 elif n == 1: 11 return 1 12 else: 13 return fib(n - 1) + fib(n - 2) 14 15 16 def on_request(ch, method, props, body): 17 n = int(body) 18 print(" [.] fib(%s)" % n) 19 response = fib(n) 20 ch.basic_publish(exchange='', 21 routing_key=props.reply_to, 22 properties=pika.BasicProperties(correlation_id=props.correlation_id), 23 body=str(response)) 24 ch.basic_ack(delivery_tag=method.delivery_tag) 25 26 channel.basic_qos(prefetch_count=1) 27 channel.basic_consume(on_request, queue='rpc_queue') 28 print(" [x] Awaiting RPC requests") 29 channel.start_consuming()
- 客户端
1 import pika,uuid 2 3 class FibonacciRpcClient(object): 4 def __init__(self): 5 self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 6 self.channel = self.connection.channel() 7 result = self.channel.queue_declare(exclusive=True) 8 self.callback_queue = result.method.queue 9 self.channel.basic_consume(self.on_response, no_ack=True,queue=self.callback_queue) 10 11 def on_response(self, ch, method, props, body): 12 if self.corr_id == props.correlation_id: 13 self.response = body 14 15 def call(self, n): 16 self.response = None 17 self.corr_id = str(uuid.uuid4()) 18 self.channel.basic_publish(exchange='', 19 routing_key='rpc_queue', 20 properties=pika.BasicProperties( 21 reply_to=self.callback_queue, 22 correlation_id=self.corr_id, 23 ), 24 body=str(n)) 25 while self.response is None: 26 self.connection.process_data_events() 27 return int(self.response) 28 29 30 fibonacci_rpc = FibonacciRpcClient() 31 print(" [x] Requesting fib(30)") 32 response = fibonacci_rpc.call(30) 33 print(" [.] Got %r" % response)
- 服务端
仰天大笑出门去,吾辈岂是蓬蒿人!