解析C#中的分部類和分部辦法。本站提示廣大學習愛好者:(解析C#中的分部類和分部辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是解析C#中的分部類和分部辦法正文
兩種協定 TCP 和 UDP
前者可以懂得為有包管的銜接,後者是尋求疾速的銜接。
固然最初一點有些 太甚相對 ,然則如今不需熬斟酌太多,由於初入套接字編程,一切從簡。
略微試想便可以或許年夜致懂得, TCP 尋求的是靠得住的傳輸數據, UDP 尋求的則是疾速的傳輸數據。
前者有繁瑣的銜接進程,後者則是基本不樹立靠得住銜接(不是相對),只是將數據發送而不斟酌能否達到。
以下例子以 *nix 平台的便准為例,由於 Windows平台須要斟酌額定的加載成績,稍作添加就可以在 Windows 平台上運轉UDP。
UDP
這是一個非常簡練的銜接方法,假定有兩台主機停止通訊,一台只發送,一台只吸收。
吸收端:
int sock; /* 套接字 */ socklen_t addr_len; /* 發送真個地址長度,用於 recvfrom */ char mess[15]; char get_mess[GET_MAX]; /* 後續版本應用 */ struct sockaddr_in recv_host, send_host; /* 創立套接字 */ sock = socket(PF_INET, SOCK_DGRAM, 0); /* 把IP 和 端標語信息綁定在套接字上 */ memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 吸收隨意率性的IP */ recv_host.sin_port = htons(6000); /* 應用6000 端標語 */ bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host)); /* 進入吸收信息的狀況 */ recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len); /* 吸收完成,封閉套接字 */ close(sock);
上述代碼省略了很多需要的 毛病檢討 ,在現實編寫時要添加
代碼說明:
PF_INET 代表協定的類型,此處代表 IPv4 收集協定族, 異樣 PF_INET6 代表 IPv6 收集協定族,這個規模在前方零丁記載,不與IPv4混在一路(其實不意味著更龐雜,現實上更輕便)。
AF_INET 代表地址的類型,此處代表 IPv4 收集協定應用的地址族, 異樣有 AF_INET6 (在操作體系完成中 PF_INET 和 AF_INET 的值一樣,然則照樣要寫宏更好,而不該該直接用數字或許,混雜應用)
htonl 和 htons 兩個函數的應用觸及到 年夜端小端成績, 這裡不論述,須要記住的是在收集編程時必定要應用這類函數將需要信息轉為 年夜端表現法 。
(struct sockaddr *) 這個強迫轉換是為了參數的必需,但不會失足,由於 sizeof(struct sockaddr_in) == sizeof(struct sockaddr) 詳細可以查詢相干信息,之所以這麼做是為了便利編寫套接字法式的法式員。
發送端:
int sock; const char* mess = "Hello Server!"; char get_mess[GET_MAX]; /* 後續版本應用 */ struct sockaddr_in recv_host; socklen_t addr_len; /* 創立套接字 */ sock = socket(PF_INET, SOCK_DGRAM, 0); /* 綁定 */ memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); recv_host.sin_port = htons(6000); /* 發送信息 */ /* 在此處,發送真個IP地址和端標語等各類信息,跟著這個函數的挪用,主動綁定在了套接字上 */ sendto(sock, mess, strlen(mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host)); /* 完成,封閉 */ close(sock);
上述代碼是發送端。
代碼說明:
inet_addr 函數是用於將字符串格局的 IP地址 轉換為 年夜端表現法的 地址類型,即 s_addr 的類型 in_addr_t
與之相反,異樣也有功效相反的函數 inet_ntoa 用於將 in_addr_t 類型轉為字符串,然則應用時必定要記住實時拷貝前往值 char addr[16]; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); strcpy(addr, inet_ntoa(recv_host.sin_addr.s_addr));
從上述代碼看出, UDP 協定的應用非常簡練,簡直就是 創立套接字->預備數據->設備套接字->發送/吸收->停止
個中,都沒有銜接的操作,然則現實上這是為了便利 UDP 隨時和 分歧的主機 停止通訊所默許的設置,假如須要和雷同主機一向通訊呢?
其中的緣由臨時不須要曉得,記載辦法,即長時光應用 UDP 和統一主機通訊時,可使用 connect 函數來停止優化本身。此時 假定兩台主機的現實功效分歧,既吸收也發送
發送端:
/* 後方高度分歧,將 bind函數調換為 */ connect(sock, (struct sockaddr *)&recv_host, sizeof(recv_host); // 將對方的 IP地址和 端標語信息 注冊進UDP的套接字中) while(1) /* 輪回的發送和吸收信息 */ { size_t read_len = 0; /* 本來應用的 sendto 函數,先擇改成應用 write 函數, Windows平台為 send 函數 */ write(sock, mess, strlen(mess)); /* send(sock, mess, strlen(mess), 0) FOR Windows Platform */ read_len = read(sock, get_mess, GET_MAX-1); /* recv(sock, mess, strlen(mess)-1, 0) FOR Windows Platform */ get_mess[read_len-1] = '\0'; printf("In Client like Host Recvive From Other Host : %s\n", get_mess); } /* 前方高度分歧 */
吸收端:
/* 後方分歧, 添加額定的 struct sockaddr_in send_host; 並添加輪回,結構收發的景象*/ while(1) { size_t read_len = 0; char sent_mess[15] = "Hello Sender!"; /* 用於發送的信息 */ sendto(sock, mess, strlen(sent_mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host)); read_len = recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len) mess[read_len-1] = '\0'; printf("In Sever like Host Recvive From other Host : %s\n", mess); } /* 前方高度分歧 */ /* * 之所以只在吸收端應用 connect 的緣由,便在於我們模仿的是 客戶端-辦事器 的模子,而辦事器的各項信息是不會隨便變革的 * 然則 客戶端就分歧了,能夠因為 ISP(Internet Server Provider) 的緣由,你的IP地址弗成能老是固定的,所以只能 * 包管 在客戶端 部門注冊了 辦事器 的各類信息,而不克不及在 辦事器端 注冊 客戶端 的信息。 * 固然也有破例,例如你就想這個軟件作為私密軟件,僅供兩小我應用, 且你有固定的 IP地址,那末你可以雙方都connect,然則 * 必定要留意,只需有一點信息更改,這個軟件便可能沒法正常的收發信息了。 */
代碼說明
故而現實上,固然後方的表格顯示,UDP 仿佛並沒有 connect 的應用需要,然則現實上照樣有效到的處所。
就 *nix 的 API 來講,sendto 和 write 的差別非常顯著,就是一個須要在參數中供給目的主機的各類信息,爾後者則不須要供給。異樣的事理recvfrom和read也是如斯。
這個代碼只是做演示罷了,所以將代碼置於無窮輪回傍邊,實際中可以自行界說出口前提。
以上是 UDP 的一些簡略解釋,入門足矣,並未具體論述某些 函數 的詳細用法,而是用現實例子來表現。 在 記載 TCP 之前,照樣須要講一個函數 shutdown
shutdown 與 close(closesocket)
起首要曉得,收集通訊普通而言是兩邊的配合停止的,換而言之就是雙向的,一個偏向只用來發送新聞,一個偏向只用來讀撤消息。
這就招致了,在停止套接字通訊的時刻,須要封閉兩個偏向的通道(臨時叫它們通道),那同時封閉不可嗎?可以啊
close(sock); // closesocket(sock); FOR Windows PlatForm 就是這麼干的,同時斷開兩個偏向的銜接。
簡略的通訊法式或許單向通訊法式這麼做切實其實無甚年夜礙,然則萬一在停止通訊的時刻須要吸收最初一個信息那該怎樣辦?
假定通訊停止,客戶端向辦事器發送 "Thank you"
辦事器須要吸收這個信息,以後能力封閉通訊
成績就在這之間,辦事器其實不曉得客戶端會在通訊停止後的甚麼時辰傳來信息
所以我們選擇在通訊完成後先封閉 辦事器的 發送通道(寫流),期待客戶端發來新聞後,封閉剩下的 吸收通道(讀流)
發送端:
/* 假定有一個 TCP 的銜接,此為客戶端 */ write(sock, "Thank you", 10); close(sock); // 寫完直接封閉通訊
吸收端:
/* 此為辦事器 */ /* 起首封閉寫流 */ shutdown(sock_c, SHUT_WR); read(sock_c, get_mess, GET_MAX); printf("Message : %s\n", get_mess); close(sock_c); close(sock_s); // 封閉兩個套接字是由於 TCP 辦事器真個須要,後續會記載
代碼說明
shutdown 函數的感化就是 可選擇的封閉誰人偏向的輸入
int shutdown(int sock, int howto);
sock 代表要操作的套接字
howto有幾個選擇
上面,有幾個構造體,和一個接口非常主要及經常使用:
改寫一下上方的例子:
吸收端:
int sock; /* 套接字 */ socklen_t addr_len; /* 發送真個地址長度,用於 recvfrom */ char mess[15]; char get_mess[GET_MAX]; /* 後續版本應用 */ struct sockaddr_in host_v4; /* IPv4 地址 */ struct sockaddr_in6 host_v6; /* IPv6 地址 */ struct addrinfo easy_to_use; /* 用於設定要獲得的信息和若何獲得信息 */ struct addrinfo *result; /* 用於存儲獲得的信息(須要留意內存洩漏) */ struct addrinfo * p; /* 預備信息 */ memset(&easy_to_use, 0, sizeof easy_to_use); easy_to_use.ai_family = AF_UNSPEC; /* 告知接口,我如今還不曉得地址類型 */ easy_to_use.ai_flags = AI_PASSIVE; /* 告知接口,稍後“你”幫我填寫我沒明白指定的信息 */ easy_to_use.ai_socktype = SOCK_DGRAM; /* UDP 的套接字 */ /* 其他位都為 0 */ /* 應用 getaddrinfo 接口 */ getaddrinfo(NULL, argv[1], &easy_to_use, &result); /* argv[1] 中寄存字符串情勢的 端標語 */ /* 創立套接字,此處會發生兩種寫法,但更保險,靠得住的寫法是如斯 */ /* 新式辦法 * sock = socket(PF_INET, SOCK_DGRAM, 0); */ /* 把IP 和 端標語信息綁定在套接字上 */ /* 新式辦法 * memset(&recv_host, 0, sizeof(recv_host)); * recv_host.sin_family = AF_INET; * recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 吸收隨意率性的IP */ * recv_host.sin_port = htons(6000); /* 應用6000 端標語 */ * bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host)); */ for(p = result; p != NULL; p = p->ai_next) /* 該語法須要開啟 -std=gnu99 尺度*/ { sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if(sock == -1) continue; if(bind(sock, p->ai_addr, p->ai_addrlen) == -1) { close(sock); continue; } break; /* 假如能履行到此,證實樹立套接字勝利,套接字綁定勝利,故不用再測驗考試。 */ } /* 進入吸收信息的狀況 */ //recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len); switch(p->ai_socktype) { case AF_INET : addr_len = sizeof host_v4; recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v4, &addr_len); break; case AF_INET6: addr_len = sizeof host_v6 recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v6, &addr_len); break; default: break; } freeaddrinfo(result); /* 釋放這個空間,由getaddrinfo分派的 */ /* 吸收完成,封閉套接字 */ close(sock);
代碼說明:
起首說明幾個新的構造體
struct addrinfo 這個構造體的外部次序關於 *nix 和 Windows 稍有分歧,以 *nix 為例
struct addrinfo{ int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr * ai_addr; /* 寄存成果地址的處所 */ char * ai_canonname; /* 疏忽它吧,很長一段時光你不必存眷它 */ struct addrinfo * ai_next; /* 一個域名/IP地址能夠解析出多個分歧的 IP */ };
ai_family 假如設定為 AF_UNSPEC 那末在挪用 getaddrinfo 時,會主動幫你肯定,傳入的地址是甚麼類型的
ai_flags 假如設定為 AI_PASSIVE 那末挪用 getaddrinfo 且向其第一個參數傳入 NULL 時會主動綁定本身 IP,相當於設定 INADDR_ANY
getaddrinfo 壯大的接口函數
int getaddrinfo(const char * node, const char * service,
const struct addrinfo * hints, struct addrinfo ** res);
淺顯的說這幾個參數的感化
node 就是待獲得或許待綁定的 域名 或是 IP,也就是說,這裡可以直接填寫域名,由操作體系來轉換成 IP 信息,或許直接填寫IP亦可,是以字符串的情勢
service 就是端標語的意思,也是字符串情勢
hints 淺顯的來講就是告知接口,我須要你反應哪些信息給我(第四個參數),並將這些信息填寫到第四個參數裡。
res 就是保留成果的處所,須要留意的是,這個成果在API外部是靜態分派內存了,所以應用完以後須要挪用另外一個接口(freeaddrinfo)將其釋放
現實上關於古代的 套接字編程 而言,多了幾個新的存儲 IP 信息的構造體,例如 struct sockaddr_in6 和 struct sockaddr_storage 等。
個中,前者是後者的年夜小上的子集,即一個 struct storage 必定可以或許裝下一個 struct sockaddr_in6,詳細(現實上基本看不到成心義的完成)
struct sockaddr_in6{ u_int16_t sin6_family; u_int16_t sin6_port; u_int32_t sin6_flowinfo; /* 臨時疏忽它 */ struct in6_addr sin6_addr; /* IPv6 的地址寄存在此構造體中 */ u_int32_t sin_scope_id; /* 臨時疏忽它 */ }; struct in6_addr{ unsigned char s6_addr[16]; } ------------------------------------------------------------ struct sockaddr_storage{ sa_family_t ss_family; /* 地址的品種 */ char __ss_pad1[_SS_PAD1SIZE]; /* 從此處開端,不是完成者簡直是沒方法懂得 */ int64_t __ss_align; /* 從名字上可以看出年夜概是為了兼容兩個分歧 IP 類型而做出的讓步 */ char __ss_pad2[_SS_PAD2SIZE]; /* 隱蔽了現實內容,除 IP 的品種之外,沒法直接獲得其他的任何信息。 */ /* 在各個*nix 的詳細完成中, 能夠有分歧的完成,例如 `__ss_pad1` , `__ss_pad2` , 能夠歸並成一個 `pad` 。 */ };
在現實中,我們常常不須要為分歧的IP類型聲明分歧的存儲類型,直接應用 struct sockaddr_storage 便可以,應用時直接強迫轉換類型便可
改寫上方 吸收端 例子中,進入吸收信息的狀況部門
/* 起首將多於的變量化簡 */ // - struct sockaddr_in host_v4; /* IPv4 地址 */ // - struct sockaddr_in6 host_v6; /* IPv6 地址 struct sockaddr_storage host_ver_any; /* + 隨意率性類型的 IP 地址 */ ... /* 進入吸收信息的狀況部門 */ recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_ver_any, &addr_len); /* 像是又回到了只要 IPv4 的年月*/
彌補完全上方對應的 發送端 代碼
int sock; const char* mess = "Hello Server!"; char get_mess[GET_MAX]; /* 後續版本應用 */ struct sockaddr_storage recv_host; /* - struct sockaddr_in recv_host; */ struct addrinfo tmp, *result; struct addrinfo *p; socklen_t addr_len; /* 獲得對真個信息 */ memset(&tmp, 0, sizeof tmp); tmp.ai_family = AF_UNSPEC; tmp.ai_flags = AI_PASSIVE; tmp.ai_socktype = SOCK_DGRAM; getaddrinfo(argv[1], argv[2], &tmp, &result); /* argv[1] 代表對真個 IP地址, argv[2] 代表對真個 端標語 */ /* 創立套接字 */ for(p = result; p != NULL; p = p->ai_next) { sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); /* - sock = socket(PF_INET, SOCK_DGRAM, 0); */ if(sock == -1) continue; /* 此處少了綁定 bind 函數,由於作為發送端不須要講對真個信息 綁定 到創立的套接字上。 */ break; /* 找到便可以加入了,固然也有能夠沒找到,那末此時 p 的值必定是 NULL */ } if(p == NULL) { /* 毛病處置 */ } /* -// 設定對端信息 memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); recv_host.sin_port = htons(6000); */ /* 發送信息 */ /* 在此處,發送真個IP地址和端標語等各類信息,跟著這個函數的挪用,主動綁定在了套接字上 */ sendto(sock, mess, strlen(mess), 0, p->ai_addr, p->ai_addrlen); /* 完成,封閉 */ freeaddrinfo(result); /* 現實上這個函數應當在應用完 result 的處所就予以挪用 */ close(sock);
到了此處,現實上是開了收集編程的一個初始,消除了古代的 UDP 最簡略的用法(乃至還算不上完全的應用),然則確切是停止了交互。
引見 UDP 其實不是由於它簡略,而是由於他簡練,也不是由於它不主要,相反他其實很壯大。
永久不要小視一個簡練的器械,就像 C說話
ARP 協定
最輕便的辦法就是找一個有 WireShark 軟件或許 tcpdump 的 *nix 平台,前者你可以選擇隨便監聽一個機械,不多時就可以看見 ARP 協定的應用,由於它應用的太頻仍了。
關於 ARP 協定而言,起首關於一台機械 A,想與 機械B 通訊,(假定此時 機械A 的高速緩存區(操作體系必定時光更新一次)中 沒有 機械B的緩存),
那末機械A就向播送地址收回 ARP要求,假如 機械B 收到了這個要求,就將本身的信息(IP地址,MAC地址)填入 ARP應對 中,再發送歸去就行。
上述中, ARP要求 和 ARP應對 是一種報文情勢的信息,是 ARP協定 所附帶的完成產物,也是用於兩台主機之間停止通訊。
這是當 機械A 和 機械B 同處於一個收集的情形下,可以借由本收集段的播送地址 發送要求報文。
關於分歧收集段的 機械A 與 機械B 而言,想要經由過程 ARP協定 獲得 MAC地址 ,就須要借助路由器的贊助了,可以想象一下,路由器(可以不止一個)在中央,機械A 和 機械B 分離在這些路由器的雙方(即在分歧子網)
因為 A 和 B 不在統一個子網內,所以沒方法經由過程經由過程直接經由過程播送達到,然則有了路由器,就可以停止 ARP署理 的操作,年夜概就是將路由器當做機械B, A向本身的當地路由器發送 ARP要求
以後路由器斷定出是發送給B的ARP要求,又正好 B 在本身的管轄規模以內,就把本身的硬件地址 寫入 ARP應對 中發還去,以後再有A向B 的數據,就都是A先發送給路由器,再經過路由器發往B了
ICMP協定
這個協定比擬主要。
要求應對報文 和 錯誤報文 ,重點在於錯誤報文。
要求應對報文在 ICMP 的運用中可以拿來查詢本機的子網掩碼之類的信息,年夜致經由過程向簿子網內的一切主機發送該要求報文(包含本身,現實上就是播送),後吸收應對,獲得信息
錯誤報文在後續中會有提到,這裡須要科普一二。
起首關於錯誤報文的一年夜部門是關於 xxx弗成達 的類型,例如主機弗成達,端口弗成達等等,每次湧現毛病的時刻,ICMP報文老是第一時光前往給對端,(它一次只會湧現一份,不然會形成收集風暴),然則對端能否可以或許吸收到,就不是發送真個成績了。
這點上 套接字的類型 有著必定的接洽,例如 UDP 在 unconnected 狀況下是會疏忽 ICMP報文的。而 TCP 由於老是 connected 的,所以關於 ICMP報文能很好的捕獲。
ICMP錯誤報文中老是帶著 失足數據報中的一部門真實數據,用於配對。