Python之RabbitMQ的使用
今天总结一下Python关于Rabbitmq的使用
RabbitMQ官网说明,其实也是一种队列,那和前面说的线程queue和进程queue有什么区别呢?
线程queue只能在同一个进程下进行数据交互
进程queue只能在父进程和子进程之间,或者同一父进程下的子进程之间做数据交互
如果需要对不同进程(eg:微信和qq)两个独立的程序间通信
方法1就是直接把数据写在硬盘(disk)上然后各自的进程读取数据就可以,但是由于硬盘的读写速度太慢,效率太低
方法2自己写个socket,直接做数据交互,问题是如果改变程序,或者再加一个程序,需要对写好的socket进行修改,还要处理黏包什么的复杂的连接关系,维护成本太高。
方法3,利用已有的中间商(代理)。这个broker其实就是封装好的socket,我们拿来直接用就好了。
这里的broker,就有RabbitMQ,ZeroMQ,ActiveMQ等等等等。
一.安装及环境配置
windows的安装和配置方法较为简单,直接安装就好了
Rabbit支持多种语言: Java, .NET, PHP, Python, JavaScript, Ruby, Go这些常用语言都支持
如图所示,python操作RabbitMQ需要的模块有上述几种选择,我们用最简单的pika,用pip直接安装
pip install pika
二.RabbitMQ的使用
这里所有的用法都是基于RabbitMQ是工作在‘localhost’上,并且端口号为15672,能在浏览器里访问http://localhost:15672这个地址。
1.消息分发(基础版)
这就是RabbitMQ最简单的工作模式,p为生产者(Producer),生产者发送message给queue,queue再把消息发送至消费者c(Customer)
先看看生产者至队列(send)这个过程
import pika
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
我们先建立了一个链接,然后就需要定义一个队列,队列的名字就暂时定位‘hello'
channel.queue_declare(queue='hello')
在RabbitMQ里消息并不能直接发送给队列,所有的信息发送都要通过一个exchange,但是这里我们先把这个exchange定义成一个空的字符串,后面在将他的具体用法
channel.basic_publish(exchange='',
routing_key='hello',
body='')
在发送确认完成后,可以将连接关闭
connect.close()
这就是send端的代码
import pika
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='',
routing_key='hello',
body='')
print("[x] Sent 'hello world!'")
connect.close()
RabbitMQ_basic_producer
运行了send代码后我们可以在terminal里RabbitMQ安装目录下sbin文件夹里查看一下消息队列
rabbitmqctl.bat list_queues
如果是Linux命令为
sudo rabbitmqctl list_queues
这里就说明了队列信息和消息状态。
然后再看一下消费者这一端的代码是什么样的,同样,先要建立连接并定义好队列名
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello')
这里可能有个疑问:我们不是在生产者里已经定义了队列名吗?为什么在消费者里还要定义呢?
因为在实际工作中,我们并不能确定是生产者还是消费者先一步运行,如果队列名没有定义的话运行时候是会报错的。下面就是对消息的处理
def callback(ch,method,properties,body):
print("[x] Received %r"%body) channel.basic_consume(callback,
queue='hello',
no_ack=True)
当消息来临时,消费者会执行回调函数callback。这里的callback就是直接打印消息内容(body)。
回调函数另外的几个参数:ch是conne.channel的内存对象地址,
<BlockingChannel impl=<Channel number=1 OPEN conn=<SelectConnection OPEN socket=('::1', 62145, 0, 0)->('::1', 5672, 0, 0) params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>>>
method是包含了发送的信息
<Basic.Deliver(['consumer_tag=ctag1.9ae48c906b014a83a512413c0e6f9ef8', 'delivery_tag=1', 'exchange=', 'redelivered=False', 'routing_key=hello'])>
properties我们以后再了解。
2.公平分发(workqueue)
在这种结构里,我们要考虑到这样一种情况:有多个消费者,消费者在得到消息时需要对消息进行处理,并且有可能处理消息所消耗的时间是不同的
。这里我们用的queue叫做workqueue。
为了模拟消费者对消息进行处理的过程,我们用time.sleep()做一个消耗时间的过程。消息的产生和接收是这样的
message = ' '.join(sys.argv[1:]) or "Hello World!"
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
time.sleep(body.count(b'.'))
print(" [x] Done")
这里插播一条''.join(sys.argv[1:])的作用:就是把在shell里输入的指令后跟的代码加在message里。消费者得到消息后,数消息里有几个“.”,sleep相应的秒数。
放出第一版的代码
import pika,sys
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello')
message = ' '.join(sys.argv[1:]) or "Hello World."
channel.basic_publish(exchange='',
routing_key='hello',
body=message)
print('send %s'%message)
RabbitMQ生产者
import time,pika connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello') print(' [*] Waiting for messages. To exit press CTRL+C') def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
time.sleep(body.count(b'.'))
print(" [x] Done")
ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_consume(callback,
queue='hello')
channel.start_consuming()
RabbbitMQ消费者
这时候,我们可以多启动几个消费者,再用生产者发送消息,看看效果!
可以发现,消息是被公平的依次被分发给各个消费者的(Fair dispatch),这种分发的方式叫轮询。
消息确认message acknowledgments
现在考虑下这种情形:消费者在处理消息时需要较长的时间,在这时把这个消费者kill掉,正在处理的消息和已经接收但未被处理的消息就丢失了。这应该是不允许的,我们可不希望有数据丢失,就需要将这些任务重新发送给其他正常工作的消费者。
为了保证任务不丢失,RabbitMQ支持使用message acknowledgments,消费者在完成任务后会给RabbitMQ发送个消息,告诉他活已经干完了,RabbitMQ就会把这个任务给释放掉。而当出现消费者宕机、掉线等情况时,RabbitMQ会重新把这个任务发送给其他的消费者。
往回看看上文说到的no_ack,这个值默认的是False,RabbitMQ是不主动销毁消息的所以我们一看看在这里把值置为True。
channel.basic_consume(callback,
queue='hello',
no_ack=True)
这样只要消费者接收到消息,RabbitMQ就直接销毁掉这个消息,就成了手动确认。我们要想实现刚才说的消息不丢失,就要这样定义
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# time.sleep(body.count(b'.'))
time.sleep(10) #修改了一下,在延时的10s把消费者断掉
print(" [x] Done")
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(callback,
queue='hello')#这里的no_ack默认为False
这样,当一个消费者宕机了,RabbitMQ就会直接把任务拍个下一个消费者。
消息持久化
刚才通过了消息确认,我们保证了消费者在掉线的时候任务不丢失,可是还有一个问题,如果RabbitMQ如果断掉(或者服务重启)了,里面的任务(包括所有queue和exchange依旧会丢失)这时候我们可以用到——消息持久化Message durability
channel.queue_declare(queue='hello',durable=True)#将队列持久化(只保存了队列)
channel.basic_publish(exchange='',
routing_key='hello',
body=message,
properties=pika.BasicProperties(delivery_mode=2))#保持消息持久化
必须同时将队列和消息持久化,可以保证RabbitMQ服务在重启后任务还存在。
注意几点:
1.如果只持久化了消息,服务重启后消息丢失
2.如果只持久化了队列,服务重启后队列还在,但消息丢失
3.在持久化队列的时候要保持生产者和消费者的一致性
最后一点,因为有可能每个消费者处理信息的能力不一样,如果按公平分发的化有可能导致负载不平衡,旱的旱死、涝的涝死。为避免这种情况发生还有一个知识点
channel.basic_qos(prefetch_count=1)
用这个语句限制了消费者待处理信息的个数
workQueue的终极代码
import pika,sys
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello',durable=True)#队列持久化
message = ' '.join(sys.argv[1:]) or "Hello World2."
channel.basic_publish(exchange='',
routing_key='hello',
body=message,
properties=pika.BasicProperties(delivery_mode=2)) #消息持久化
print('send %s'%message)
RabbitMQ_workqueue_procucer
import time,pika
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.queue_declare(queue='hello',durable=True)#队列持久化
print(' [*] Waiting for messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
time.sleep(body.count(b'.'))
print(" [x] Done")
ch.basic_ack(delivery_tag = method.delivery_tag) #消息回执
channel.basic_qos(prefetch_count=1) #限制消费者待处理任务个数
channel.basic_consume(callback,
queue='hello'
)
channel.start_consuming()
RabbitMQ_workqueue_customer
3.发布/订阅(publish/subscribe)
我们在前面两部分将的都是将消息由生产者到消费者之间通过queue传递,现在将引入一个新的成员:exchange。
其实生产者在发送的时候是不知道消息要发送给那个queue的,甚至他都不知道消息是由queue接收的。实际上生产者只是把message发送给了exchange。至于message后续的处理都是由exchange决定的。
就像图上标示的,exchange在sender和queue之间起到了转呈的作用。
按照工作方式,我们将exchange分成了fanout、direct、topic和headers四种类型。
fanout:所有绑定到这个exchange的队列都接收消息
direct:通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic:所有符合routingKey(可以是表达式)的queue可以接收消息
表达式说明:#表示一个或多个字符
*表示任何字符
使用RoutingKey为#时相当于fanout
headers:通过headers来决定把消息发送给哪些queue。
在这个part我们来看fanout的作用。
channel.exchange_declare(exchange='logs',
exchange_type='fanout')
我们定义一个exchange,名字随便起一个‘logs’,类型就声明为fanout。
(在前面两节我们还没有引入exchange这个概念,就用了默认的exchange设置
channel.basic_publish(exchange='',
routing_key='hello',
body='')
,exchange=''空的字符串表示了默认的exchange或名字是空的,那exchange就把消息发送给routing_key指定的queue里(前提是这个queue是存在的),在声明了exchange以后,我们就可以用这个exchange发送消息了
channel.basic_publish(exchange='logs', #使用的exchange名称
routing_key='', #使用的队列名称
body='') #消息内容
注意到了一点没有?这里并没有定义队列的名称?为什么?在广播的时候是不用固定具体的哪个queue的,我们
result = channel.queue_declare() #生成随机queue
我们在消费端声明queue的时候可以生成一个随机的queue,这里还要加个命令
result = channel.queue_declare(exclusive=True)
这个exclusive表示在连接在关闭以后这个queue直接被销毁掉。
然后把这个queue绑定在转发器上。所有进入这个exchange的消息被发送给所有和他绑定的队列里。
随机的queue已经声明了,现在就把他跟exchange绑定
channel.queue_bind(exchange='logs',
queue=result.method.queue)#注意queue名的获取方法
这就是最终的代码:
import pika
import sys
connect = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connect.channel()
channel.exchange_declare(exchange='logs',
exchange_type='fanout') #logs 是随便起的名字,声明了exchange
message = ' '.join(sys.argv[1:]) or 'info: Hello World!'
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
fanout_publish
import pika
connect = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connect.channel()
channel.exchange_declare(exchange='logs',exchange_type='fanout')
result = channel.queue_declare(exclusive=True) #exclusive 唯一的,为True时不指定queue名的化随机生成一个queue,
# 在断开连接后把queue删除,相当于生成一个随机queue
channel.queue_bind(exchange='logs',
queue=result.method.queue) #绑定的是exchange对应的queue
print('waiting for logs.')
def callback(ch,method,preproteries,body):
print('get data:%r'%body)
channel.basic_consume(callback,
queue=result.method.queue,
no_ack=True)
channel.start_consuming()
fanout_customer
总体看一下,发送端的代码跟前面的差不太多,最重要的差别就是把routingKey给忽略掉了,但是明确了exchange的对象。
而接收方是在建立连接后要声明exchange,并且要和队列绑定。如果没有队列和exchange绑定,消息就被销毁了。这就是整个发送的过程
还有一点,这个订阅——发布的模型就像电台和收音机一样,如果customer下线了是收不到信息的,消息也是在线发送的,并不会保存。
4.routing(exchange type:direct)
在这个过程中,我们大致了解了发布——订阅模型。其实就是在发送端定义了一个exchange,在接收端定义了一个队列,然后把这两者绑定,就OK了。可是我们现在只想订阅一部分有用的信息,比如只获取错误信息写到日志文件里,但同时又能将所有的信息都显示在控制台(或者terminal上)。
上一节所讲述的bind,也可以简单的理解为这个queue对这个exchange的内容“感兴趣”。
在binding的时候,还可以加一个routingKey的参数,这个参数就描述了queue对哪些exchange感兴趣。
channel.queue_bind(exchange='logs', #被绑定的exchange名
queue='queue_name', #被绑定的queue名
routing_key='black') #queue的‘兴趣爱好’
对queue和exchange进行bind时,bind的参数主要取决于exchange的类型,比如在fanout模式下是不能有这个routingKey的,运行时候会报错。
我们使用了fanout的发布订阅模式,在这个模式下接收端不能对信息进行一定原则的过滤,一股脑的照单全收,已经不能满足我们的要求了,现在就要用direct模式。
在上面的图里,有两个queue分别和exchange绑定,Q1的routingKey是orange,Q2则有两个分别是black和green。在这个模型中,发布的消息关键字是orange则被分发到Q1内,而包含有black或green的则发给Q2.剩余的消息就被discard了。
而在上图中,同样的key同多个队列进行绑定的方法也是合法的。所有包含关键字black的消息会被同时发送 给Q1和Q2。
了解了上面所说的方法,我们来按照本节一开始的目标来修改下代码
首先要声明exchange
channel.exchange_declare(exchange='logs',
exchange_type='direct')#声明exchange,注意接收端的代码是一样的
在发送的时候对消息进行分类
serverity = sys.argv[1] if len(sys.argv)>1 else 'info'
然后发送消息
channel.basic_publish(exchange='logs',
routing_key=serverity, #消息的分类
body=message)
在接收端,我们用一个循环把所有的routingKey和queue绑定(有可能出现多个关键字和一个queue同时绑定的情况)
servrities = sys.argv[1:] #获取所有的关键字
if not servrities:
sys.stderr.write('Usage: %s [info] [warning] [error]\n'%sys.argv[0])
sys.exit(1) #关键字不存在打印提示后退出
print('recived:%s'%servrities)
for servrity in servrities: #循环绑定
channel.queue_bind(exchange='logs',
queue=queue_name,
routing_key=servrity)
整个方案就是这样的
我们启动两个terminal,按这样的方式启动
python direct_consumer.py info error warning
python direct_consumer.py error
在分别发送
python direct_publisher.py info
python direct_publisher.py error
python direct_publisher.py warning
看看是什么效果
是不是达到了订阅的效果!
4.更加细致的消息过滤(topic模式)
在上一节我们利用了direct的模式实现了初步的消息过滤,在这一节里,我们要看看如何实现如何实现更加细致的消息过滤,比如我在获取info的前提下还要知道哪些message是RabbitMQ发来的,哪些是Redis发来的,那怎么区分呢?
就想这个图里的一样,我们在定义RoutingKey的时候利用了表达式,就像模糊查询一样其中
*表示任意一个字符
#表示0个或多个字符
topic模式的代码和上一节的基本一致,只是改变了exchange的模式
channel.exchange_declare(exchange='logs',
exchange_type='topic')#声明exchange
启动terminal,输入指令
python topic_customer kern.* 可以接收以kern.开头的所有消息 kern.123 abc 接收到abc
python topic_customer.py *.kern.* 中间包含.kern.的消息 .kern. abc 接收到abc
同时绑定多个关键字
接收端
d:\python\week11>python topic_customer.py kern.* pip.*
['kern.*', 'pip.*']
[*] Waiting for logs. To exit press CTRL+C
[x] 'pip.11':b'duziele'
[x] 'kern.11':b'duziele'
发送端
d:\python\week11>python topic_publisher.py pip. duziele
[x] Sent 'pip.11':'duziele'
d:\python\week11>python topic_publisher.py kern. duziele
[x] Sent 'kern.11':'duziele'
还可以用#获取所有消息
d:\python\week11>python topic_customer.py #
ps:#的作用我一直不大明白,我试过了
d:\python\week11>python topic_customer.py kern.#
效果和kern.*是一样的。
6.Remote procedure call(RPC)
我们在前面的章节将到了在多个消费者之间分发耗时任务的方法,可是现在要实现这样的功能:调用远程的设备上的一个函数,然后等执行完毕返回结果。这样的工作模式就叫远程过程调用——Remote Procedure Call(RPC)。
利用RabbitMQ也可以实现RPC的功能,为了能模拟这个过程,我们在server端设立一个fun:给定一个整数n,然后返回n对应的斐波那契数列。
callback queue
通过RabbitMQ实现RPC的方法很简单——客户端发送请求,服务端对请求响应然后把消息发送至叫callback的queue,过程类似这样
result = channel.queue_declare(exclusive=True)
callback_queue = result.method.queue channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to = callback_queue,
),
body=request)
Correlation id
我们刚才为每个请求对应的响应都声明了一个队列,但是在这里等待着结果的返回效率是不是太低了?还好有另外的一种方法:为每个客户端创建一个callback的队列。然而又引发了一个新问题:在这个队列里我不知道哪个响应是对应我这个请求的!这时候就到大神出马了——Correlation ID。对每个请求都设置一个唯一的ID,在callback的队列里通过查看属性来判断他对应哪个请求。如果出现没有对应的ID,安全起见我们还是把他忽略掉。
总之我们的RPC的工作流程就是这样的:
1.client启动,声明一个匿名的callback queue
2.建立RPC请求,请求里除了消息还包含两个参数:a.replay_to(告诉server响应的结论callback的队列里)
b.correlation_id:每个请求都被赋予一个独一无二的值
3.请求被发送给RPC_queue
4.server等待queue里的消息,一旦出现请求,server响应请求并把结论发送给通过replay_to要求的queue里
5.client在callback_queue里等待数据,一旦消息出现,他将correlation进行比对,如果相同就获取请求结果。
import pika connect = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connect.channel() channel.queue_declare(queue='rpc_queue') def fib(n):
if n == 0:
return 0
elif n == 1:return 1
else:return fib(n-1)+fib(n-2) def on_request(ch,method,props,body):
n = int(body)
print('[.]fib(%s)'%n)
response = fib(n)
print(response) ch.basic_publish(exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(
correlation_id=props.correlation_id),
body = str(response))
print('send over')
ch.basic_ack(delivery_tag=method.delivery_tag) #确认client接收到消息 channel.basic_qos(prefetch_count=1) #限制消息处理个数
channel.basic_consume(on_request,queue='rpc_queue') print('[x]Awaitiong RPC requests')
channel.start_consuming()
RPC_server
import pika
import uuid class FibonacciRpcClient(object):
def __init__(self):
self.connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
result = self.channel.queue_declare(exclusive=True)
self.callback_queue = result.method.queue #声明请求响应回执的queue self.channel.basic_consume(consumer_callback=self.on_response,
queue=self.callback_queue,no_ack=True) #监听回执queue def on_response(self,ch,method,props,body): #callback_queue的回调函数
print(body)
if self.corr_id == props.correlation_id:
self.response = body def call(self,n):
self.response = None
self.corr_id = str(uuid.uuid4())
self.channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties #发送回执消息的参数
(reply_to=self.callback_queue,
correlation_id=self.corr_id),
body=str(n)
)
while self.response is None:
self.connection.process_data_events() #事件驱动,非阻塞版的start_consuming
return int(self.response) fibonacci_rpc = FibonacciRpcClient()
n = input('>>>')
print('[x] Requesting fib(%s)'%n)
response = fibonacci_rpc.call(n)
print(response)
RPC_client
以上就是RabbitMQ的常规用法。
Python之RabbitMQ的使用的更多相关文章
- Python操作RabbitMQ
RabbitMQ介绍 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现的产品,RabbitMQ是一个消息代理,从“生产者”接收消息并传递消 ...
- 用 Python、 RabbitMQ 和 Nameko 实现微服务
用 Python. RabbitMQ 和 Nameko 实现微服务 原创 07-17 17:57 首页 Linux中国 "微服务是一股新浪潮" - 现如今,将项目拆分成多个独立的. ...
- python之RabbitMQ
一.安装RabbitMQ 1. 安装erlang 1 2 3 4 tar xf otp_src_18.3.tar.gz cd otp_src_18.3 ./configure --prefix=/ma ...
- Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
Python之路[第九篇]:Python操作 RabbitMQ.Redis.Memcache.SQLAlchemy Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用 ...
- python - 操作RabbitMQ
python - 操作RabbitMQ 介绍 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议.MQ全称为Mess ...
- 文成小盆友python-num12 Redis发布与订阅补充,python操作rabbitMQ
本篇主要内容: redis发布与订阅补充 python操作rabbitMQ 一,redis 发布与订阅补充 如下一个简单的监控模型,通过这个模式所有的收听者都能收听到一份数据. 用代码来实现一个red ...
- Python之路第十二天,高级(4)-Python操作rabbitMQ
rabbitMQ RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列(M ...
- Python与RabbitMQ交互
RabbitMQ 消息队列 成熟的中间件RabbitMQ.ZeroMQ.ActiveMQ等等 RabbitMQ使用erlang语言开发,使用RabbitMQ前要安装erlang语言 RabbitMQ允 ...
- python中RabbitMQ的使用(安装和简单教程)
1,简介 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现的产品,RabbitMQ是一个消息代理,从"生产者"接收消息 ...
随机推荐
- Excel技巧--实现交叉查询
如上图,要实现某个地区和某个产品的销售额查询显示.可以使用Match和Index函数的使用来实现: 1.产品名称和城市栏,制作成列表可选:使用“数据”-->“数据验证”的方法. 2.先在旁边空位 ...
- 第一个Unity3D脚本
学习就该简单粗暴,看了一天Unity3d的教程加文档,尝试一个小练习,再快速写个博客加深印象. 一:首先建立一个空白工程,创建一个空GameObject,在Assets Pannel中创建一个名为Le ...
- ubuntu彻底卸载opencv
说正事之前,先啰嗦两句背景,算是拿个小本本记下了. 我本打算下载opencv2.4.在github上找到源码,在Branch处选择切换到2.4,然后复制URL,在terminal里面使用git clo ...
- 黄聪:分享几个免费IP地址查询接口(API)
淘宝IP地址库 提供的服务包括:1. 根据用户提供的IP地址,快速查询出该IP地址所在的地理信息和地理相关的信息,包括国家.省.市和运营商.2. 用户可以根据自己所在的位置和使用的IP地址更新我们的服 ...
- SpringBoot Web开发(3) WebMvcConfigurerAdapter过期替代方案
springboot2.0中 WebMvcConfigurerAdapter过期替代方案 最近在学习尚硅谷的<springboot核心技术篇>,项目中用到SpringMVC的自动配置和扩展 ...
- 关于想通过ros的dstnat实现公网IP nat 公网IP的情况
转发群里大神的“苏州-海豚在跳舞”的图 理论上客户机收到2.2.2.2过来的包应该直接丢弃.因为它并没有给2.2.2.2发过包.这里不知道如果在2.2.2.2上做 src-nat伪装成1.1.1.1回 ...
- ef join查询
temp = temp.OrderByDescending(s => s.CreateTime).Skip((param.PageIndex - ) * param.PageSize).Take ...
- 第23课 可变参数模板(4)_Optional和Lazy类的实现
1. optional类的实现 (1)optional的功能 ①optional<T>的内部存储空间可能存储了T类型的值,也可能没有.只有当optional被T初始化之后,这个option ...
- Windows和Linux查看端口占用
Windows方法 TCP netstat -aon|findstr "TCP"|findstr "LISTENING"|findstr ":135[ ...
- linux tail 命令详解
linux ---tail命令 linux中tail命令---用于查看文件内容 最基本的是cat.more和less. 1. 如果你只想看文件的前5行,可以使用head命令,如: head -5 /e ...