linux網絡編程系列(二)--套接口、ip、端口號相關

1. 網絡相關概念

1.1 套接口的概念

套接口,也叫「套接字」。是操做系統內核中的一個數據結構,它是網絡中的節點進行相互通訊的門戶。網絡通訊,歸根到底仍是進程間的通訊(不一樣計算機上的進程間通訊)。在網絡中,每個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通訊時,首先要肯定各自所在的網絡節點的網絡地址。可是,網絡地址只能肯定進程所在的計算機,而一臺計算機上極可能同時運行着多個進程,因此僅憑網絡地址還不能肯定究竟是和網絡中的哪個進程進行通訊,所以套接口中還須要包括其餘的信息,也就是端口號(PORT)。在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應關係。因此,使用端口號和網絡地址的組合能夠惟一的肯定整個網絡中的一個網絡進程。 例如,如網絡中某一臺計算機的IP爲123.207.251.21,操做系統分配給計算機中某一應用程序進程的端口號爲1500,則此時 123.207.251.21:1500就構成了一個套接口。linux

1.2 端口號的概念

在網絡技術中,端口大體有兩種意思:編程

  • 一是物理意義上的端口,如集線器、交換機、路由器等用於鏈接其餘網絡設備的接口;
  • 二是指TCP/IP協議中的端口;

端口號的範圍從0-65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些經常使用的應用程序固定使用的「周知的端口」,其值通常爲0~1024,例如http的端口號是80,ftp爲21,ssh爲22,telnet爲23等;還有一類是用戶本身定義的,一般是大於1024的整型值。bash

1.3 ip地址的表示

一般用戶在表達IP地址時採用的是點分十進制表示的數值(或者是爲冒號分開的十六進制Ipv6地址),而在一般使用的socket編程中使用的則是二進制值,這就須要將這兩個數值進行轉換。 ipv4地址:32bit, 4字節,至關於一個整型,一般採用點分十進制記法。 例如對於:10000000 00001011 00000011 00011111, 點分十進制表示爲:128.11.3.31。網絡

2. socket概念

Linux中的網絡編程是經過socket接口來進行的。socket是一種特殊的I/O接口,它也是一種文件描述符。它是一種經常使用的進程之間通訊機制,經過它不只能實現本地機器上的進程之間的通訊,並且經過網絡可以在不一樣機器上的進程之間進行通訊。數據結構

每個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個相似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨後的鏈接創建、數據傳輸等操做都是經過socket來實現的。ssh

2.1 socket類型

2.1.1 流式socket(SOCK_STREAM)

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

2.1.2 數據報socket(SOCK_DGRAM)

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

2.1.3 原始socket (SOCK_RAW)

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

2.2 socket信息數據結構

//頭文件<netinet/in.h> sockaddr和sockaddr_in大小一致
struct sockaddr {
     unsigned short sa_family; /*地址族*/
     char sa_data[14]; /*14字節的協議地址,包含該socket的IP地址和端口號。*/
};
struct sockaddr_in {
     short int sa_family; /*地址族 AF_INET IPv4協議 AF_INET6 IPv6協議*/
     unsigned short int sin_port; /*端口號*/
     struct in_addr sin_addr; /*IP地址*/
     unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr一樣大小*/
};
struct in_addr {
unsigned long int s_addr; /* 32位IPv4地址,網絡字節序 */
};
複製代碼

2.3 數據存儲優先順序的轉換

計算機數據存儲有兩種字節優先順序:高位字節優先(稱爲大端模式)和低位字節優先(稱爲小端模式)。內存的低地址存儲數據的低字節,高地址存儲數據的高字節的方式叫小端模式。內存的高地址存儲數據的低字節,低地址存儲數據高字節的方式稱爲大端模式。 eg,對於內存中存放的數0x12345678來講:ui

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

若是稱某個系統所採用的字節序爲主機字節序,則它多是小端模式的,也多是大端模式的。而端口號和IP地址都是以網絡字節序存儲的,不是主機字節序,網絡字節序都是大端模式。要把主機字節序和網絡字節序相互對應起來,須要對這兩個字節存儲優先順序進行相互轉化。這裏用到四個函數:htons(),ntohs(),htonl()和ntohl().這四個地址分別實現網絡字節序和主機字節序的轉化,這裏的h表明host,n表明network,s表明short,l表明long。一般16位的IP端口號用s表明,而IP地址用l來表明。

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

2.4 地址格式轉化

