在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 0x12-套接字編程-2
0x0E-單線程備份(下)
0x11-套接字編程-1
0x05-C語言指針:(Volume-1)
0x13-套接字編程-HTTP服務器(1)
0x0C-開始行動
C 語言進階
第一部分
0x05-C語言指針(Volume-2)
0x08-C語言效率(下)
0x07-C語言效率(上)
0x04 C代碼規(guī)范
0x0F-多線程備份
0x05-C語言變量
第四部分
0x16-套接字編程-HTTP服務器(4)
0x0D-單線程備份(上)
總結
0x01-C語言序言
0x15-套接字編程-HTTP服務器(3)
0x14-套接字編程-HTTP服務器(2)
0x17-套接字編程-HTTP服務器(5)
第三部分
我的C語言
0x06-C語言預處理器
0x09-未曾領略的新風景
0x0A-C線程和Glib的視角
第二部分
0x10-網絡的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語言錯誤處理

0x12-套接字編程-2

0x12-套接字編程-2

新時代的 套接字網絡編程

  1. 首先有幾個結構體,以及一個接口十分重要及常用:
    • struct sockaddr_in6 : 代表的是 IPv6 的地址信息
    • struct addrinfo : 這是一個通用的結構體,里面可以存儲 IPv4 或 IPv6 類型地址的信息
    • getaddrinfo : 這是一個十分方便的接口,在上述 UDP 程序中許多手動填寫的部分,都能夠省去,有該函數替我們完成
  2. 改寫一下上方的例子:

    • 接收端:

          int sock; /* 套接字 */
          socklen_t addr_len; /* 發(fā)送端的地址長度,用于 recvfrom */
          char mess[15];
          char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
          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] 中存放字符串形式的 端口號 */
      
          /* 創(chuàng)建套接字,此處會產生兩種寫法,但更保險,可靠的寫法是如此 */
          /* 舊式方法
          *  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; /* 如果能執(zhí)行到此,證明建立套接字成功,套接字綁定成功,故不必再嘗試。 */
          }
      
          /* 進入接收信息的狀態(tài) */
          //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);
      • 代碼解釋:

        • 首先解釋幾個新的結構體

          1. struct addrinfo 這個結構體的內部順序對于 *nixWindows 稍有不同,以 *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 */
              };
          2. ai_family 如果設定為 AF_UNSPEC 那么在調用 getaddrinfo 時,會自動幫你確定,傳入的地址是什么類型的
          3. ai_flags 如果設定為 AI_PASSIVE 那么調用 getaddrinfo 且向其第一個參數傳入 NULL 時會自動綁定自身 IP,相當于設定 INADDR_ANY
          4. ai_socktype 就是要創(chuàng)建的套接字類型,這個必須明確聲明,系統(tǒng)沒法預判(日后人工智能說不定呢?)
          5. ai_protocol 一般情況下我們設置為 0,含義可以自行查找,例如 MSDN 或者 UNP
          6. ai_addr 這里保存著結果,可以通過 調用getaddrinfo之后第四個參數獲得。
          7. ai_addrlen 同上
          8. ai_next 同上
          9. getaddrinfo 強大的接口函數

              int getaddrinfo(const char * node, const char * service,
                                const struct addrinfo * hints, struct addrinfo ** res);
          10. 通俗的說這幾個參數的作用
          11. node 便是待獲取或者待綁定的 域名 或是 IP,也就是說,這里可以直接填寫域名,由操作系統(tǒng)來轉換成 IP 信息,或者直接填寫IP亦可,是以字符串的形式
          12. service 便是端口號的意思,也是字符串形式
          13. hints 通俗的來說就是告訴接口,我需要你反饋哪些信息給我(第四個參數),并將這些信息填寫到第四個參數里。
          14. res 便是保存結果的地方,需要注意的是,這個結果在API內部是動態(tài)分配內存了,所以使用完之后需要調用另一個接口(freeaddrinfo)將其釋放
          15. 實際上對于現代的 套接字編程 而言,多了幾個新的存儲 IP 信息的結構體,例如 struct sockaddr_in6struct 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 類型而做出的妥協(xié) */
                    char __ss_pad2[_SS_PAD2SIZE]; /* 隱藏了實際內容,除了 IP 的種類以外,無法直接獲取其他的任何信息。 */
                    /* 在各個*nix 的具體實現中, 可能有不同的實現,例如 `__ss_pad1` , `__ss_pad2` , 可能合并成一個 `pad` 。 */
                  };

              在實際中,我們往往不需要為不同的IP類型聲明不同的存儲類型,直接使用 struct sockaddr_storage 就可以,使用時直接強制轉換類型即可

          16. 改寫上方 接收端 例子中,進入接收信息的狀態(tài)部分

                /* 首先將多于的變量化簡 */
                // - struct sockaddr_in host_v4; /* IPv4 地址 */
                // - struct sockaddr_in6 host_v6; /* IPv6 地址
                struct sockaddr_storage host_ver_any; /* + 任意類型的 IP 地址 */
                ...
                /* 進入接收信息的狀態(tài)部分 */
                recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_ver_any, &addr_len); /* 像是又回到了只有 IPv4 的年代*/
          17. 補充完整上方對應的 發(fā)送端 代碼

                int sock;
                const char* mess = "Hello Server!";
                char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
                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] 代表對端的 端口號 */
            
                /* 創(chuàng)建套接字 */
                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 函數,因為作為發(fā)送端不需要講對端的信息 綁定 到創(chuàng)建的套接字上。 */  
                  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);
                */
            
                /* 發(fā)送信息 */
                /* 在此處,發(fā)送端的IP地址和端口號等各類信息,隨著這個函數的調用,自動綁定在了套接字上 */
                sendto(sock, mess, strlen(mess), 0, p->ai_addr, p->ai_addrlen);
                /* 完成,關閉 */
                freeaddrinfo(result); /* 實際上這個函數應該在使用完 result 的地方就予以調用 */
                close(sock);                
          18. 到了此處,實際上是開了網絡編程的一個初始,解除了現代的 UDP 最簡單的用法(甚至還算不上完整的使用),但是確實是進行了交互。

  • 首先介紹 UDP 并不是因為它簡單,而是因為他簡潔,也不是因為它不重要,相反他其實很強大。
  • 永遠不要小看一個簡潔的東西,就像 C語言
  • 下一篇將詳細記錄 UDP 的相關記錄

 在這之前

