程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 解析C說話基於UDP協定停止Socket編程的要點

解析C說話基於UDP協定停止Socket編程的要點

編輯:關於C++

解析C說話基於UDP協定停止Socket編程的要點。本站提示廣大學習愛好者:(解析C說話基於UDP協定停止Socket編程的要點)文章只能為提供參考,不一定能成為您想要的結果。以下是解析C說話基於UDP協定停止Socket編程的要點正文


兩種協定 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有幾個選擇

  • * nix ** : SHUT_RD SHUT_WR SHUT_RDWR
  • Windows : SD_RECEIVE SD_SEND SD_BOTH


上面,有幾個構造體,和一個接口非常主要及經常使用:

  • struct sockaddr_in6 : 代表的是 IPv6 的地址信息
  • struct addrinfo : 這是一個通用的構造體,外面可以存儲 IPv4 或 IPv6 類型地址的信息
  • getaddrinfo : 這是一個非常便利的接口,在上述 UDP 法式中很多手動填寫的部門,都可以或許省去,有該函數替我們完成

改寫一下上方的例子:

吸收端:

  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

  • ai_socktype 就是要創立的套接字類型,這個必需明白聲明,體系沒法預判(往後人工智能說不定呢?)
  • ai_protocol 普通情形下我們設置為 0,寄義可以自行查找,例如 MSDN 或許 UNP
  • ai_addr 這裡保留著成果,可以經由過程 挪用getaddrinfo以後 的第四個參數取得。
  • ai_addrlen 同上
  • ai_next 同上

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錯誤報文中老是帶著 失足數據報中的一部門真實數據,用於配對。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved