linux網絡編程系列(二)

以前發的在公衆號上代碼閱讀體驗不佳,因此排版後從新發布。css

1. 網絡編程基本概念

1.1 什麼是套接字

套接字,也叫socket,是操做系統內核中的一個數據結構,它是網絡中的節點進行相互通訊的門戶。網絡通訊,說白了就是進程間的通訊(同一臺機器上不一樣進程或者不一樣計算機上的進程間通訊)。linux

在網絡中,每一臺計算機或者路由都有一個網絡地址,就是IP地址。兩個進程通訊時,首先要肯定各自所在的網絡節點的網絡地址。可是,網絡地址只能肯定進程所在的計算機,而一臺計算機上通常都是同時運行着多個進程,因此僅憑網絡地址還不能肯定究竟是和網絡中的哪個進程進行通訊,所以套接口中還須要包括其餘的信息,好比端口號和協議。web

1.2 端口號的概念

在網絡的世界裏,端口大體有兩種:編程

  • 一是物理意義上的端口,如交換機、路由器等用於鏈接其餘網絡設備的接口;微信

  • 二是指TCP/IP協議族中的端口號;網絡

端口號的範圍從0-65535,分類以下:數據結構

  • 一類是衆所周知的,公用的端口號,其值通常爲0~1024,例如http的端口號是80,ftp爲21,ssh爲22,telnet爲23等;app

  • 一類是用戶本身定義的,一般是大於1024而且小於65535的整型值;ssh

1.3 ip地址的表示

一般咱們在表達IP地址時習慣使用點分十進制表示的數值(或者是爲冒號分開的十六進制Ipv6地址),而在socket編程中使用的則是二進制值,這就須要對這兩個數值進行轉換。socket

ipv4地址:32bit, 4字節,至關於一個整型,一般採用點分十進制記法,例如對於:10000000 00001011 00000111 00011111, 點分十進制表示爲:128.11.7.31。

2. socket的概念

socket是一種特殊的I/O接口,它也是一種文件描述符。如第一節所說,經過它不只能實現本地機器上的進程之間的通訊,並且經過網絡可以在不一樣機器上的進程之間進行通訊。

  • 每個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;

  • 一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示;

socket也有一個相似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨後的鏈接創建、數據傳輸等操做都是經過這個socket描述符來實現的。

2.1 socket類型

2.1.1 流式socket(SOCK_STREAM)

用於TCP通訊,流式套接字提供可靠的、面向鏈接的通訊流,使用TCP協議,從而保證了數據傳輸的正確性和順序性。

2.1.2 數據報socket(SOCK_DGRAM)

用於UDP通訊,數據報套接字定義了一種無鏈接的服務,數據經過相互獨立的報文進行傳輸,是無序的,而且是不可靠的,它使用數據報協議UDP。

2.1.3 原始socket (SOCK_RAW)

用於新的網絡協議實現的測試等,原始套接字容許對底層協議如IP或ICMP進行直接訪問,它功能強大但使用較爲不便,主要用於一些自定義協議的開發。

2.2 socket信息數據結構

 1//頭文件<netinet/in.h> sockaddr和sockaddr_in大小一致
2struct sockaddr
3{

4     unsigned short sa_family; /*地址族*/
5     char sa_data[14]; /*14字節的協議地址,包含該socket的IP地址和端口號。*/
6};
7struct sockaddr_in
8{

9     short int sa_family; /*地址族 AF_INET IPv4協議 AF_INET6 IPv6協議*/
10     unsigned short int sin_port; /*端口號*/
11     struct in_addr sin_addr; /*IP地址*/
12     unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr一樣大小*/
13};
14struct in_addr
15{

16unsigned long int s_addr; /* 32位IPv4地址,網絡字節序 */
17};

2.3 數據存儲字節序的轉換

計算機數據存儲有兩種字節優先順序:高位字節優先(稱爲大端模式)和低位字節優先(稱爲小端模式)。

  • 內存的低地址存儲數據的低字節,高地址存儲數據的高字節的方式叫小端模式;

  • 內存的高地址存儲數據的低字節,低地址存儲數據高字節的方式稱爲大端模式;

