1. 網絡編程基本概念
1.1 什么是套接字
套接字,也叫,是操作系統內核中的一個數據結構,它是網絡中的節點進行相互通信的門戶。網絡通信,說白了就是進程間的通信(同一臺機器上不同進程或者不同計算機上的進程間通信)。
在網絡中,每一臺計算機或者路由都有一個網絡地址,就是IP地址。兩個進程通信時,首先要確定各自所在的網絡節點的網絡地址。但是,網絡地址只能確定進程所在的計算機,而一臺計算機上一般都是同時運行著多個進程,所以僅憑網絡地址還不能確定到底是和網絡中的哪一個進程進行通信,因此套接口中還需要包括其他的信息,比如端口號和協議。
1.2 端口號的概念
在網絡的世界里,端口大致有兩種:
端口號的范圍從0-65535linux網絡編程原始套接字的魔力下,分類如下:
1.3 ip地址的表示
通常我們在表達IP地址時習慣使用點分十進制表示的數值(或者是為冒號分開的十六進制Ipv6地址),而在編程中使用的則是二進制值,這就需要對這兩個數值進行轉換。
ipv4地址:32bit, 4字節,相當于一個整型,通常采用點分十進制記法,例如對于:11 , 點分十進制表示為:128.11.7.31。
2. 的概念
是一種特殊的I/O接口,它也是一種文件描述符。如第一節所說,通過它不僅能實現本地機器上的進程之間的通信,而且通過網絡能夠在不同機器上的進程之間進行通信。
也有一個類似于打開文件的函數調用,該函數返回一個整型的描述符,隨后的連接建立、數據傳輸等操作都是通過這個描述符來實現的。
2.1 類型
2.1.1 流式()
用于TCP通信,流式套接字提供可靠的、面向連接的通信流,使用TCP協議,從而保證了數據傳輸的正確性和順序性。
2.1.2 數據報()
用于UDP通信,數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且是不可靠的,它使用數據報協議UDP。
2.1.3 原始 ()
用于新的網絡協議實現的測試等,原始套接字允許對底層協議如IP或ICMP進行直接訪問,它功能強大但使用較為不便,主要用于一些自定義協議的開發。
2.2 信息數據結構
//頭文件 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,對于內存中存放的數來說:
端口號和IP地址都是以網絡字節序存儲的,不是主機字節序,網絡字節序都是大端模式linux網絡編程原始套接字的魔力下,而主機字節序則一般都是小端模式(也有特殊的是大端模式,這里不考慮)。所以在網絡連接過程中,要把主機字節序和網絡字節序相互對應起來,需要對這兩個字節存儲順序進行轉換。
這里用到四個函數:htons(),ntohs(),htonl()和ntohl().這四個函數分別實現網絡字節序和主機字節序的轉化,這里的h代表host,n代表,s代表short,l代表long。通常16位的IP端口號用s代表,而IP地址用l來代表。
#include
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 IP地址格式轉化
通常在表達地址時采用的是點分十進制表示的數值(或者是為冒號分開的十進制Ipv6地址),而在編程中使用的則是32位的網絡字節序的二進制值,這就需要對這兩個數值進行轉換。
這里在Ipv4中用到的函數有()、()和(),而IPV4和Ipv6兼容的函數有()和()。
2.4.1 IPv4的函數原型
#include
#include
#include
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);
函數():將點分十進制數的IP地址轉換成為網絡字節序的32位二進制數值。返回值:成功,則返回1,不成功返回0.
函數():將網絡字節序的32位二進制數值轉換為點分十進制的IP地址。
函數():功能與相同,但是結果傳遞的方式不同。()若成功則返回32位二進制的網絡字節序地址。
2.4.2 IPv4和IPv6兼容的函數原型
#include
int inet_pton(int family, const char *src, void *dst);
const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);
函數跟實現的功能類似,只是多了參數,該參數指定為,表示是IPv4協議,如果是,表示IPv6協議。
函數跟類似,其中len表示表示轉換之后的長度(字符串的長度)。
2.4.3 具體實現代碼
#include
#include
#include
#include
#include
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地址的對應關系
一般來講,我們在上網的過程中都不愿意記憶冗長的IP地址,尤其到Ipv6時,地址長度多達128位,那時就更加不可能一次性記憶那么長的IP地址了。我們一般記住的,都是這個網站的域名地址。
大家都知道,百度的域名為:,而這個域名其實對應了一個百度公司的IP地址,那么百度公司的IP地址是多少呢?
我們可以利用ping 來得到百度公司的ip地址。那么,系統是如何將 這個域名轉化為IP地址的呢?
在linux中,最常用的是()和(),它們都可以實現IPv4/IPv6的地址和主機名之間的轉化。其中()是將主機名轉化為IP地址,()則是逆操作,是將IP地址轉化為主機名。
函數原型:
#include
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地址*/
:
//test.cpp 將百度的www.baidu.com 轉換為ip地址
#include
#include
#include
#include
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
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