目錄java
Peer-to-peer
沒有服務器
任意端系統之間直接通訊
節點階段性接入Internet
節點可能更換IP地址python
問題 : 從一個服務器向N個節點分發一個文件須要多長時間?linux
==如下速度計算所有是創建在假設:因特網核心速度無限制;服務器客戶端帶寬所有被運用到文件傳輸。並且所有是一個下限!==git
us: 服務器上傳帶寬(upload)
ui: 節點i的上傳帶寬
di: 節點i的下載帶寬程序員
服務器串行地發送N個副本
時間: NF/us
客戶機i須要F/di時間下載github
==對cs,能夠看到時間在N大的時候是和N呈線性的!==算法
服務器必須發送一個副本
時間: F/us
客戶機i須要F/di時間下載
總共須要下載NF比特
最快的可能上傳速率:us + ui編程
==這解釋了p2p的自拓展性:對等方除了是bit的消費者仍是從新分發者。==windows
tracker: 跟蹤參與torrent的節點,每一個torrent都有一個trackerapi
torrent: 交換同一個文件的文件塊的節點組
BitTorrent技術對網絡性能有哪些潛在的危害?
這個欠考慮。
BT三大指控:高溫、重複讀寫、扇區斷塊。
Bittorrent下載是寬帶時代新興的P2P交換文件模式,各用戶之間共享資源,互至關種子和中繼站,俗稱BT下載。因爲每一個用戶的下載和上傳幾乎是同時進行,所以下載的速度很是快。不過,開發BT的人由於缺少對維護硬盤的考慮,使用了不好的HASH算法,它會將下載的數據直接寫進硬盤(不像FlashGet等下載工具能夠調整緩存,到指定的數據量後才寫入硬盤),所以形成硬盤損害,提前結束硬盤的壽命。
此外,BT下載事先要申請硬盤空間,在下載較大的文件的時候,通常會有2~3分鐘時間整個系統優先權所有被申請空間的任務佔用,其餘任務反應極慢。有些人爲了充分利用帶寬,還會同時進行幾個BT下載任務,此時就很是容易出現因爲磁盤佔用率太高而致使的死機故障。
由於BT對硬盤的重複讀寫動做會產生高溫,令硬盤的溫度升高,直接影響硬盤的壽命。而當下載人數愈多,同一時間讀取你的硬盤的人亦愈多,硬盤大量進行重複讀寫的動做,加速消耗。基於對硬盤工做原理的分析能夠知道,硬盤的磁頭壽命是有限的,頻繁的讀寫會加快磁頭臂及磁頭電機的磨損,頻繁的讀寫磁盤某個區域更會使該區溫度升高,將影響該區磁介質的穩定性還會導至讀寫錯誤,高溫還會使該區因熱膨漲而使磁頭和碟面更近了(正常狀況下磁頭和碟面只有幾個微米,高溫膨脹會讓磁頭更靠近碟面),並且也會影響薄膜式磁頭的數據讀取靈敏度,會使晶體振盪器的時鐘主頻發生改變,還會形成硬盤電路元件失靈。任務繁多也會導至ide硬盤過早損壞,因爲ide硬盤自身的不足,過多任務請求是會使尋道失敗率上升導至磁頭頻繁複位(復位就是磁頭回復到 0磁道,以便從新尋道)加速磁頭臂及磁頭電機磨損。所以有些人形容,BT就像把單邊燃燒的柴枝折開兩、三段一塊兒燃燒,大量的讀寫動做會大大加速硬盤的消耗,燃燒硬盤的生命。
其次,同時由於下載太多東西,使扇區的編排混亂,讀寫數據時要在不一樣扇區中讀取,增長讀寫次數,加速硬盤消耗。
二、對網絡帶寬的損害當前,以BitTorrent(如下簡稱BT)爲表明的P2P下載軟件流量佔用了寬帶接入的大量帶寬,據統計已經超過了50%。這對於以太網接入等共享帶寬的寬帶接入方式提出了很大的挑戰,大量的使接入層交換機的端口長期工做在線速狀態,嚴重影響了用戶使用正常的Web、E-mail以及視頻點播等業務,並可能形成重要數據沒法及時傳輸而給企業帶來損失。所以,運營商、企業用戶以及教育等行業的用戶都有對這類流量進行限制的要求。BT將會佔用太多的網絡資源,從而有可能在接入網、傳輸網、骨幹網等不一樣層面造成瓶頸,形成資源緊張,這彷佛也是目前運營商包括網通、長寬等封掉BT端口的最大理由。
三、滋長了病毒的傳播
2005年11月17日,公安部公共信息網絡安全監察處許劍卓處長在天津AVAR2005大會上作了《中國網絡犯罪現狀》的報告,報告指出,經過計算機病毒和木馬進行的黑客行爲是計算機網絡犯罪的主要根源。調查狀況代表,計算機病毒除了經過常規的電子郵件等途徑傳播外,目前網絡上盛行的P2P軟件成爲計算機病毒和木馬傳播的主要途徑。這些病毒和木馬對企業的安全造成巨大的挑戰。
四、可能面臨着版權侵害的風險Fred Lawrence是一個美國普通老人,今年67歲,由於本身孫子的緣故惹來了美國電影協會(MPAA)的大麻煩。Lawrence的孫子經過iMesh P2P服務在家中的電腦下載並分享了4部電影,美國電影協會經過IP地址找到了他和他的電腦,並以侵犯版權爲由要求老人爲此在18個月中付出4000美圓的罰金……;
如今國內外都在嚴厲打擊盜版,不排除版權做者或機構經過各類網絡跟蹤技術來找到非法進行P2P下載的用戶,並提起訴訟或者其餘賠償要求;若是企業員工進行了這些行爲,可能由此對企業的形象形成極大負面影響,並可能使得企業遭受其餘損失。此外,員工可能經過BT等下載一些色情、反動、暴力的等違法的信息,這些信息可能被公安機關檢測到,由此可能給員工和企業帶來法律風險。
由覆蓋網絡(overlay network): Graph來組織查詢和鏈接
在覆蓋網絡上進行廣播來完成查詢。
若是查詢命中,則利用反向路徑發回查詢節點
通話時是P2P的:==用戶/節點對之間直接通訊。可是索引階段是層次式覆蓋網絡架構==
私有應用層協議
採用層次式覆蓋網絡架構
索引負責維護用戶名與IP地址間的映射
索引分佈在超級節點上
查閱Skype應用的相關資料,就其架構、協議、算法等撰寫一篇調研報告,長度在5000字以上。
開發網絡應用程序關聯的API類型。
按5層結構觀察,除物理層外,全部層次,包括應用層自己,都能提供網絡程序設計的api。
抽象通訊機制。是一種門面模式,爲應用層封裝傳輸層協議,爲應用層提供抽象鏈路。
sockaddr與sockaddr_in:
註釋中標明瞭屬性的含義及其字節大小,這兩個結構體同樣大,都是16個字節,並且都有family屬性,不一樣的是:
sockaddr用其他14個字節來表示sa_data,而sockaddr_in把14個字節拆分紅sin_port, sin_addr和sin_zero
分別表示端口、ip地址。sin_zero用來填充字節使sockaddr_in和sockaddr保持同樣大小。
sockaddr和sockaddr_in包含的數據都是同樣的,但他們在使用上有區別:
程序員不該操做sockaddr,sockaddr是給操做系統用的
程序員應使用sockaddr_in來表示地址,sockaddr_in區分了地址和端口,使用更方便。
==思考:爲什麼這裏要維護地址長度?==
winsock實現機制是動態鏈接庫,因此要初始化、釋放動態鏈接庫,使用api開始和結束要分別調用
使用Socket的應用程序在使用Socket以前必須首先調用WSAStartup函數
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); wVersionRequested = MAKEWORD( 2, 1 ); err = WSAStartup( wVersionRequested, &wsaData );
應用程序在完成對請求的Socket庫的使用,最後要調用WSACleanup函數。
int WSACleanup (void);
下面的不帶wsa的api是多系統通用的,上面的wsa api是win專用的
建立套接字
sd = socket(protofamily,type,proto);
sd
protofamily協議族(說明面向哪一個協議族)
==type套接字類型==(每種協議族不一樣,下面的例子是)
proto(協議號,訪問的是哪一種協議):0表示缺省,可使用對應數字表示所選協議族和套接字類型的支持的協議號
例:建立一個流套接字的代碼段
struct protoent *p; p=getprotobyname("tcp"); SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);
==思考:爲什麼這裏擁PF——INEF?==
stream(傳輸流,使用tcp):可靠、面向鏈接、字節流傳輸、點對點(一個鏈接只能鏈接兩點)
dgram(datagram數據報,使用udp):不可靠、無鏈接、數據報傳輸
raw(raw:生的,這裏指不加傳輸層處理的原始套接字)
關閉一個描述符爲sd的套接字。
int closesocket(SOCKET sd);
綁定(填寫)套接字的本地端點地址(IP地址+==16進制==端口號)
int bind(sd,localaddr,addrlen);
==思考:爲何要加地址長度?localaddr裏面已經包含長度==
置流套接字處於監聽狀態,listen只用於服務器端,僅面向tcp鏈接的流類型。
int listen(sd,queuesize);
鏈接創建不等同數據請求發送,還須要send
connect(sd,saddr,saddrlen);
newsock = accept(sd,caddr,caddrlen);
send(sd,*buf,len,flags); sendto(sd,*buf,len,flags,destaddr,addrlen);
recv(sd,*buffer,len,flags); recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);
int setsockopt(int sd, int level, int optname, *optval, int optlen); int getsockopt(int sd, int level, int optname,*optval, socklen_t *optlen);
==按照服務器、客戶機、tcp、udp去理解==
osi7層模型纔有表示層來兼容字節順序,5層中是沒有的,須要協議輔助完成該功能。
注意,這張圖並不完整,注意區分tcp udp
listen並不阻塞,listen只是開啓listen狀態。
recv、accept是真正對鏈接的反饋須要循環開啓,也是後續的操做的前提,因此要阻塞accept、recv。
阻塞過程,表示當函數未成功則一直等待。
左右兩邊都有2個阻塞函數。
==ns:newsocket==
注意兩邊close的區別,服務器只是關閉了ns。
socket服務器按上述流程要:選擇服務器ip、端口號,網絡字節轉化,選擇協議。
具體實現上要求:4位ip/域名:服務名轉化爲32位ip:端口號,協議名轉化爲服務號
設計一個connectsock過程封裝底層代碼,這部分代碼在udp和tcp中均可能用到。
/* consock.cpp - connectsock */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <winsock.h> #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff #endif /* INADDR_NONE */ void errexit(const char *, ...); /*------------------------------------------------------- * connectsock - allocate & connect a socket using TCP or UDP *------------------------------------------------------ */ //transport指的是所用協議名稱 SOCKET connectsock(const char *host, const char *service, const char *transport ) { struct hostent *phe; /* pointer to host information entry */ struct servent *pse; /* pointer to service information entry */ struct protoent *ppe; /* pointer to protocol information entry */ struct sockaddr_in sin;/* an Internet endpoint address */ int s, type; /* socket descriptor and socket type */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; /* Map service name to port number */ if ( pse = getservbyname(service, transport) ) sin.sin_port = pse->s_port; else if ( (sin.sin_port = htons((u_short)atoi(service))) == 0 ) errexit("can't get \"%s\" service entry\n", service); /* Map host name to IP address, allowing for dotted decimal */ if ( phe = gethostbyname(host) ) memcpy(&sin.sin_addr, phe->h_addr, phe->h_length); else if ( (sin.sin_addr.s_addr = inet_addr(host))==INADDR_NONE) errexit("can't get \"%s\" host entry\n", host); /* Map protocol name to protocol number */ if ( (ppe = getprotobyname(transport)) == 0) errexit("can't get \"%s\" protocol entry\n", transport); /* Use protocol to choose a socket type */ if (strcmp(transport, "udp") == 0) type = SOCK_DGRAM; else type = SOCK_STREAM; /* Allocate a socket */ s = socket(PF_INET, type, ppe->p_proto); if (s == INVALID_SOCKET) errexit("can't create socket: %d\n", GetLastError()); /* Connect the socket */ if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==SOCKET_ERROR) errexit("can't connect to %s.%s: %d\n", host, service, GetLastError()); return s; }
設計connectUDP過程用於建立鏈接模式客戶端UDP套接字
/* conUDP.cpp - connectUDP */ #include <winsock.h> SOCKET connectsock(const char *, const char *, const char *); /*------------------------------------------------------- * connectUDP - connect to a specified UDP service * on a specified host *----------------------------------------------------- */ SOCKET connectUDP(const char *host, const char *service ) { return connectsock(host, service, "udp"); }
設計connectTCP過程,用於建立客戶端TCP套接字
/* conTCP.cpp - connectTCP */ #include <winsock.h> SOCKET connectsock(const char *, const char *, const char *); /*---------------------------------------------------- * connectTCP - connect to a specified TCP service * on a specified host *--------------------------------------------------- */ SOCKET connectTCP(const char *host, const char *service ) { return connectsock( host, service, "tcp"); }
/* errexit.cpp - errexit */ #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <winsock.h> /*---------------------------------------------------------- * errexit - print an error message and exit *---------------------------------------------------------- */ /*VARARGS1*/ void errexit(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); WSACleanup(); exit(1);}
DAYTIME服務
獲取日期和時間
雙協議服務(TCP、 UDP),端口號13
TCP版利用TCP鏈接請求觸發服務(==不須要發送任何數據,只須要發送鏈接創建請求==)
UDP版須要==客戶端發送一個數據報==
/* TCPdtc.cpp - main, TCPdaytime */ #include <stdlib.h> #include <stdio.h> #include <winsock.h> void TCPdaytime(const char *, const char *); void errexit(const char *, ...); SOCKET connectTCP(const char *, const char *); #define LINELEN 128 #define WSVERS MAKEWORD(2, 0) /*-------------------------------------------------------- * main - TCP client for DAYTIME service *-------------------------------------------------------- */ int main(int argc, char *argv[]) { char *host = "localhost"; /* host to use if none supplied */ char *service = "daytime"; /* default service port */ WSADATA wsadata; switch (argc) { case 1: host = "localhost"; break; case 3: service = argv[2]; /* FALL THROUGH */ case 2: host = argv[1]; break; default: fprintf(stderr, "usage: TCPdaytime [host [port]]\n"); exit(1); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); TCPdaytime(host, service); WSACleanup(); return 0; /* exit */ } /*----------------------------------------------------- * TCPdaytime - invoke Daytime on specified host and print results *----------------------------------------------------- */ void TCPdaytime(const char *host, const char *service) { char buf[LINELEN+1]; /* buffer for one line of text */ SOCKET s; /* socket descriptor */ int cc; /* recv character count */ s = connectTCP(host, service); cc = recv(s, buf, LINELEN, 0); while( cc != SOCKET_ERROR && cc > 0) { buf[cc] = '\0'; /* ensure null-termination */ (void) fputs(buf, stdout); cc = recv(s, buf, LINELEN, 0); } closesocket(s); }
/* UDPdtc.cpp - main, UDPdaytime */ #include <stdlib.h> #include <stdio.h> #include <winsock.h> void UDPdaytime(const char *, const char *); void errexit(const char *, ...); SOCKET connectUDP(const char *, const char *); #define LINELEN 128 #define WSVERS MAKEWORD(2, 0) #define MSG 「what daytime is it?\n" /*-------------------------------------------------------- * main - UDP client for DAYTIME service *-------------------------------------------------------- */ int main(int argc, char *argv[]) { char *host = "localhost"; /* host to use if none supplied */ char *service = "daytime"; /* default service port */ WSADATA wsadata; switch (argc) { case 1: host = "localhost"; break; case 3: service = argv[2]; /* FALL THROUGH */ case 2: host = argv[1]; break; default: fprintf(stderr, "usage: UDPdaytime [host [port]]\n"); exit(1); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); UDPdaytime(host, service); WSACleanup(); return 0; /* exit */ } /*----------------------------------------------------- * UDPdaytime - invoke Daytime on specified host and print results *----------------------------------------------------- */ void UDPdaytime(const char *host, const char *service) { char buf[LINELEN+1]; /* buffer for one line of text */ SOCKET s; /* socket descriptor */ int n; /* recv character count */ s = connectUDP(host, service); (void) send(s, MSG, strlen(MSG), 0); /* Read the daytime */ n = recv(s, buf, LINELEN, 0); if (n == SOCKET_ERROR) errexit("recv failed: recv() error %d\n", GetLastError()); else { buf[cc] = '\0'; /* ensure null-termination */ (void) fputs(buf, stdout); } closesocket(s); return 0; /* exit */ }
循環:同時處理一個用戶的請求
併發:同時處理多個用戶請求
無鏈接:基於udp
面向鏈接:基於tcp
服務器端不能使用connect()函數,connect()是客戶端專用
無鏈接服務器使用sendto()函數發送數據報
調用recvfrom()函數接收數據時,自動提取客戶進程端點地址
這裏123指的是第一步第二步
主線程1: 建立套接字,並綁定熟知端口號;
主線程2: 反覆調用recvfrom()函數,接收下一個客戶請求並建立==新線程處理(爲了併發)==該客戶響應;
子線程1: 接收一個特定請求;
子線程2: 依據應用層協議構造響應報文,並調用sendto()發送;
子線程3: 退出(一個子線程處理一個請求後即終止)。
這裏123指的是第一步第二步
主線程1: 建立(主)套接字,並綁定熟知端口號;
主線程2: 設置(主)套接字爲被動監聽模式,準備用於服務器;
主線程3: 反覆調用accept()函數接收下一個鏈接請求(經過主套接字),並創建一個新子線程處理該客戶響應;
子線程1: 接收一個客戶的服務請求(經過新建立的套接字);
子線程2: 遵循應用層協議與特定客戶進行交互;
子線程3: 關閉/釋放鏈接並退出(線程終止).
實現的一種設計,能夠不這麼設計。
設計一個底層過程隱藏底層代碼:passivesock()
兩個高層過程分別用於建立服務器端UDP套接字和TCP套接字(調用passivesock()函數):
passiveUDP()
passiveTCP()
/* passsock.cpp - passivesock */ #include <stdlib.h> #include <string.h> #include <winsock.h> void errexit(const char *, ...); /*----------------------------------------------------------------------- * passivesock - allocate & bind a server socket using TCP or UDP *------------------------------------------------------------------------ */ SOCKET passivesock(const char *service, const char *transport, int qlen) { struct servent *pse; /* pointer to service information entry */ struct protoent *ppe; /* pointer to protocol information entry */ struct sockaddr_in sin;/* an Internet endpoint address */ SOCKET s; /* socket descriptor */ int type; /* socket type (SOCK_STREAM, SOCK_DGRAM)*/ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; /* Map service name to port number */ if ( pse = getservbyname(service, transport) ) sin.sin_port = (u_short)pse->s_port; else if ( (sin.sin_port = htons((u_short)atoi(service))) == 0 ) errexit("can't get \"%s\" service entry\n", service); /* Map protocol name to protocol number */ if ( (ppe = getprotobyname(transport)) == 0) errexit("can't get \"%s\" protocol entry\n", transport); /* Use protocol to choose a socket type */ if (strcmp(transport, "udp") == 0) type = SOCK_DGRAM; else type = SOCK_STREAM; /* Allocate a socket */ s = socket(PF_INET, type, ppe->p_proto); if (s == INVALID_SOCKET) errexit("can't create socket: %d\n", GetLastError()); /* Bind the socket */ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) errexit("can't bind to %s port: %d\n", service, GetLastError()); if (type == SOCK_STREAM && listen(s, qlen) == SOCKET_ERROR) errexit("can't listen on %s port: %d\n", service, GetLastError()); return s;}
/* passUDP.cpp - passiveUDP */ #include <winsock.h> SOCKET passivesock(const char *, const char *, int); /*------------------------------------------------------------------------------------- * passiveUDP - create a passive socket for use in a UDP server *------------------------------------------------------------------------------------- */ SOCKET passiveUDP(const char *service) { return passivesock(service, "udp", 0); }
/* passTCP.cpp - passiveTCP */ #include <winsock.h> SOCKET passivesock(const char *, const char *, int); /*------------------------------------------------------------------------------------ * passiveTCP - create a passive socket for use in a TCP server *------------------------------------------------------------------------------------ */ SOCKET passiveTCP(const char *service, int qlen) { return passivesock(service, "tcp", qlen);
/* UDPdtd.cpp - main, UDPdaytimed */ #include <stdlib.h> #include <winsock.h> #include <time.h> void errexit(const char *, ...); SOCKET passiveUDP(const char *); #define WSVERS MAKEWORD(2, 0) /*------------------------------------------------------------------------ * main - Iterative UDP server for DAYTIME service *------------------------------------------------------------------------ */ void main(int argc, char *argv[]) { struct sockaddr_in fsin; /* the from address of a client */ char *service = "daytime"; /* service name or port number */ SOCKET sock; /* socket */ int alen; /* from-address length */ char * pts; /* pointer to time string */ time_t now; /* current time */ WSADATA wsadata; switch (argc) { case 1: break; case 2: service = argv[1]; break; default: errexit("usage: UDPdaytimed [port]\n"); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); sock = passiveUDP(service); while (1) { alen = sizeof(struct sockaddr); if (recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) == SOCKET_ERROR) errexit("recvfrom: error %d\n", GetLastError()); (void) time(&now); pts = ctime(&now); (void) sendto(sock, pts, strlen(pts), 0, (struct sockaddr *)&fsin, sizeof(fsin)); } return 1; /* not reached */ }
/* TCPdtd.cpp - main, TCPdaytimed */ #include <stdlib.h> #include <winsock.h> #include <process.h> #include <time.h> void errexit(const char *, ...); void TCPdaytimed(SOCKET); SOCKET passiveTCP(const char *, int); #define QLEN 5 #define WSVERS MAKEWORD(2, 0) /*------------------------------------------------------------------------ * main - Concurrent TCP server for DAYTIME service *------------------------------------------------------------------------ */ void main(int argc, char *argv[]) { struct sockaddr_in fsin; /* the from address of a client */ char *service = "daytime"; /* service name or port number*/ SOCKET msock, ssock; /* master & slave sockets */ int alen; /* from-address length */ WSADATA wsadata; switch (argc) { case1: break; case2: service = argv[1]; break; default: errexit("usage: TCPdaytimed [port]\n"); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); msock = passiveTCP(service, QLEN); while (1) { alen = sizeof(struct sockaddr); ssock = accept(msock, (struct sockaddr *)&fsin, &alen); if (ssock == INVALID_SOCKET) errexit("accept failed: error number %d\n", GetLastError()); if (_beginthread((void (*)(void *)) TCPdaytimed, 0, (void *)ssock) < 0) { errexit("_beginthread: %s\n", strerror(errno)); } } return 1; /* not reached */ } /*---------------------------------------------------------------------- * TCPdaytimed - do TCP DAYTIME protocol *----------------------------------------------------------------------- */ void TCPdaytimed(SOCKET fd) { char * pts; /* pointer to time string */ time_t now; /* current time */ (void) time(&now); pts = ctime(&now); (void) send(fd, pts, strlen(pts), 0); (void) closesocket(fd); }
Markdown文本:https://github.com/ArrogantL/BlogData/tree/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9Cspoc/W2
本文做者: ArrogantL (arrogant262@gmail.com) : 本博客全部文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明出處!