eg,對於內存中存放的數0x12345678來講:

  • 若是是採用大端模式存放的,則其真實的數是:0x12345678;

  • 若是是採用小端模式存放的,則其真實的數是:0x78563412;

端口號和IP地址都是以網絡字節序存儲的,不是主機字節序,網絡字節序都是大端模式,而主機字節序則通常都是小端模式(也有特殊的是大端模式,這裏不考慮)。因此在網絡鏈接過程當中,要把主機字節序和網絡字節序相互對應起來,須要對這兩個字節存儲順序進行轉換。

這裏用到四個函數:htons(),ntohs(),htonl()和ntohl().這四個函數分別實現網絡字節序和主機字節序的轉化,這裏的h表明host,n表明network,s表明short,l表明long。一般16位的IP端口號用s表明,而IP地址用l來表明。

1#include <arpa/inet.h> 
2uint32_t htonl(uint32_t hostlong); //將主機的無符號長整型數轉換成網絡字節序
3uint16_t htons(uint16_t hostshort); //將主機的無符號短整形數轉換成網絡字節序
4uint32_t ntohl(uint32_t netlong); //將一個無符號長整型數從網絡字節序轉換爲主機字節序
5uint16_t ntohs(uint16_t netshort); //將一個無符號短整形數從網絡字節序轉換爲主機字節序

2.4 IP地址格式轉化

一般在表達地址時採用的是點分十進制表示的數值(或者是爲冒號分開的十進制Ipv6地址),而在socket編程中使用的則是32位的網絡字節序的二進制值,這就須要對這兩個數值進行轉換。

這裏在Ipv4中用到的函數有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函數有inet_pton()和inet_ntop()。

2.4.1 IPv4的函數原型
1#include <sys/socket.h>
2#include <netinet/in.h>
3#include <arpa/inet.h>
4int inet_aton(const char *straddr, struct in_addr *addrptr);
5char *inet_ntoa(struct in_addr inaddr);
6in_addr_t inet_addr(const char *straddr);

函數inet_aton():將點分十進制數的IP地址轉換成爲網絡字節序的32位二進制數值。返回值:成功,則返回1,不成功返回0.

  • 參數straddr:存放輸入的點分十進制數IP地址字符串。

  • 參數addrptr:傳出參數,保存網絡字節序的32位二進制數值。

函數inet_ntoa():將網絡字節序的32位二進制數值轉換爲點分十進制的IP地址。

函數inet_addr():功能與inet_aton相同,可是結果傳遞的方式不一樣。inet_addr()若成功則返回32位二進制的網絡字節序地址。

2.4.2 IPv4和IPv6兼容的函數原型
1#include <arpa/inet.h>
2int inet_pton(int family, const char *src, void *dst);
3const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);

函數inet_pton跟inet_aton實現的功能相似,只是多了family參數,該參數指定爲AF_INET,表示是IPv4協議,若是是AF_INET6,表示IPv6協議。

函數inet_ntop跟inet_ntoa相似,其中len表示表示轉換以後的長度(字符串的長度)。

2.4.3 具體實現代碼
 1#include <stdio.h>
2#include <sys/socket.h>
3#include <netinet/in.h>
4#include <arpa/inet.h>
5#include <string.h>
6int main()
7
{
8  char ip[] = "192.168.0.101";
9  struct in_addr myaddr;
10 memset((void*)&myaddr, 0sizeof(struct in_addr));
11 /* inet_aton */
12 int iRet = inet_aton(ip, &myaddr);
13 if ( iRet == 1)
14 {
15   printf("%ld\n", myaddr.s_addr);
16   /* inet_addr */
17   printf("%x\n", inet_addr(ip));
18 }
19 else
20 {
21  printf("call inet_aton failed\n");
22 }
23
24 /* inet_pton */
25 iRet = inet_pton(AF_INET, ip, &myaddr);
26 if ( iRet == 1 )
27 {
28  printf("%x\n", myaddr.s_addr);
29 }
30 else
31 {
32  printf("call inet pton failed\n");
33 }
34 myaddr.s_addr = 0xac100ac4;
35 /* inet_ntoa */
36 printf("%s\n", inet_ntoa(myaddr));
37 /* inet_ntop */
38 inet_ntop(AF_INET, &myaddr, ip, 16);
39 puts(ip);
40 return 0;
41}