一般用戶在表達地址時採用的是點分十進制表示的數值(或者是爲冒號分開的十進制Ipv6地址),而在一般使用的socket編程中使用的則是32位的網絡字節序的二進制值,這就須要將這兩個數值進行轉換。這裏在Ipv4中用到的函數有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函數有inet_pton()和inet_ntop()。

2.4.1 IPv4的函數原型
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *straddr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
in_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的函數原型
#include <arpa/inet.h>
int inet_pton(int family, const char *src, void *dst);
const 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 例子
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main() {
  char ip[] = "192.168.0.101";
  struct in_addr myaddr;
 memset((void*)&myaddr, 0, sizeof(struct in_addr));
 /* inet_aton */
 int iRet = inet_aton(ip, &myaddr);
 if ( iRet == 1)
 {
   printf("%ld\n", myaddr.s_addr);
   /* inet_addr */
   printf("%x\n", inet_addr(ip));
 }
 else
 {
  printf("call inet_aton failed\n");
 }

 /* inet_pton */
 iRet = inet_pton(AF_INET, ip, &myaddr);
 if ( iRet == 1 )
 {
  printf("%x\n", myaddr.s_addr);
 }
 else
 {
  printf("call inet pton failed\n");
 }
 myaddr.s_addr = 0xac100ac4;
 /* inet_ntoa */
 printf("%s\n", inet_ntoa(myaddr));
 /* inet_ntop */
 inet_ntop(AF_INET, &myaddr, ip, 16);
 puts(ip);
 return 0;
}
複製代碼

3. 名字地址轉化

一般,人們在使用過程當中都不肯意記憶冗長的IP地址,尤爲到Ipv6時,地址長度多達128位,那時就更加不可能一次性記憶那麼長的IP地址了。所以,使用主機名或域名將會是很好的選擇。主機名與域名的區別:主機名一般在局域網裏面使用,經過/etc/hosts文件,主機名能夠解析到對應的ip,域名一般是再internet上使用。

衆所周知,百度的域名爲:www.baidu.com,而這個域名其實對應了一個百度公司的IP地址,那麼百度公司的IP地址是多少呢?咱們能夠利用ping www.baidu.com來獲得百度公司的ip地址。那麼,系統是如何將www.baidu.com 這個域名轉化爲IP地址220.181.111.148的呢?

在linux中,有一些函數能夠實現主機名和地址的轉化,最多見的有gethostbyname()、gethostbyaddr()等,它們均可以實現IPv4和IPv6的地址和主機名之間的轉化。其中gethostbyname()是將主機名轉化爲IP地址,gethostbyaddr()則是逆操做,是將IP地址轉化爲主機名。

函數原型:

#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
結構體:
struct hostent {
     char *h_name; /*正式主機名*/
     char **h_aliases; /*主機別名*/
     int h_addrtype; /*主機IP地址類型 IPv4爲AF_INET*/
     int h_length; /*主機IP地址字節長度,對於IPv4是4字節,即32位*/
     char **h_addr_list; /*主機的IP地址列表*/
}
#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:

//test.cpp 將百度的www.baidu.com 轉換爲ip地址
#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char **argv) {
 char *ptr, **pptr;
 struct hostent *hptr = NULL;
 char str[32] = {0};
 if ( argc < 2 )
 {
  printf("please input an addr,eg:./a.out www.baidu.com\n");
  return 0;
 }
 /* 取得命令後第一個參數,即要解析的域名或主機名 */
 ptr = argv[1];
 /* 調用gethostbyname()。結果存在hptr結構中 */
 if((hptr = gethostbyname(ptr)) == NULL)
 {
  printf("gethostbyname error for host:%s\n", ptr);
  return 0;
 }
 else
 {
  /* 將主機的規範名打出來 */
  printf("official hostname:%s\n", hptr->h_name);
 }
 /* 主機可能有多個別名,將全部別名分別打出來 */
 for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
  printf("alias:%s\n", *pptr);
 /* 根據地址類型,將地址打出來 */
 switch(hptr->h_addrtype)
 {
  case AF_INET:
  case AF_INET6:
   pptr = hptr->h_addr_list;
   /* 將剛纔獲得的全部地址都打出來。其中調用了inet_ntop()函數 */
   for(; *pptr!=NULL; pptr++ )
   {
    printf("address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
   }
   printf("first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
   break;
  default:
   printf("unknown address type\n");
   break;
 }
 return 0;
}
複製代碼

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

official hostname:www.a.shifen.com
alias:www.baidu.com
address:14.215.177.39
address:14.215.177.38
first address: 14.215.177.39
複製代碼

原文首發自簡書:www.jianshu.com/p/2df8cdada…

相關文章
相關標籤/搜索