環境資訊中心記者 孫文臨報導
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年11月27日ETtoday新聞雲報導
藍鯨是目前世界上體型最大的動物,也是地球史上已知動物中體型最大的,由於體型過大,使得科學家一直很難完整得知牠的生理特徵,近來卻有科學家首次測到藍鯨的心跳,也因此解開了更多關於藍鯨的祕密。
這項研究近來發表在《美國國家科學院院刊》(Proceedings of the National Academy of Sciences of the United States of America)上,一組海洋生物學家透過在藍鯨背部安裝吸盤的方式,成功在加州外海測到了心跳。研究人員長達九個小時持續觀察一條藍鯨發現,牠浮出水面時心跳最高可達每分鐘34下,沉入水面時心跳最低卻可降到每分鐘兩下。
科學家解釋,這是因為藍鯨潛入水中時,身體會重新分配氧氣,其心臟和大腦會需要比較多氧氣,肌肉、皮膚和其他器官所吸收的氧氣量較少,因此每呼吸一次便能更長時間停留在水中。
一條成年藍鯨身長可超過30公尺,讓科學家們一直很好奇,要有多大的力量才能為世界上體型最大的動物提供動力,2015年他們從藍鯨的解剖標本發現,其心臟竟然重達180公斤,看起來和一台高爾夫球車差不多大。
加利福尼亞大學助理教授戈德博根(Jeremy Goldbogen)表示,測得藍鯨的心跳能幫助他們了解,藍鯨為什麼沒有長得比現在更大,因為藍鯨身體所需要的動能要求,已經到達心臟所能承受的最大值,光是張開大口這個動作,就會讓其心臟運作達到極限。
若是地球上有體型比藍鯨更大的動物存在,其心臟的跳動速度會需要更快,但科學家認為從目前數據看起來這是不可能的事,也因此解釋了為何目前地球上沒有發現比藍鯨體積更大的動物存在之謎。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
在前面的學習中我們其實已經可以通過socket模塊來建立我們的服務端,並且還介紹了關於TCP協議的粘包問題。但是還有一個非常大的問題就是我們所編寫的Server端是不支持併發性服務的,在我們之前的代碼中只能加入一個通信循環來進行排隊式的單窗口一對一服務。那麼這一篇文章將主要介紹如何使用socketserver模塊來建立具有併發性的Server端。
我們先看它的一段代碼,對照代碼來看功能。
#!/usr/bin/env python3 # _*_ coding:utf-8 _*_ # ==== 使用socketserver創建支持多併發性的服務器 TCP協議 ==== import socketserver class MyServer(socketserver.BaseRequestHandler): """自定義類""" def handle(self): """handle處理請求""" print("雙向鏈接通道建立完成:", self.request) # 對於TCP協議來說,self.request相當於雙向鏈接通道conn,即accept()的第一部分 print("客戶端的信息是:", self.client_address) # 對於TCP協議來說,相當於accept()的第二部分,即客戶端的ip+port while 1: # 開始內層通信循環 try: # # bug修復:針對windows環境 data = self.request.recv(1024) if not data: break # bug修復:針對類UNIX環境 print("收到客戶機[{0}]的消息:[{1}]".format(self.client_address, data)) self.request.sendall(data.upper()) # #sendall是重複調用send. except Exception as e: break self.request.close() # 當出現異常情況下一定要關閉鏈接 if __name__ == '__main__': s1 = socketserver.ThreadingTCPServer(("0.0.0.0", 6666), MyServer) # 公網服務器綁定 0.0.0.0 私網測試為 127.0.0.1 s1.serve_forever() # 啟動服務
1.導入
socketserver模塊2.創建一個新的類,並繼承
socketserver.BaseRequestHandler類3.覆寫
handle方法,對於TCP協議來說,self.request相當於雙向鏈接通道conn,self.client_address相當於被服務方的ip和port信息,也就是addr,而整個handle方法相當於鏈接循環。4.寫入收發邏輯規則
5.防止客戶端發送空的消息已致雙方卡死
6.防止客戶端突然斷開已致服務端崩潰
7.粘包優化(可選)
8.實例化
socketserver.ThreadingTCPServer類,並傳入IP+port,以及剛寫好的類名9.使用
socketserver.ThreadingTCPServer實例化對象中的server_forever( )方法啟動服務
它其實是這樣的:
我們不用管鏈接循環,因為在執行
handle方法之前內部已經幫我們做好了。當我們使用serve_forever()方法的時候便開始監聽鏈接描述符對象,一旦有鏈接請求就創建一個子線程來處理該鏈接。
基於UDP協議的socketserver服務端與基於TCP協議的socketserver服務端大相徑庭,但是還是有幾點不太一樣的地方。
對TCP來說:
self.request = 雙向鏈接通道(conn)對UDP來說:
self.request = (client_data_byte,udp的套接字對象)
#!/usr/bin/env python3 # _*_ coding:utf-8 _*_ # ==== 使用socketserver創建支持多併發性的服務器 UDP協議 ==== import socketserver class MyServer(socketserver.BaseRequestHandler): """自定義類""" def handle(self): """handle處理請求""" # 由於UDP是基於消息的協議,故根本不用通信循環 data = self.request[0] # 對於UDP協議來說,self.request其實是個元組。第一個元素是消息內容主題(Bytes類型),相當於recvfrom()的第一部分 server = self.request[1] # 第二個元素是服務端本身,即自己 print("客戶端的信息是:", self.client_address) # 對於UDP協議來說,相當於recvfrom()的第二部分,即客戶端的ip+port print("收到客戶機[{0}]的消息:[{1}]".format(self.client_address, data)) server.sendto(data.upper(),self.client_address) if __name__ == '__main__': s1 = socketserver.ThreadingUDPServer(("0.0.0.0", 6666), MyServer) # 公網服務器綁定 0.0.0.0 私網測試為 127.0.0.1 s1.serve_forever() # 啟動服務
好了,接下來我們開始剖析socketserver模塊中的源碼部分。在Pycharm下使用CTRL+鼠標左鍵,可以進入源碼進行查看。
我們在查看源碼前一定要首先要明白兩點:
socketserver類分為兩部分,其一是server類主要是負責處理鏈接方面,另一類是request類主要負責處理通信方面。
好了,請在腦子里記住這個概念。我們來看一些socketserver模塊的實現用了哪些其他的基礎模塊。
注意,接下來的源碼註釋部分我並沒有在源代碼中修改,也請讀者不要修改源代碼的任何內容。
import socket # 這模塊挺熟悉吧 import selectors # 這個是一個多線程模塊,主要支持I/O多路復用。 import os # 老朋友了 import sys # 老朋友 import threading # 多線程模塊 from io import BufferedIOBase # 讀寫相關的模塊 from time import monotonic as time # 老朋友time模塊
socketserver中用到的基礎模塊
好了,讓我們接着往下走。可以看到一個變量__all__,是不是覺得很熟悉?就是我們使用 from xxx import xxx 能導入進的東西全是被__all__控制的,我們看一下它包含了哪些內容。
__all__ = ["BaseServer", "TCPServer", "UDPServer", "ThreadingUDPServer", "ThreadingTCPServer", "BaseRequestHandler", "StreamRequestHandler", "DatagramRequestHandler", "ThreadingMixIn"] # 這個是我們原本的 __all__ 中的值。 if hasattr(os, "fork"): __all__.extend(["ForkingUDPServer","ForkingTCPServer", "ForkingMixIn"]) if hasattr(socket, "AF_UNIX"): __all__.extend(["UnixStreamServer","UnixDatagramServer", "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) # 上面兩個if判斷是給__all__添加內容的,os.fork()這個方法是創建一個新的進程,並且只在類UNIX平台下才有效,Windows平台下是無效的,所以這裏對於Windows平台來說就from socketserver import xxx 肯定少了三個類,這三個類的作用我們接下來會聊到。而關於socket中的AF_UNIX來說我們其實已經學習過了,是基於文件的socket家族。這在Windows上也是不支持的,只有在類UNIX平台下才有效。所以Windows平台下的導入又少了4個類。 # poll/select have the advantage of not requiring any extra file descriptor, # contrarily to epoll/kqueue (also, they require a single syscall). if hasattr(selectors, 'PollSelector'): _ServerSelector = selectors.PollSelector else: _ServerSelector = selectors.SelectSelector # 這兩個if還是做I/O多路復用使用的,Windows平台下的結果是False,而類Unix平台下的該if結果為True,這關乎I/O多路復用的性能選擇。到底是select還是poll或者epoll。
socketserver模塊對於from xxx import * 導入的處理
我們接着向下看源碼,會看到許許多多的類。先關掉它來假設自己是解釋器一行一行往下走會去執行那個部分。首先是一條if判斷
if hasattr(os, "fork"): class ForkingMixIn: pass # 這裏我自己省略了 # 我們可以看見這條代碼是接下來執行的,它意思還是如果在類Unix環境下,則會去創建該類。如果在Windows平台下則不會創建該類
處理點一
繼續走,其實這種if判斷再創建類的地方還有兩處。我這裏全部列出來:
if hasattr(os, "fork"): class ForkingUDPServer(ForkingMixIn, UDPServer): pass class ForkingTCPServer(ForkingMixIn, TCPServer): pass if hasattr(socket, 'AF_UNIX'): class UnixStreamServer(TCPServer): address_family = socket.AF_UNIX class UnixDatagramServer(UDPServer): address_family = socket.AF_UNIX class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass
處理點二 and 三
好了,說完了大體粗略的一個流程,我們該來研究這裏面的類都有什麼作用,這裏可以查看每個類的文檔信息。大致如下:
前面已經說過,socketserver模塊中主要分為兩大類,我們就依照這個來進行劃分。
| socketserver模塊源碼內部class功能一覽 | |
|---|---|
| 處理鏈接相關 | |
| BaseServer | 基礎鏈接類 |
| TCPServer | TCP協議類 |
| UDPServer | UDP協議類 |
| UnixStreamServer | 文件形式字節流類 |
| UnixDatagramServer | 文件形式數據報類 |
| 處理通信相關 | |
| BaseRequestHandler | 基礎請求處理類 |
| StreamRequestHandler | 字節流請求處理類 |
| DatagramRequestHandler | 數據報請求處理類 |
| 多線程相關 | |
| ThreadingMixIn | 線程方式 |
| ThreadingUDPServer | 多線程UDP協議服務類 |
| ThreadingTCPServer | 多線程TCP協議服務類 |
| 多進程相關 | |
| ForkingMixIn | 進程方式 |
| ForkingUDPServer | 多進程UDP協議服務類 |
| ForkingTCPServer | 多進程TCP協議服務類 |
他們的繼承關係如下:
ForkingUDPServer(ForkingMixIn, UDPServer)
ForkingTCPServer(ForkingMixIn, TCPServer)
ThreadingUDPServer(ThreadingMixIn, UDPServer)
ThreadingTCPServer(ThreadingMixIn, TCPServer)
StreamRequestHandler(BaseRequestHandler)
DatagramRequestHandler(BaseRequestHandler)
處理鏈接相關
處理通信相關
多線程相關
總繼承關係(處理通信相關的不在其中,並且不包含多進程)
最後補上一個多進程的繼承關係,就不放在總繼承關係中了,容易圖形造成混亂。
多進程相關
有了繼承關係我們可以來模擬實例化的過程,我們以TCP協議為準:
socketserver.ThreadingTCPServer(("0.0.0.0", 6666), MyServer)
我們點進(選中上面代碼的ThradingTCPServer部分,CTRL+鼠標左鍵)源碼部分,查找其 __init__ 方法:
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
看來沒有,那麼就找第一父類有沒有,我們點進去可以看到第一父類ThreadingMixIn也沒有__init__方法,看上面的繼承關係圖可以看出是普通多繼承,那麼就是廣度優先的查找順序。我們來看第二父類TCPServer中有沒有,看來第二父類中是有__init__方法的,我們詳細來看。
class TCPServer(BaseServer): """註釋全被我刪了,影響視線""" address_family = socket.AF_INET # 基於網絡的套接字家族 socket_type = socket.SOCK_STREAM # TCP(字節流)協議 request_queue_size = 5 # 消息隊列最大為5,可以理解為backlog,即半鏈接池的大小 allow_reuse_address = False # 端口重用默認關閉 def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) # 可以看見,上面先是調用了父類的__init__方法,然後又實例化出了一個socket對象!所以我們先不着急往下看,先看其父類中的__init__方法。 if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise
TCPServer中的__init__()
來看一下,BaseServer類中的__init__方法。
class BaseServer: """註釋依舊全被我刪了""" timeout = None # 這個變量可以理解為超時時間,先不着急說他。先看 __init__ 方法 def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address # 即我們傳入的 ip+port ("0.0.0.0", 6666) self.RequestHandlerClass = RequestHandlerClass # 即我們傳入的自定義類 MyServer self.__is_shut_down = threading.Event() # 這裏可以看到執行了該方法,這裏先不詳解,因為它是一個事件鎖,所以不用管 self.__shutdown_request = False
BaseServer中的__init__()
在BaseServer中執行了thrading模塊下的Event()方法。我這裏還是提一嘴這個方法是幹嘛用的,它會去控制線程的啟動順序,這裏實例化出的self.__is_shut_down其實就是一把鎖,沒什麼深究的,接下來的文章中我也會寫到。我們繼續往下看,現在是該回到TCPServer的__init__方法中來了。
class TCPServer(BaseServer): """註釋全被我刪了,影響視線""" address_family = socket.AF_INET # 基於網絡的套接字家族 socket_type = socket.SOCK_STREAM # TCP(字節流)協議 request_queue_size = 5 # 消息隊列最大為5,可以理解為backlog,即半鏈接池的大小 allow_reuse_address = False # 端口重用默認關閉 def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): # 看這裏!!!! """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: # 在創建完socket對象后就會進行該判斷。默認參數bind_and_activate就是為True try: self.server_bind() # 現在進入該方法查看細節 self.server_activate() except: self.server_close() raise
TCPServer中的__init__()
好了,需要找這個self.bind()方法,還是從頭開始找。實例本身沒有,第一父類ThreadingMixIn也沒有,所以現在我們看的是TCPServer的server_bind()方法:
def server_bind(self): """Called by constructor to bind the socket. May be overridden. """ if self.allow_reuse_address: # 這裏的變量對應 TCPServer.__init__ 上面定義的類方法,端口重用這個。由於是False,所以我們直接往下執行。 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) # 綁定 ip+port 即 ("0.0.0.0", 6666) self.server_address = self.socket.getsockname() # 獲取socket的名字 其實還是 ("0.0.0.0", 6666)
TCPServer中的server_bind()
現在我們該看TCPServer下的server_activate()方法了。
def server_activate(self): """Called by constructor to activate the server. May be overridden. """ self.socket.listen(self.request_queue_size) # 其實就是監聽半鏈接池,backlog為5
TCPServer中的server_activate()
這個時候沒有任何異常會拋出的,所以我們已經跑完了整個實例化的流程。並將其賦值給s1
現在我們看一下s1的__dict__字典,再接着進行源碼分析。
{'server_address': ('0.0.0.0', 6666), 'RequestHandlerClass': <class '__main__.MyServer'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000002A96A0208E0>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=716, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 6666)>}
s1的__dict__
我們接着來看下一條代碼。
s1.serve_forever()
還是老規矩,由於s1是ThreadingTCPServer類的實例對象,所以我們去一層層的找serve_forever(),最後在BaseServer類中找到了。
def serve_forever(self, poll_interval=0.5): """註釋被我刪了""" self.__is_shut_down.clear() # 上面說過了那個Event鎖,控制子線程的啟動順序。這裏的clear()代表清除,這個不是重點,往下看。 try: # XXX: Consider using another file descriptor or connecting to the # socket to wake this up instead of polling. Polling reduces our # responsiveness to a shutdown request and wastes cpu at all other # times. with _ServerSelector() as selector: selector.register(self, selectors.EVENT_READ)# 這裡是設置了一個監聽類型為讀取事件。也就是說當有請求來的時候當前socket對象就會發生反應。 while not self.__shutdown_request: # 為False,會執行,注意!下面都是死循環了!!! ready = selector.select(poll_interval) # 設置最大監聽時間為0.5s # bpo-35017: shutdown() called during select(), exit immediately. if self.__shutdown_request: # BaseServer類中的類方法,為False,所以不執行這個。 break if ready: # 代表有鏈接請求會執行下面的方法 self._handle_request_noblock() # 這兒是比較重要的一個點。我們先來看。 self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set() # 這裡是一個釋放鎖的行為
BaseServer中的serve_forever()
如果有鏈接請求,則會執行self._handle_request_noblock()方法,它在哪裡呢?剛好這個方法就在BaseServer中serve_forever()方法的正下方第4個方法的位置。
def _handle_request_noblock(self): """註釋被我刪了""" try: request, client_address = self.get_request() # 這裏的這個方法在TCPServer中,它的return值是 self.socket.accept(),就是就是返回了元組然後被解壓賦值了。其實到這一步三次握手監聽已經開啟了。 except OSError: return if self.verify_request(request, client_address): # 這個是驗證ip和port,返回的始終是True try: self.process_request(request, client_address) # request 雙向鏈接通道,client_address客戶端ip+port。現在我們來找這個方法。 except Exception: self.handle_error(request, client_address) self.shutdown_request(request) except: self.shutdown_request(request) raise else: self.shutdown_request(request)
BaseServer中的_handle_request_noblock()
現在開始查找self.process_request(request, client_address)該方法,還是先從實例對象本身找,找不到去第一父類找。他位於第一父類ThreadingMixIn中。
def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) # 創建子線程!!看這裏! t.daemon = self.daemon_threads # ThreadingMixIn的類屬性,為False if not t.daemon and self.block_on_close: # 第一個值為False,第二個值為True。他們都是ThreadingMixIn的類屬性 if self._threads is None: # 會執行 self._threads = [] # 創建了空列表 self._threads.append(t) # 將當前的子線程添加至空列表中 t.start() # 開始當前子線程的運行,即運行self.process_request_thread方法
ThreadingMixIn中的process_request()
我們可以看到,這裏的target參數中指定了一個方法self.process_request_thread,其實意思就是說當這個線程t在start的時候會去執行該方法。我們看一下它都做了什麼,這個方法還是在ThreadingMixIn類中。
def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) # 可以看到又執行該方法了,這裏我再標註一下,別弄頭暈了。request 雙向鏈接通道,client_address客戶端ip+port。 except Exception: self.handle_error(request, client_address) finally: self.shutdown_request(request) # 它不會關閉這個線程,而是將其設置為wait()狀態。
ThreadingMixIn中的 process_request_thread()
看self.finish_request()方法,它在BaseServer類中
def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) # 這裡是幹嘛?其實就是在進行實例化!
BaseServer中的finish_request
self.RequestHandlerClass(request, client_address, self),我們找到self的__dict__字典,看看這個到底是什麼東西
{'server_address': ('0.0.0.0', 6666), 'RequestHandlerClass': <class '__main__.MyServer'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000002A96A0208E0>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=716, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 6666)>}
s1的__dict__
可以看到,它就是我們傳入的那個類,即自定義的MyServer類。我們把request,client_address,以及整個是實例self傳給了MyServer的__init__方法。但是我們的MyServer類沒有__init__,怎麼辦呢?去它父類BaseRequestHandler裏面找唄。
class BaseRequestHandler: """註釋被我刪了""" def __init__(self, request, client_address, server): self.request = request # request 雙向鏈接通道 self.client_address = client_address # 客戶端ip+port self.server = server # 即 實例對象本身。上面的__dict__就是它的__dict__ self.setup() # 鈎子函數,我們可以自己寫一個類然後繼承`BaseRequestHandler`並覆寫其setup方法即可。 try: self.handle() # 看,自動執行handle finally: self.finish() # 鈎子函數 def setup(self): pass def handle(self): pass def finish(self): pass
BaseRequestHandler中的__init__
現在我們知道了,為什麼一定要覆寫handle方法了吧。
實例化過程圖解
server_forever()啟動服務圖解
在很多時候,我們的TCP服務端為了防止網絡泛洪可以設置一個三次握手驗證機制。那麼這個驗證機制的實現其實也是非常簡單的,我們的思路在於進入通信循環之前,客戶端和服務端先走一次鏈接認證,只有通過認證的客戶端才能夠繼續和服務端進行鏈接。
下面就來看一下具體的實現步驟。
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' from socket import * import hmac,os secret_key=b'linhaifeng bang bang bang' def conn_auth(conn): ''' 認證客戶端鏈接 :param conn: :return: ''' print('開始驗證新鏈接的合法性') msg=os.urandom(32) # 新方法,生成32位隨機Bytes類型的值 conn.sendall(msg) h=hmac.new(secret_key,msg) digest=h.digest() respone=conn.recv(len(digest)) return hmac.compare_digest(respone,digest) # 對比結果為True或者為False def data_handler(conn,bufsize=1024): if not conn_auth(conn): print('該鏈接不合法,關閉') conn.close() return print('鏈接合法,開始通信') while True: data=conn.recv(bufsize) if not data:break conn.sendall(data.upper()) def server_handler(ip_port,bufsize,backlog=5): ''' 只處理鏈接 :param ip_port: :return: ''' tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(backlog) while True: conn,addr=tcp_socket_server.accept() print('新連接[%s:%s]' %(addr[0],addr[1])) data_handler(conn,bufsize) if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 server_handler(ip_port,bufsize)
Server端
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' from socket import * import hmac,os secret_key=b'linhaifeng bang bang bang' def conn_auth(conn): ''' 驗證客戶端到服務器的鏈接 :param conn: :return: ''' msg=conn.recv(32) # 拿到隨機位數 h=hmac.new(secret_key,msg) # 摻鹽 digest=h.digest() conn.sendall(digest) def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) conn_auth(tcp_socket_client) while True: data=input('>>: ').strip() if not data:continue if data == 'quit':break tcp_socket_client.sendall(data.encode('utf-8')) respone=tcp_socket_client.recv(bufsize) print(respone.decode('utf-8')) tcp_socket_client.close() if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize)
Client端
到這裏已經很簡單了,服務器將隨機數給客戶機發過去,客戶機收到后也用自家的鹽與隨機數加料,再使用digest()將它轉化為字節,直接發送了回來然後客戶端通過hmac.compare_digest()方法驗證兩個的值是否相等,如果不等就說明鹽不對。客戶機不合法服務端將會關閉與該客戶機的雙向鏈接通道。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年12月2日公視報導
日本福島核一廠發生核能災變事故,即將屆滿九年。雖然說日本政府已經決定廢爐,但因為技術問題,時間表一改再改。專家會議在今(2日)做出第五次修正,預估從2021年展開,要花10年完成核燃料棒等殘骸的取出工作。至於正式廢爐,預估是2041到2051年間才能完成。
2011年3月11號受到東北大地震與海嘯影響、而發生的福島核電廠災變事故,六個反應爐當中,1號機到3號機發生爐心熔毀的狀況,但事故後連同當時也在運轉中的4號機,都在事故後立即停止運轉,並於隔年2012年的4月19號永久停機。至於事故當時正在檢修的5號與6號機,則是到2014年1月31號永久停機。
將近九年來,除了三不五時傳出放射性汙水外流,以及東京電力公司面對的各種賠償訴訟,日本政府也多次召開專家會議,研商福島核一廠廢爐的時間與程序。日本媒體的報導指出,廢爐過程中最大的困難,就是如何取出熔毀的核燃料殘骸。
福島核一廠廢爐委員會委員長宮野廣說:「廢爐過程中最需要注意的,就是放射性廢棄物飛散的問題。如何找出不發生輻射廢料飛散的工法,是首要解決的技術課題。」
另外,取出核燃料殘骸的前置作業,將由機器人先進入反應爐採集樣本。2號的專家會議決定,為了慎重起見,在派遣機器人之前,要先製作模擬現場的模型,讓操作人員更熟悉機器人在反應爐內的運作,因此,正式的作業將從2021年開始。這也是廢爐相關時間表第五次的修正。
整個核燃料棒移除的工作,預計在2031年告一段落。後續的廢爐相關作業,將視情況於2041年到2051年完成。也就是說,從福島核能災變到核一廠完成廢爐,花費的時間長達30到40年。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年12月3日中央通訊社報導
聯合國世界氣象組織(WMO)今天(3日)發表年度評估報告指出,氣候變遷已超出人類適應能力,今年(2019年)以來全球氣溫較工業革命前均溫高出攝氏1.1度,即將成為史上最熱10年。
世界氣象組織說,2019年可能成為歷來最熱的前3年。燃燒化石燃料、建造基礎設施、種植作物及運送產品等造成的人為排放,將讓今年打破大氣中二氧化碳濃度紀錄,地球勢必會進一步升溫。
海洋負責吸收溫室氣體新增的9成熱量,讓全球海水溫度也達歷史新高,如今海水也比150年前酸25%,威脅數以十億計民眾賴以維生的重要海洋生態系統。在格陵蘭過去12個月融冰3290億公噸助長下,全球平均海平面高度於去年10月達到歷來新高。
隨著過去40年的每10年都比前10年還熱,氣候變遷不只是未來世代要面臨的問題,因為人類無節制的慾望與不計代價的消費,已讓數以百萬計民眾身受其害。報告中說,今年上半年有超過1000萬人在自己國家內流離失所,其中700萬人直接受到諸如風暴、洪水及乾旱等極端氣候影響。世界氣象組織說,因極端氣候新增的無家可歸人口,到今年底可能增至2200萬人。
世界氣象組織秘書長塔拉斯(Petteri Taalas)表示:「2019年再度發生天氣及氣候相關風險重創事件。百年一遇的熱浪及水災變得愈來愈常發生。」
在只比工業革命前均溫高出攝氏1.1度的今年,歐洲、澳洲與日本已相繼出現致命熱浪,還有超級風暴重創非洲東南部,加州和澳洲也有失控野火肆虐。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
目錄
MyBatis是一個持久層框架,使用簡單,學習成本較低。可以執行自己手寫的SQL語句,比較靈活。但是MyBatis的自動化程度不高,移植性也不高,有時從一個數據庫遷移到另外一個數據庫的時候需要自己修改配置。
一個Mybatis最簡單的使用列子如下:
public class UserDaoTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception{
ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void selectUserTest(){
String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
SqlSession sqlSession = sqlSessionFactory.openSession();
CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);
Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);
System.out.println(cbondissuer);
sqlSession.close();
}
}
本博客只涉及創建SessionFactory,以及從SessionFactory獲取SqlSession的流程。具體執行Sql的流程會在其他博客中分析。
ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
通過上面代碼發現,創建SqlSessionFactory的代碼在SqlSessionFactoryBuilder中,進去一探究竟:
//整個過程就是將配置文件解析成Configration對象,然後創建SqlSessionFactory的過程
//Configuration是SqlSessionFactory的一個內部屬性
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
下面我們看下解析配置文件過程中的一些細節。
先給出一個配置文件的列子:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--SqlSessionFactoryBuilder中配置的配置文件的優先級最高;config.properties配置文件的優先級次之;properties標籤中的配置優先級最低 -->
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
<!--一些重要的全局配置-->
<settings>
<setting name="cacheEnabled" value="true"/>
<!--<setting name="lazyLoadingEnabled" value="true"/>-->
<!--<setting name="multipleResultSetsEnabled" value="true"/>-->
<!--<setting name="useColumnLabel" value="true"/>-->
<!--<setting name="useGeneratedKeys" value="false"/>-->
<!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
<!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
<!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
<!--<setting name="defaultStatementTimeout" value="25"/>-->
<!--<setting name="defaultFetchSize" value="100"/>-->
<!--<setting name="safeRowBoundsEnabled" value="false"/>-->
<!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
<!--<setting name="localCacheScope" value="STATEMENT"/>-->
<!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
<!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
<!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
</settings>
<typeAliases>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--默認值為 false,當該參數設置為 true 時,如果 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果-->
<!--如果某些查詢數據量非常大,不應該允許查出所有數據-->
<property name="pageSizeZero" value="true"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
<property name="username" value="windty_opr"/>
<property name="password" value="windty!234"/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<mappers>
<!--這邊可以使用package和resource兩種方式加載mapper-->
<!--<package name="包名"/>-->
<!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
<mapper resource="./mappers/CbondissuerMapper.xml"/>
</mappers>
</configuration>
下面是解析配置文件的核心方法:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties標籤,並set到Configration對象中
//在properties配置屬性后,在Mybatis的配置文件中就可以使用${key}的形式使用了。
propertiesElement(root.evalNode("properties"));
//解析setting標籤的配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
//添加vfs的自定義實現,這個功能不怎麼用
loadCustomVfs(settings);
//配置類的別名,配置后就可以用別名來替代全限定名
//mybatis默認設置了很多別名,參考附錄部分
typeAliasesElement(root.evalNode("typeAliases"));
//解析攔截器和攔截器的屬性,set到Configration的interceptorChain中
//MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:
//Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
//ParameterHandler (getParameterObject, setParameters)
//ResultSetHandler (handleResultSets, handleOutputParameters)
//StatementHandler (prepare, parameterize, batch, update, query)
pluginElement(root.evalNode("plugins"));
//Mybatis創建對象是會使用objectFactory來創建對象,一般情況下不會自己配置這個objectFactory,使用系統默認的objectFactory就好了
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//設置在setting標籤中配置的配置
settingsElement(settings);
//解析環境信息,包括事物管理器和數據源,SqlSessionFactoryBuilder在解析時需要指定環境id,如果不指定的話,會選擇默認的環境;
//最後將這些信息set到Configration的Environment屬性裏面
environmentsElement(root.evalNode("environments"));
//
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。解析typeHandler。
typeHandlerElement(root.evalNode("typeHandlers"));
//解析Mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面解析流程結束後會生成一個Configration對象,包含所有配置信息,然後會創建一個SqlSessionFactory對象,這個對象包含了Configration對象。
下面是openSession的過程:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//獲取執行器,這邊獲得的執行器已經代理攔截器的功能(見下面代碼)
final Executor executor = configuration.newExecutor(tx, execType);
//根據獲取的執行器創建SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//interceptorChain生成代理類,具體參見Plugin這個類的方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
到此為止,我們已經獲得了SqlSession,拿到SqlSession就可以執行各種CRUD方法了。
對於MyBatis啟動的流程(獲取SqlSession的過程)這邊簡單總結下:
SQL的具體執行流程見後續博客。
一些重要類總結:
//TypeAliasRegistry
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
https://blog.csdn.net/luanlouis/article/details/40422941
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年12月7日公共電視報導
馬拉威國家公園520隻大象,最近剛遷到新的野生動物保護區,希望能促進繁衍,但沒想到經常與大象共生的采采蠅,數量也大為增加,由於采采蠅會傳染寄生蟲,釀成致命的非洲昏睡病,附近民眾非常擔憂。
采采蠅不論公母都會吸血,被牠叮咬的話,就可能感染非洲錐蟲症,症狀跟瘧疾非常類似,很容易被誤診,釀成致命危險。「非洲錐蟲症」又稱為非洲昏睡病,主要出現在非洲撒哈拉以南的36個國家,初期症狀是發燒、頭痛,如果侵入腦部引起腦膜炎,可能導致神智不清,癲癇、昏睡不醒而死。非洲昏睡病在1890年代曾經造成數十萬人死亡,重創區域畜牧業跟農業,世界衛生組織直到2008年才控制住這種疾病。
動保NGO團體African Parks經營經理羅伯特說,「這有點諷刺,因為這是成功(大象遷移)的負面效應,動物數量增加所造成的後果之一,就是采采蠅的數量也增加了。」
為了防止非洲昏睡病擴散,動保團體跟馬拉威政府合作,在大象保護區內設置了600個陷阱與標靶,用采采蠅喜歡的藍色布料誘捕消滅牠們。此外也將增加醫療專業人手,並加強訓練,提高早期確診率,減少死亡病例。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年12月7日中央社報導
位於非洲南部尚比西河(Zambezi River)中游, 坐落於尚比亞與辛巴威的交界處,高108公尺、寬1708公尺,被認為是世界上最大的瀑布,是該區域最大旅遊景點,數十年來吸引了數百萬旅客造訪辛巴威及尚比亞一睹奇景。
然而,路透社與英國「每日郵報」報導,維多利亞瀑布(Victoria Falls),非洲南部在嚴重旱災衝擊下,水位降至25年來最低,這場世紀最嚴重旱災,造成維多利亞瀑布減緩為細流,加劇氣候變遷可能破壞瀑布的憂慮。通常於乾季瀑布水流會減少,但官員表示,今年水位下降程度前所未見。
Shocking before and after photos show Victoria Falls almost completely dried up
— Daily Mail Online (@MailOnline)
The Victoria Falls, which draws millions of holidaymakers to Zimbabwe and Zambia, has shrunk to a trickle, fuelling fears that climate change could kill one of the region’s biggest tourist attractions
— Reuters (@Reuters)
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
摘錄自2019年12月9日自由時報烏干達報導
烏干達數周的降雨造成嚴重的水災,根據紅十字會8日表示,已發現17名屍體,死亡人口持續增加,目前仍有許多人失蹤。這也是上周暴雨,烏干達地區最新公告的死者數據。
綜合外媒報導,自10月起,東非各國遭到暴雨的侵襲,包括烏干達、肯亞等國。根據聯合國資料,過去兩個月中,洪水和山區滑坡摧毀了東非大部分地區,造成近300人死亡,數百萬人流離失所。
東非國家正在經歷第二個雨季,該雨季已在大部分地區達到頂峰。烏干達國家氣象局預測,整個12月將繼續出現強降雨。暴雨在烏干達低窪地區造成了破壞性的洪災,並在山區造成了滑坡。破壞性的災害襲擊了烏干達各地的種植園和農作物。
根據飢荒預警系統網絡(Famine Early Warning Systems Network)的數據,在過去的兩個月中,大雨使東非部分地區遭受嚴重破壞,非洲之角的降雨量比平均水平高出300%(10月至11月中旬)。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?