Python開發【第十篇】:RabbitMQ隊列

来源:http://www.cnblogs.com/yinshoucheng-golden/archive/2017/08/17/7384686.html
-Advertisement-
Play Games

簡介 RabbitMQ是流行的開源消息隊列系統,用erlang語言開發。RabbitMQ是AMQP(高級消息隊列協議)的標準實現。 安裝 首先安裝erlang環境。 官網:http://www.erlang.org/ Windows版下載地址:http://erlang.org/download/o... ...


簡介

RabbitMQ是流行的開源消息隊列系統,用erlang語言開發。RabbitMQ是AMQP(高級消息隊列協議)的標準實現。

安裝

首先安裝erlang環境。

官網:http://www.erlang.org/

Windows版下載地址:http://erlang.org/download/otp_win64_20.0.exe

Linux版:yum安裝

Windows安裝步驟

第一步運行

第二步

第三步

第四步

第五步

Erlang安裝完成。

然後安裝RabbitMQ,首先下載RabbitMQ的Windows版本。

官網:http://www.rabbitmq.com/

Windows版下載地址:http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10.exe

打開安裝程式,按照下麵步驟安裝。

RabbitMQ安裝完成。

開始菜單中進入管理工具。

運行命令

  1. rabbitmq-plugins enable rabbitmq_management

查看RabbitMQ服務是否啟動。

至此全部安裝完成。

Linux安裝步驟

安裝erlang。

  1. yum -y install erlang

安裝RabbitMQ。

  1. wget https://github.com/rabbitmq/rabbitmq-server/archive/rabbitmq_v3_6_10.tar.gz
  2. rpm -ivh rabbitmq-server-3.6.10-1.el6.noarch.rpm

RabbitMQ安裝失敗,報錯如下。

  1. warning: rabbitmq-server-3.6.10-1.el6.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 6026dfca: NOKEY
  2. error: Failed dependencies:
  3.         erlang >= R16B-03 is needed by rabbitmq-server-3.6.10-1.el6.noarch
  4.         socat is needed by rabbitmq-server-3.6.10-1.el6.noarch

原因是yum安裝的erlang版本太低,這裡提供的RabbitMQ是最新版3.6.10,所需的erlang版本最低為R16B-03,否則編譯時將失敗,也就是上述錯誤。

重新安裝erlang。

  1. wget http://erlang.org/download/otp_src_20.0.tar.gz
  2. tar xvzf otp_src_20.0.tar.gz
  3. cd otp_src_20.0
  4. ./configure
  5. make && make install

重新安裝erlang完畢。

運行erlang。

  1. erl
  2. Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:10] [hipe] [kernel-poll:false]
  3.  
  4. Eshell V9.0 (abort with ^G)

安裝socat。

  1. yum install -y socat

再次安裝RabbitMQ。

  1. rpm -ivh rabbitmq-server-3.6.10-1.el6.noarch.rpm
  2. warning: rabbitmq-server-3.6.10-1.el6.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 6026dfca: NOKEY
  3. error: Failed dependencies:
  4.         erlang >= R16B-03 is needed by rabbitmq-server-3.6.10-1.el6.noarch

上述錯誤信息顯示安裝失敗,因為rabbitMQ的依賴關係所導致,所以要忽略依賴,執行以下命令。

  1. rpm -ivh --nodeps rabbitmq-server-3.6.10-1.el6.noarch.rpm

安裝成功。

啟動、停止RabbitMQ。

  1. rabbitmq-server start     #啟動
  2. rabbitmq-server stop     #停止
  3. rabbitmq-server restart    #重啟

 

RabbitMQ使用

實現最簡單的隊列通信

send端(producer)

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
  8. channel = connection.channel()
  9.  
  10. # 聲明queue
  11. channel.queue_declare(queue='hello')
  12.  
  13. channel.basic_publish(exchange='',
  14.                       routing_key='hello',
  15.                       body='hello word')
  16. print("[x] Sent 'hello word!'")
  17. connection.close()

receive端(consumer)

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,time
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.queue_declare(queue='hello')
  11.  
  12. def callback(ch,method,properties,body):
  13.     print('-->',ch,method,properties)
  14.     print("[x] Received %s" % body)
  15.  
  16. channel.basic_consume(callback,
  17.                       queue='hello',
  18.                       no_ack=True
  19.                       )
  20.  
  21. print('[*] waiting for messages.To exit press CTRL+C')
  22. channel.start_consuming()