3. 域名與IP地址的對應關係

通常來說,咱們在上網的過程當中都不肯意記憶冗長的IP地址,尤爲到Ipv6時,地址長度多達128位,那時就更加不可能一次性記憶那麼長的IP地址了。咱們通常記住的,都是這個網站的域名地址。

你們都知道,百度的域名爲:www.baidu.com,而這個域名其實對應了一個百度公司的IP地址,那麼百度公司的IP地址是多少呢?

咱們能夠利用ping www.baidu.com來獲得百度公司的ip地址。那麼,系統是如何將www.baidu.com 這個域名轉化爲IP地址的呢?

在linux中,最經常使用的是gethostbyname()和gethostbyaddr(),它們均可以實現IPv4/IPv6的地址和主機名之間的轉化。其中gethostbyname()是將主機名轉化爲IP地址,gethostbyaddr()則是逆操做,是將IP地址轉化爲主機名。

函數原型:

 1#include <netdb.h>
2struct hostent* gethostbyname(const char* hostname);
3struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
4結構體:
5struct hostent
6{

7     char *h_name; /*正式主機名*/
8     char **h_aliases; /*主機別名*/
9     int h_addrtype; /*主機IP地址類型 IPv4爲AF_INET*/
10     int h_length; /*主機IP地址字節長度,對於IPv4是4字節,即32位*/
11     char **h_addr_list; /*主機的IP地址列表*/
12}
13#define h_addr h_addr_list[0] /*保存的是ip地址*/
  • gethostbyname():用於將域名(www.baidu.com)或主機名轉換爲IP地址。參數hostname指向存放域名或主機名的字符串。

  • gethostbyaddr():用於將IP地址轉換爲域名或主機名。參數addr是一個IP地址,此時這個ip地址不是普通的字符串,而是要經過函數inet_aton()轉換。len爲IP地址的長度,AF_INET爲4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。

Example:

 1//test.cpp 將百度的www.baidu.com 轉換爲ip地址
2#include <netdb.h>
3#include <sys/socket.h>
4#include <stdio.h>
5#include <arpa/inet.h>
6int main(int argc, char **argv)
7
{
8 char *ptr, **pptr;
9 struct hostent *hptr = NULL;
10 char str[32] = {0};
11 if ( argc < 2 )
12 {
13  printf("please input an addr,eg:./a.out www.baidu.com\n");
14  return 0;
15 }
16 /* 取得命令後第一個參數,即要解析的域名或主機名 */
17 ptr = argv[1];
18 /* 調用gethostbyname()。結果存在hptr結構中 */
19 if((hptr = gethostbyname(ptr)) == NULL)
20 {
21  printf("gethostbyname error for host:%s\n", ptr);
22  return 0;
23 }
24 else
25 {
26  /* 將主機的規範名打出來 */
27  printf("official hostname:%s\n", hptr->h_name);
28 }
29 /* 主機可能有多個別名,將全部別名分別打出來 */
30 for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
31  printf("alias:%s\n", *pptr);
32 /* 根據地址類型,將地址打出來 */
33 switch(hptr->h_addrtype)
34 {
35  case AF_INET:
36  case AF_INET6:
37   pptr = hptr->h_addr_list;
38   /* 將剛纔獲得的全部地址都打出來。其中調用了inet_ntop()函數 */
39   for(; *pptr!=NULL; pptr++ )
40   {
41    printf("address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
42   }
43   printf("first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
44   break;
45  default:
46   printf("unknown address type\n");
47   break;
48 }
49 return 0;
50}

編譯運行
g++ test.cpp
./a.out www.baidu.com

1official hostname:www.a.shifen.com
2alias:www.baidu.com
3address:14.215.177.39
4address:14.215.177.38
5first address: 14.215.177.39


本文分享自微信公衆號 - cpp加油站(xy13640954449)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索