ARP 協(xié)議

  • 最簡便的方法就是找一個有 WireShark 軟件或者 tcpdump*nix 平臺,前者你可以選擇隨意監(jiān)聽一個機器,不多時就能看見 ARP 協(xié)議的使用,因為它使用的太頻繁了。
  • 對于 ARP 協(xié)議而言,首先對于一臺機器 A,想與 機器B 通信,(假設此時 機器A 的高速緩存區(qū)(操作系統(tǒng)一定時間更新一次)中 沒有 機器B的緩存),
    • 那么機器A就向廣播地址發(fā)出 ARP請求,如果 機器B 收到了這個請求,就將自己的信息(IP地址,MAC地址)填入 ARP應答 中,再發(fā)送回去就行。
    • 上述中, ARP請求ARP應答 是一種報文形式的信息,是 ARP協(xié)議 所附帶的實現產品,也是用于兩臺主機之間進行通信。
    • 這是當 機器A 和 機器B 同處于一個網絡的情況下,可以借由本網絡段的廣播地址 發(fā)送請求報文。
  • 對于不同網絡段的 機器A 與 機器B 而言,想要通過 ARP協(xié)議 獲取 MAC地址 ,就需要借助路由器的幫助了,可以想象一下,路由器(可以不止一個)在中間,機器A 和 機器B 分別在這些路由器的兩邊(即在不同子網)
    • 由于 A 和 B 不在同一個子網內,所以沒辦法通過通過直接通過廣播到達,但是有了路由器,就能進行 ARP代理 的操作,大概就是將路由器當成機器B, A向自己的本地路由器發(fā)送 ARP請求
    • 之后路由器判斷出是發(fā)送給B的ARP請求,又正好 B 在自己的管轄范圍之內,就把自己的硬件地址 寫入 ARP應答 中發(fā)回去,之后再有A向B 的數據,就都是A先發(fā)送給路由器,再經由路由器發(fā)往B了
    • 一篇比較好的資源是 Proxy ARP

      ICMP

  • 這個協(xié)議比較重要,后方的概念也會涉及。
    • 請求應答報文差錯報文 ,重點在于差錯報文。
    • 請求應答報文在 ICMP 的應用中可以拿來查詢本機的子網掩碼之類的信息,大致通過向本子網內的所有主機發(fā)送該請求報文(包括自己,實際上就是廣播),后接收應答,得到信息
    • 差錯報文在后續(xù)中會有提到,這里需要科普一二。
    • 首先對于差錯報文的一大部分是關于 xxx不可達 的類型,例如主機不可達,端口不可達等等,每次出現錯誤的時候,ICMP報文總是第一時間返回給對端,(它一次只會出現一份,否則會造成網絡風暴),但是對端是否能夠接收到,就不是發(fā)送端的問題了。
    • 這點上 套接字的類型 有著一定的聯(lián)系,例如 UDP 在 unconnected 狀態(tài)下是會忽略 ICMP報文的。而 TCP 因為總是 connected 的,所以對于 ICMP報文能很好的捕捉。
    • ICMP差錯報文中總是帶著 出錯數據報中的一部分真實數據,用于配對。

注意,對于UDP而言,只有connected狀態(tài)下,才會收到 ICMP 報文,可以通過errno == ECONNREFUSED來確定,具體來說就是在你發(fā)送完本次數據之后,的下一次系統(tǒng)調用時會有這個想想,代表你的小心沒有被送到對方手里,而是被對方丟棄了。

在沒有一個完備的思路以及良好的設計之前,UDP是一個十分艱難的挑戰(zhàn),只有在TCP實在無法滿足我們的性能需求時,我們才來重新考慮 UDP