no_ack分析

no_ack屬性是在調用Basic.Consume方法時可以設置的一個重要參數。no_ack的用途是確保message被consumer成功處理了。這裡成功的意識是,在設置了no_ack=false的情況下,只要consumer手動應答了Basic.Ack,就算其成功處理了。

no_ack=true(此時為自動應答)

在這種情況下,consumer會在接收到Basic.Deliver+Content-Header+Content-Body之後,立即回覆Ack,而這個Ack是TCP協議中的Ack。此Ack的回覆不關心consumer是否對接收到的數據進行了處理,當然也不關心處理數據所需要的耗時。

no_ack=False(此時為手動應答)

在這種情況下,要求consumer在處理完接收到的Basic.Deliver+Content-Header+Content-Body之後才回覆Ack,而這個Ack是AMQP協議中的Basic.Ack。此Ack的回覆與業務處理相關,所以具體的回覆時間應該要取決於業務處理的耗時。

總結

Basic.Ack發給RabbitMQ以告知,可以將相應message從RabbitMQ的消息從緩存中移除。

Basic.Ack未被consumer發給RabbitMQ前出現了異常,RabbitMQ發現與該consumer對應的連接被斷開,將該該message以輪詢方式發送給其他consumer(需要存在多個consumer訂閱同一個queue)。

在no_ack=true的情況下,RabbitMQ認為message一旦被deliver出去後就已被確認了,所以會立即將緩存中的message刪除,因此在consumer異常時會導致消息丟失。

來自consumer的Basic.Ack與發送給Producer的Basic.Ack沒有直接關係。

消息持久化

acknowledgment消息持久化

no-ack=False,如果consumer掛掉了,那麼RabbitMQ會重新將該任務添加到隊列中。

回調函數中

  1. ch.basic_ack(delivery_tag=method.delivery_tag)

basic_consume中

  1. no_ack=False

receive端(consumer)

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,time
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.queue_declare(queue='hello')
  11.  
  12. # 定義回調函數
  13. def callback(ch,method,properties,body):
  14.     print('-->',ch,method,properties)
  15.     print("[x] Received %s" % body)
  16.     ch.basic_ack(delivery_tag=method.delivery_tag)
  17.  
  18. # no_ack=False表示消費完以後不主動把狀態通知RabbitMQ
  19. channel.basic_consume(callback,
  20.                       queue='hello',
  21.                       no_ack=False
  22.                       )
  23.  
  24. print('[*] waiting for messages.To exit press CTRL+C')
  25. channel.start_consuming()

durable消息持久化

producer發送消息時掛掉了,consumer接收消息時掛掉了,以下方法會讓RabbitMQ重新將該消息添加到隊列中。

回調函數中

  1. ch.basic_ack(delivery_tag=method.delivery_tag)

basic_consume中

  1. no_ack=False

basic_publish中添加參數

  1. properties=pika.BasicProperties(delivery_mode=2)

channel.queue_declare中添加參數

  1. channel.queue_declare(queue='hello',durable=True)

send端(producer)

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
  8. channel = connection.channel()
  9.  
  10. # 聲明queue
  11. channel.queue_declare(queue='hello',durable=True)
  12.  
  13. channel.basic_publish(exchange='',
  14.                       routing_key='hello',
  15.                       body='hello word',
  16.                       properties=pika.BasicProperties(delivery_mode=2))
  17. print("[x] Sent 'hello word!'")
  18. connection.close()

receive端(consumer)與acknowledgment消息持久化中receive端(consumer)相同。

消息分發

預設消息隊列里的數據是按照順序分發到各個消費者,但是大部分情況下,消息隊列後端的消費者伺服器的處理能力是不相同的,這就會出現有的伺服器閑置時間較長,資源浪費的情況。那麼,我們就需要改變預設的消息隊列獲取順序。可以在各個消費者端配置prefetch_count=1,意思就是告訴RabbitMQ在這個消費者當前消息還沒有處理完的時候就不要再發新消息了。

