利用Python語(yǔ)言的grpc實(shí)現(xiàn)消息傳送詳解
1. grpc開(kāi)源包的安裝
# conda $ conda create -n grpc_env python=3.9 # install grpc $ pip install grpc -i https://pypi.doubanio.com/simple $ pip install grpc-tools -i https://pypi.doubanio.com/simple # 有時(shí)proto生成py文件不對(duì)就是得換換grpc兩個(gè)包的版本
2. grpc的使用之傳送消息
整體結(jié)構(gòu),client.py server.py 和proto目錄下的example.proto
1)在example.proto定義傳送體
// 聲明 syntax = "proto3"; package proto; // service創(chuàng)建 service HelloService{ rpc Hello(Request) returns (Response) {} // 單單傳送消息 } // 請(qǐng)求參數(shù)消息體 1、2是指參數(shù)順序 message Request { string data = 1; } // 返回參數(shù)消息體 message Response { int32 ret = 1; //返回碼 string data = 2; } //python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ ./example.proto
2) 在虛擬環(huán)境里使用命令生成py文件
$ conda activate grpc_env
$ f:
$ cd F:\examples
$ python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ ./example.proto
在proto目錄下會(huì)生成兩個(gè)py文件,如下圖所示:
3) 編輯client.py 和 server.py
# server.py import time import grpc from concurrent import futures from proto import example_pb2_grpc, example_pb2 class ServiceBack(example_pb2_grpc.HelloServiceServicer): """接口的具體功能實(shí)現(xiàn)""" def Hello(self, request, context): """hello""" data = request.data print(data) ret_data = "Response:" + data return example_pb2.Response(ret=0, data=ret_data) def server(ip: str, port: int) -> None: server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # ??為10的線程池 ai_servicer = ServiceBack() example_pb2_grpc.add_HelloServiceServicer_to_server(ai_servicer, server) server.add_insecure_port(f"{ip}:{port}") server.start() try: print(f"server is started! ip:{ip} port:{str(port)}") while True: time.sleep(60 * 60) except Exception as es: print(es) server.stop(0) if __name__ == '__main__': server("127.0.0.1", 8000) # client.py import grpc from proto import example_pb2_grpc, example_pb2 def client(ip: str, port: int) -> None: target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub data = "hello 123" request = example_pb2.Request(data=data) res = cli.Hello(request) print(f"ret:{res.ret}, data:{res.data}") if __name__ == '__main__': client("127.0.0.1", 8000)
3. grpc的使用之?dāng)?shù)據(jù)傳輸大小配置
默認(rèn)情況下,gRPC 將傳入消息限制為 4 MB。 傳出消息沒(méi)有限制。
1)example.proto定義不變
2)編輯client.py 和 server.py
# server.py import time import grpc from concurrent import futures from proto import example_pb2_grpc, example_pb2 class ServiceBack(example_pb2_grpc.HelloServiceServicer): """接口的具體功能實(shí)現(xiàn)""" def Hello(self, request, context): """hello""" data = request.data print(data) ret_data = "Response:" + data return example_pb2.Response(ret=0, data=ret_data) def server(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=options) # ??為10的線程池 ai_servicer = ServiceBack() example_pb2_grpc.add_HelloServiceServicer_to_server(ai_servicer, server) server.add_insecure_port(f"{ip}:{port}") server.start() try: print(f"server is started! ip:{ip} port:{str(port)}") while True: time.sleep(60 * 60) except Exception as es: print(es) server.stop(0) if __name__ == '__main__': server("127.0.0.1", 8000)
# client.py import grpc from proto import example_pb2_grpc, example_pb2 def client(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub data = "hello 123" * 1024 * 1024 request = example_pb2.Request(data=data) res = cli.Hello(request) print(f"ret:{res.ret}, data:{res.data}") if __name__ == '__main__': client("127.0.0.1", 8000)
4. grpc的使用之超時(shí)配置
1)example.proto定義不變
2)編輯client.py 和 server.py
# server.py import time import grpc from concurrent import futures from proto import example_pb2_grpc, example_pb2 class ServiceBack(example_pb2_grpc.HelloServiceServicer): """接口的具體功能實(shí)現(xiàn)""" def Hello(self, request, context): """hello""" data = request.data print(data) time.sleep(2) ret_data = "Response:" + data return example_pb2.Response(ret=0, data=ret_data) def server(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=options) # ??為10的線程池 ai_servicer = ServiceBack() example_pb2_grpc.add_HelloServiceServicer_to_server(ai_servicer, server) server.add_insecure_port(f"{ip}:{port}") server.start() try: print(f"server is started! ip:{ip} port:{str(port)}") while True: time.sleep(60 * 60) except Exception as es: print(es) server.stop(0) if __name__ == '__main__': server("127.0.0.1", 8000)
# client.py import sys import grpc from proto import example_pb2_grpc, example_pb2 def client(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: data = "hello 123" request = example_pb2.Request(data=data) res = cli.Hello(request, timeout=1) # timeout 單位:秒 print(f"ret:{res.ret}, data:{res.data}") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) if __name__ == '__main__': client("127.0.0.1", 8000)
運(yùn)行結(jié)果:
grpc.RpcError Deadline Exceeded
5. grpc之大文件之流stream傳輸
1)在example.proto重新定義傳送體
// 聲明 syntax = "proto3"; package proto; // service創(chuàng)建 service HelloService{ rpc Hello(Request) returns (Response) {} // 單單傳送消息 rpc ClientTOServer(stream UpFileRequest) returns (Response) {} // 流式上傳文件 rpc ServerTOClient(Request) returns (stream UpFileRequest) {} // 流式下載文件 } // 請(qǐng)求參數(shù)消息體 1、2是指參數(shù)順序 message Request { string data = 1; } // 返回參數(shù)消息體 message Response { int32 ret = 1; //返回碼 string data = 2; } message UpFileRequest { string filename = 1; int64 sendsize = 2; int64 totalsize = 3; bytes data = 4; } //python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ ./example.proto
2)在虛擬環(huán)境里使用命令生成py文件,參考2. 2)
3)編輯client.py 和 server.py
# server.py import os import time import grpc from concurrent import futures from proto import example_pb2_grpc, example_pb2 class ServiceBack(example_pb2_grpc.HelloServiceServicer): """接口的具體功能實(shí)現(xiàn)""" def Hello(self, request, context): """hello""" data = request.data print(data) time.sleep(2) ret_data = "Response:" + data return example_pb2.Response(ret=0, data=ret_data) def ClientTOServer(self, request_iterator, context): """上傳文件""" data = bytearray() for UpFileRequest in request_iterator: file_name = UpFileRequest.filename file_size = UpFileRequest.totalsize file_data = UpFileRequest.data print(f"文件名稱(chēng):{file_name}, 文件總長(zhǎng)度:{file_size}") data.extend(file_data) # 拼接兩個(gè)bytes print(f"已接收長(zhǎng)度:{len(data)}") if len(data) == file_size: with open("242_copy.mp3", "wb") as fw: fw.write(data) print(f"{file_name=} 下載完成") (ret, res) = (0, file_name) else: print(f"{file_name=} 下載失敗") (ret, res) = (-1, file_name) return example_pb2.Response(ret=ret, data=res) def ServerTOClient(self, request, context): """下載文件""" fp = request.data print(f"下載文件:{fp=}") # 獲取文件名和文件大小 file_name = os.path.basename(fp) file_size = os.path.getsize(fp) # 獲取文件大小 # 發(fā)送文件內(nèi)容 part_size = 1024 * 1024 # 每次讀取1MB數(shù)據(jù) count = 1 with open(fp, "rb") as fr: while True: try: if count == 1: count += 1 yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=0, data=b"") else: context = fr.read(part_size) if context: yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=len(context), data=context) else: print(f"發(fā)送完畢") return 0 except Exception as es: print(es) def server(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=options) # ??為10的線程池 ai_servicer = ServiceBack() example_pb2_grpc.add_HelloServiceServicer_to_server(ai_servicer, server) server.add_insecure_port(f"{ip}:{port}") server.start() try: print(f"server is started! ip:{ip} port:{str(port)}") while True: time.sleep(60 * 60) except Exception as es: print(es) server.stop(0) if __name__ == '__main__': server("127.0.0.1", 8000)
# client.py import os import sys import grpc from proto import example_pb2_grpc, example_pb2 def send_stream_data(fp: str): """迭代器發(fā)送大文件""" # 獲取文件名和文件大小 file_name = os.path.basename(fp) file_size = os.path.getsize(fp) # 獲取文件大小 # 發(fā)送文件內(nèi)容 part_size = 1024 * 1024 # 每次讀取1MB數(shù)據(jù) count = 1 with open(fp, "rb") as fr: while True: try: if count == 1: count += 1 yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=0, data=b"") else: context = fr.read(part_size) if context: yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=len(context), data=context) else: print(f"發(fā)送完畢") return 0 except Exception as es: print(es) def client(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: data = "hello 123" request = example_pb2.Request(data=data) res = cli.Hello(request, timeout=1) # timeout 單位:秒 print(f"ret:{res.ret}, data:{res.data}") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) def client_to_server(ip: str, port: int, fp: str): """ 流式上傳數(shù)據(jù)。 """ # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: request = send_stream_data(fp=fp) res = cli.ClientTOServer(request, timeout=600) # timeout 單位:秒 print(f"ret:{res.ret}, data:{res.data}") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) def server_to_client(ip: str, port: int, fp: str): """ 流式上傳數(shù)據(jù)。 """ # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: data = bytearray() request = example_pb2.Request(data=fp) filename = "" for res in cli.ServerTOClient(request, timeout=300): filename = res.filename total_size = res.totalsize data += res.data if total_size == len(data): with open("242_1.mp3", "wb") as fw: fw.write(data) print(f"{filename=} : {total_size=} 下載完成!") else: print(f"{filename=} 下載失??!") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) if __name__ == '__main__': # client("127.0.0.1", 8000) # client_to_server("127.0.0.1", 8000, "242.mp3") server_to_client("127.0.0.1", 8000, "242.mp3")
6. grpc之大文件之流async異步傳輸
# server.py import os import time import grpc from concurrent import futures from proto import example_pb2_grpc, example_pb2 import asyncio class ServiceBack(example_pb2_grpc.HelloServiceServicer): """接口的具體功能實(shí)現(xiàn)""" def Hello(self, request, context): """hello""" data = request.data print(data) time.sleep(2) ret_data = "Response:" + data return example_pb2.Response(ret=0, data=ret_data) def ClientTOServer(self, request_iterator, context): """上傳文件""" data = bytearray() for UpFileRequest in request_iterator: file_name = UpFileRequest.filename file_size = UpFileRequest.totalsize file_data = UpFileRequest.data print(f"文件名稱(chēng):{file_name}, 文件總長(zhǎng)度:{file_size}") data.extend(file_data) # 拼接兩個(gè)bytes print(f"已接收長(zhǎng)度:{len(data)}") if len(data) == file_size: with open("242_copy.mp3", "wb") as fw: fw.write(data) print(f"{file_name=} 下載完成") (ret, res) = (0, file_name) else: print(f"{file_name=} 下載失敗") (ret, res) = (-1, file_name) return example_pb2.Response(ret=ret, data=res) def ServerTOClient(self, request, context): """下載文件""" fp = request.data print(f"下載文件:{fp=}") # 獲取文件名和文件大小 file_name = os.path.basename(fp) file_size = os.path.getsize(fp) # 獲取文件大小 # 發(fā)送文件內(nèi)容 part_size = 1024 * 1024 # 每次讀取1MB數(shù)據(jù) count = 1 with open(fp, "rb") as fr: while True: try: if count == 1: count += 1 yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=0, data=b"") else: context = fr.read(part_size) if context: yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=len(context), data=context) else: print(f"發(fā)送完畢") return 0 except Exception as es: print(es) async def server(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=10), options=options) # ??為10的線程池 ai_servicer = ServiceBack() example_pb2_grpc.add_HelloServiceServicer_to_server(ai_servicer, server) server.add_insecure_port(f"{ip}:{port}") await server.start() try: print(f"server is started! ip:{ip} port:{str(port)}") await server.wait_for_termination() except Exception as es: print(es) await server.stop(None) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait([server("127.0.0.1", 8000)])) loop.close()
# client.py import os import sys import grpc from proto import example_pb2_grpc, example_pb2 import asyncio def send_stream_data(fp: str): """迭代器發(fā)送大文件""" # 獲取文件名和文件大小 file_name = os.path.basename(fp) file_size = os.path.getsize(fp) # 獲取文件大小 # 發(fā)送文件內(nèi)容 part_size = 1024 * 1024 # 每次讀取1MB數(shù)據(jù) count = 1 with open(fp, "rb") as fr: while True: try: if count == 1: count += 1 yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=0, data=b"") else: context = fr.read(part_size) if context: yield example_pb2.UpFileRequest(filename=file_name, totalsize=file_size, sendsize=len(context), data=context) else: print(f"發(fā)送完畢") return 0 except Exception as es: print(es) async def client(ip: str, port: int) -> None: # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) async with grpc.aio.insecure_channel(target, options=options) as channel: # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: data = "hello 123" request = example_pb2.Request(data=data) res = await cli.Hello(request, timeout=3) # timeout 單位:秒 print(f"ret:{res.ret}, data:{res.data}") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) async def client_to_server(ip: str, port: int, fp: str): """ 流式上傳數(shù)據(jù)。 """ # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) async with grpc.aio.insecure_channel(target, options=options) as channel: # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: request = send_stream_data(fp=fp) res = await cli.ClientTOServer(request, timeout=600) # timeout 單位:秒 print(f"ret:{res.ret}, data:{res.data}") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) def server_to_client(ip: str, port: int, fp: str): """ 流式上傳數(shù)據(jù)。 """ # 數(shù)據(jù)傳輸大小配置 max_message_length = 1024 * 1024 * 1024 # 1G options = [('grpc.max_send_message_length', max_message_length), ('grpc.max_receive_message_length', max_message_length), ('grpc.enable_retries', 1), ] target = str(ip) + ":" + str(port) channel = grpc.insecure_channel(target, options=options) # 連接rpc服務(wù)器 cli = example_pb2_grpc.HelloServiceStub(channel) # 創(chuàng)建Stub try: data = bytearray() request = example_pb2.Request(data=fp) filename = "" for res in cli.ServerTOClient(request, timeout=300): filename = res.filename total_size = res.totalsize data += res.data if total_size == len(data): with open("242_1.mp3", "wb") as fw: fw.write(data) print(f"{filename=} : {total_size=} 下載完成!") else: print(f"{filename=} 下載失??!") except grpc.RpcError as rpc_error: print("grpc.RpcError", rpc_error.details()) except Exception as es: print(es) finally: sys.exit(-1) if __name__ == '__main__': # asyncio.run(client("127.0.0.1", 8000)) asyncio.run(client_to_server("127.0.0.1", 8000, "242.mp3")) # server_to_client("127.0.0.1", 8000, "242.mp3")
結(jié)論: 在本地測(cè)了一下不加async和加async的文件上傳傳送, async還慢點(diǎn),嘿嘿嘿。
到此這篇關(guān)于利用Python語(yǔ)言的grpc實(shí)現(xiàn)消息傳送詳解的文章就介紹到這了,更多相關(guān)Python grpc消息傳送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python3.5編程實(shí)現(xiàn)修改IIS WEB.CONFIG的方法示例
這篇文章主要介紹了Python3.5編程實(shí)現(xiàn)修改IIS WEB.CONFIG的方法,涉及Python針對(duì)xml格式文件的讀寫(xiě)以及節(jié)點(diǎn)操作相關(guān)技巧,需要的朋友可以參考下2017-08-08Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11python結(jié)合多線程爬取英雄聯(lián)盟皮膚(原理分析)
多線程是為了同步完成多項(xiàng)任務(wù),不是為了提高運(yùn)行效率,而是為了提高資源使用效率來(lái)提高系統(tǒng)的效率。這篇文章主要介紹了python爬取英雄聯(lián)盟皮膚結(jié)合多線程的方法,需要的朋友可以參考下2021-05-05python list刪除元素時(shí)要注意的坑點(diǎn)分享
下面小編就為大家分享一篇python list刪除元素時(shí)要注意的坑點(diǎn)分享,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-046個(gè)實(shí)用的Python自動(dòng)化腳本詳解
每天你都可能會(huì)執(zhí)行許多重復(fù)的任務(wù),例如閱讀 pdf、播放音樂(lè)、查看天氣、打開(kāi)書(shū)簽、清理文件夾等等,使用自動(dòng)化腳本,就無(wú)需手動(dòng)一次又一次地完成這些任務(wù),非常方便??旄S小編一起試一試吧2022-01-01Python 使用 PyQt5 開(kāi)發(fā)的關(guān)機(jī)小工具分享
這篇文章主要介紹了Python 使用 PyQt5 開(kāi)發(fā)的關(guān)機(jī)小工具分享,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07python中numpy數(shù)組與list相互轉(zhuǎn)換實(shí)例方法
在本篇文章里小編給大家整理的是一篇關(guān)于python中numpy數(shù)組與list相互轉(zhuǎn)換實(shí)例方法,對(duì)此有興趣的朋友們可以學(xué)習(xí)下。2021-01-01Pytorch使用MNIST數(shù)據(jù)集實(shí)現(xiàn)基礎(chǔ)GAN和DCGAN詳解
今天小編就為大家分享一篇Pytorch使用MNIST數(shù)據(jù)集實(shí)現(xiàn)基礎(chǔ)GAN和DCGAN詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01