消費者端

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4. __author__ = 'Golden'
  5. #!/usr/bin/env python3
  6. # -*- coding:utf-8 -*-
  7.  
  8. import pika,time
  9.  
  10. connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
  11. channel = connection.channel()
  12.  
  13. channel.queue_declare(queue='hello2',durable=True)
  14.  
  15. def callback(ch,method,properties,body):
  16.     print('-->',ch,method,properties)
  17.     print("[x] Received %s" % body)
  18.     time.sleep(30)
  19.     ch.basic_ack(delivery_tag=method.delivery_tag)
  20.  
  21. channel.basic_qos(prefetch_count=1)
  22. channel.basic_consume(callback,
  23.                       queue='hello2',
  24.                       no_ack=False
  25.                       )
  26.  
  27. print('[*] waiting for messages.To exit press CTRL+C')
  28. channel.start_consuming()

生產者端不變。

消息發佈和訂閱(publish\subscribe)

發佈和訂閱與簡單的消息隊列區別在於,發佈和訂閱會將消息發送給所有的訂閱者,而消息隊列中的數據被消費一次便消失。所以,RabbitMQ實現發佈和訂閱時,會為每一個訂閱者創建一個隊列,而發佈者發佈消息時,會將消息放置在所有相關隊列中。類似廣播的效果,這時候就要用到exchange。Exchange在定義的時候是有類型的,以決定到底是哪些Queue符合條件,可以接收消息。

fanout:所有bind到此exchange的queue都可以接收消息。

direct:通過routingKey和exchange決定的哪個唯一的queue可以接收消息。

topic:所有符合routingKey(可以是一個表達式)的routingKey所bind的queue可以接收消息。

表達式符號說明

#:一個或多個字元

*:任何字元

例如:#.a會匹配a.a,aa.a,aaa.a等。

*.a會匹配a.a,b.a,c.a等。

註意:使用RoutingKey為#,Exchange Type為topic的時候相對於使用fanout。

heaers:通過headers來決定把消息發給哪些queue。

publisher

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,sys
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.exchange_declare(exchange='logs',type='fanout')
  11.  
  12. message = ''.join(sys.argv[1:]) or 'info:Hello World!'
  13. channel.basic_publish(exchange='logs',
  14.                       routing_key='',
  15.                       body=message)
  16.  
  17. print('[x] Send %r' % message)
  18. connection.close()

subscriber

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9. channel.exchange_declare(exchange='logs',type='fanout')
  10. # 不指定queue名字,rabbit會隨機分配一個名字,exclusive=True會在使用此queue的消費者斷開後,自動將queue刪除
  11. result = channel.queue_declare(exclusive=True)
  12. queue_name = result.method.queue
  13. channel.queue_bind(exchange='logs',queue=queue_name)
  14. print('[*]Waiting for logs.To exit press CTRL+C')
  15. def callback(ch,method,properties,body):
  16.     print('[*] %s'%body)
  17.  
  18. channel.basic_consume(callback,
  19.                       queue=queue_name,
  20.                       no_ack=True)
  21.  
  22. channel.start_consuming()

關鍵字發送(echange type=direct)

發送消息時明確指定某個隊列並向其中發送消息,RabbitMQ還支持根據關鍵字發送,即隊列綁定關鍵字,發送者將數據根據關鍵字發送到消息exchange,exchange根據關鍵字判定應該將數據發送至哪個隊列。

publisher

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,sys
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.exchange_declare(exchange='direct_logs',
  11.                          type='direct')
  12.  
  13. # severity = 'error'
  14. severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
  15. # message = 'Hello World!'
  16. message = ''.join(sys.argv[2:]) or 'Hello World!'
  17.  
  18. channel.basic_publish(exchange='direct_logs',
  19.                       routing_key=severity,
  20.                       body=message)
  21. print('[x] Send %r:%r' % (severity,message))
  22. connection.close()

subscriber

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,sys
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.exchange_declare(exchange='direct_logs',
  11.                          type='direct')
  12.  
  13. result = channel.queue_declare(exclusive=True)
  14. queue_name = result.method.queue
  15.  
  16. severities = sys.argv[1:]
  17. if not severities:
  18.     sys.stderr.write('Usage: %s [info] [warning] [error]\n' % sys.argv[0])
  19.     sys.exit(1)
  20.  
  21. for severity in severities:
  22.     channel.queue_bind(exchange='direct_logs',
  23.                        queue=queue_name,
  24.                        routing_key=severity)
  25.  
  26. print('[*] Waiting for logs.To exit press CTRL+C')
  27.  
  28. def callback(ch,method,properties,body):
  29.     print('[*] %r:%r' % (method.routing_key,body))
  30.  
  31. channel.basic_consume(callback,
  32.                       queue=queue_name,
  33.                       no_ack=True)
  34.  
  35. channel.start_consuming()

啟動subscriber1

  1. python3 direct_subscriber.py warning

啟動subscriber2

  1. python3 direct_subscriber.py error

啟動publisher1

  1. python3 direct_publisher.py info

啟動publisher2

  1. python3 direct_publisher.py warning

啟動publisher3

  1. python3 direct_publisher.py error

結果

模糊匹配(exchange type=topic)

在topic類型下,可以讓隊列綁定幾個模糊的關鍵字,發送者將數據發送到exchange,exchange將傳入"路由值"和"關鍵字"進行匹配,匹配成功則將數據發送到指定隊列。

*:匹配任意一個字元

#:匹配任意個字元

publisher

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,sys
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.exchange_declare(exchange='topic_logs',
  11.                          type='topic')
  12.  
  13. routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
  14. message = ''.join(sys.argv[2:]) or 'Hello World!'
  15. channel.basic_publish(exchange='topic_logs',
  16.                       routing_key=routing_key,
  17.                       body=message)
  18.  
  19. print('[x] Sent %r:%r' % (routing_key,message))
  20. connection.close()

subscriber

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,sys
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.exchange_declare(exchange='topic_logs',
  11.                          type='topic')
  12.  
  13. result = channel.queue_declare(exclusive=True)
  14. queue_name = result.method.queue
  15.  
  16. binding_keys = sys.argv[1:]
  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. def callback(ch,method,properties,body):
  29.     print('[x] %r:%r' % (method.routing_key,body))
  30.  
  31. channel.basic_consume(callback,
  32.                       queue=queue_name,
  33.                       no_ack=True)
  34.  
  35. channel.start_consuming()

測試

遠程過程調用(RPC)

RPC(Remote Procedure Call Protocol)遠程過程調用協議。在一個大型的公司,系統由大大小小的服務構成,不同的團隊維護不同的代碼,部署在不同的伺服器。但是在做開發的時候往往要用到其他團隊的方法,因為已經有了實現。但是這些服務部署在不同的伺服器,想要調用就需要網路通信,這些代碼繁瑣且複雜,一不小心就會很低效。PRC協議定義了規劃,其它的公司都給出了不同的實現。比如微軟的wcf,以及WebApi。

在RabbitMQ中RPC的實現是很簡單高效的,現在客戶端、服務端都是消息發佈者與消息接受者。

首先客戶端通過RPC向服務端發生請求。correlation_id:請求標識,erply_to:結果返回隊列。(我這裡有一些數據需要你給我處理一下,correlation_id是我請求標識,你處理完成之後把結果返回到erply_to隊列)

服務端拿到請求,開始處理並返回。correlation_id:客戶端請求標識。(correlation_id這是你的請求標識,還給你。這時候客戶端用自己的correlation_id與服務端返回的correlation_id進行對比,相同則接收。)

rpc_server

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,time
  6.  
  7. connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  8. channel = connection.channel()
  9.  
  10. channel.queue_declare(queue='rpc_queue')
  11. def fib(n):
  12.     if n == 0:
  13.         return 0
  14.     elif n == 1:
  15.         return 1
  16.     else:
  17.         return fib(n-1) + fib(n-2)
  18.  
  19. def on_request(ch,method,props,body):
  20.     n = int(body)
  21.     print('[.] fib(%s)' % n)
  22.     response = fib(n)
  23.     ch.basic_publish(exchange='',
  24.                      routing_key=props.reply_to,
  25.                      properties=pika.BasicProperties(correlation_id=props.correlation_id),
  26.                      body = str(response))
  27.     ch.basic_ack(delivery_tag=method.delivery_tag)
  28.  
  29. channel.basic_qos(prefetch_count=1)
  30. channel.basic_consume(on_request,queue='rpc_queue')
  31.  
  32. print('[x] Awaiting RPC requests')
  33. channel.start_consuming()

rpc_client

  1. __author__ = 'Golden'
  2. #!/usr/bin/env python3
  3. # -*- coding:utf-8 -*-
  4.  
  5. import pika,uuid
  6.  
  7. class FibonacciRpcClient(object):
  8.     def __init__(self):
  9.         self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
  10.         self.channel = self.connection.channel()
  11.         result = self.channel.queue_declare(exclusive=True)
  12.         self.callback_queue = result.method.queue
  13.         self.channel.basic_consume(self.on_response,no_ack=True,
  14.                                    queue=self.callback_queue)
  15.  
  16.     def on_response(self,ch,method,props,body):
  17.         if self.corr_id == props.correlation_id:
  18.             self.response = body
  19.  
  20.     def call(self,n):
  21.         self.response = None
  22.         self.corr_id = str(uuid.uuid4())
  23.         self.channel.basic_publish(exchange='',
  24.                                    routing_key='rpc_queue',
  25.                                    properties=pika.BasicProperties(
  26.                                        reply_to=self.callback_queue,
  27.                                        correlation_id=self.corr_id,),
  28.                                    body=str(n))
  29.         while self.response is None:
  30.             self.connection.process_data_events()
  31.         return int(self.response)
  32.  
  33. fibonacci_rpc = FibonacciRpcClient()
  34.  
  35. print('[x] Requesting fib(10)')
  36. response = fibonacci_rpc.call(10)
  37. print('[.] Got %r ' % response)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、摘要 1.1、為什麼叫本次的分享課叫《修煉手冊》? 阿笨希望本次的分享課中涉及覆蓋的一些小技巧、小技能給您帶來一些幫助。希望您在日後工作中把它作為一本實際技能手冊進行儲備,以備不時之需,一旦當手頭遇到與Dapper修煉手冊中相似用法的地方和場景,可以直接拿來進行翻閱並靈活的運用到項目中。最後阿笨 ...
  • 版本15.3更新在用戶離線下載時更加人性化,包含了進度顯示,下載出錯可以輸入R,進行下載的重新嘗試,併在當前下載框下繼續下載為完成的作業,結合 --layout 參數的離線文件的檢查和修複,並且在下載的完成後不會自動退出下載框,而是等待任意鍵的輸入。 本次針對15.3版本進行了中文語言的下載,在下載 ...
  • 回到目錄 DotNetCore里一切都是依賴註入的,對於appsettings這個可擴展的配置對象也不例外,它位於項目根目錄,一般在startup里去註冊它,在類中通過構造方法註入來獲取當前的對象,以便去使用它,當然我們也可以自己去構建和使用它,下麵我就來總結一下。 傳統方法,startup註入,構 ...
  • Overview Generally, one of the first steps when you are trying to work with databases is open it. You can find several types of those, and each have a ...
  • 因為在X86上long會被分割為兩個int進行操作, 那麼Interlocked.Increment的實現成為了一個問題。 在一番搜索後未發現有現成的文章解釋這個問題,於是我就動手分析了。 這篇是筆記,不會做過多的解釋。 首先重現環境是 .Net Core 2.0 Windows (x86) Bin ...
  • 最近工作感覺特別操心,不是因為任務多,也不是因為有解決不了的問題,而是感覺項目團隊太懶散了。大多數員工沒有工作積極性,工作經常拖延,公司沒有獎懲措施,做得好的、及時完成工作的與那些得過且過的、經常拖延工作計劃的人一樣待遇,沒有任何區別。導致一些沒有一定毅力的好員工也逐漸被同化。 公司部門的許可權分配不 ...
  • 網站地址:http://m.3y.uu456.com/mbp_56hl91r1rx5uqa87qrzo_1.html ...
  • 之前就瞭解到了裝飾器, 但是就會點皮毛, 而且對其調用方式感到迷茫,正好現在的項目我想優化,就想到了用裝飾器, 因此深入研究了下裝飾器.先看下代碼: 我的疑惑就是明明return 的是一個函數名,按道理來講,返回的就是一個函數地址啊!我理解有問題?隨後上網查資料,又是閉包....但是我個人對它不感冒 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...