
1. 網(wǎng)絡(luò)相關(guān)概念
1.1 套接口的概念
套接口,也叫“套接字”。是操作系統(tǒng)內(nèi)核中的一個(gè)數(shù)據(jù)結(jié)構(gòu),它是網(wǎng)絡(luò)中的節(jié)點(diǎn)進(jìn)行相互通信的門戶。網(wǎng)絡(luò)通信,歸根到底還是進(jìn)程間的通信(不同計(jì)算機(jī)上的進(jìn)程間通信)。在網(wǎng)絡(luò)中,每一個(gè)節(jié)點(diǎn)(計(jì)算機(jī)或路由)都有一個(gè)網(wǎng)絡(luò)地址,也就是IP地址。兩個(gè)進(jìn)程通信時(shí),首先要確定各自所在的網(wǎng)絡(luò)節(jié)點(diǎn)的網(wǎng)絡(luò)地址。但是,網(wǎng)絡(luò)地址只能確定進(jìn)程所在的計(jì)算機(jī),而一臺(tái)計(jì)算機(jī)上很可能同時(shí)運(yùn)行著多個(gè)進(jìn)程,所以僅憑網(wǎng)絡(luò)地址還不能確定到底是和網(wǎng)絡(luò)中的哪一個(gè)進(jìn)程進(jìn)行通信,因此套接口中還需要包括其他的信息,也就是端口號(hào)(PORT)。在一臺(tái)計(jì)算機(jī)中,一個(gè)端口號(hào)一次只能分配給一個(gè)進(jìn)程,也就是說(shuō),在一臺(tái)計(jì)算機(jī)中,端口號(hào)和進(jìn)程之間是一一對(duì)應(yīng)關(guān)系。所以,使用端口號(hào)和網(wǎng)絡(luò)地址的組合可以唯一的確定整個(gè)網(wǎng)絡(luò)中的一個(gè)網(wǎng)絡(luò)進(jìn)程。
例如,如網(wǎng)絡(luò)中某一臺(tái)計(jì)算機(jī)的IP為123.207.251.21,操作系統(tǒng)分配給計(jì)算機(jī)中某一應(yīng)用程序進(jìn)程的端口號(hào)為1500,則此時(shí) 123.207.251.21:1500就構(gòu)成了一個(gè)套接口。
1.2 端口號(hào)的概念
在網(wǎng)絡(luò)技術(shù)中,端口大致有兩種意思:
- 一是物理意義上的端口,如集線器、交換機(jī)、路由器等用于連接其他網(wǎng)絡(luò)設(shè)備的接口;
- 二是指TCP/IP協(xié)議中的端口;
端口號(hào)的范圍從0-65535,一類是由互聯(lián)網(wǎng)指派名字和號(hào)碼公司ICANN負(fù)責(zé)分配給一些常用的應(yīng)用程序固定使用的“周知的端口”,其值一般為0~1024,例如http的端口號(hào)是80,ftp為21,ssh為22,telnet為23等;還有一類是用戶自己定義的,通常是大于1024的整型值。
1.3 ip地址的表示
通常用戶在表達(dá)IP地址時(shí)采用的是點(diǎn)分十進(jìn)制表示的數(shù)值(或者是為冒號(hào)分開的十六進(jìn)制Ipv6地址),而在通常使用的socket編程中使用的則是二進(jìn)制值,這就需要將這兩個(gè)數(shù)值進(jìn)行轉(zhuǎn)換。
ipv4地址:32bit, 4字節(jié),相當(dāng)于一個(gè)整型,通常采用點(diǎn)分十進(jìn)制記法。
例如對(duì)于:10000000 00001011 00000011 00011111, 點(diǎn)分十進(jìn)制表示為:128.11.3.31。
2. socket概念
Linux中的網(wǎng)絡(luò)編程是通過(guò)socket接口來(lái)進(jìn)行的。socket是一種特殊的I/O接口,它也是一種文件描述符。它是一種常用的進(jìn)程之間通信機(jī)制,通過(guò)它不僅能實(shí)現(xiàn)本地機(jī)器上的進(jìn)程之間的通信,而且通過(guò)網(wǎng)絡(luò)能夠在不同機(jī)器上的進(jìn)程之間進(jìn)行通信。
每一個(gè)socket都用一個(gè)半相關(guān)描述{協(xié)議、本地地址、本地端口}來(lái)表示;一個(gè)完整的套接字則用一個(gè)相關(guān)描述{協(xié)議、本地地址、本地端口、遠(yuǎn)程地址、遠(yuǎn)程端口}來(lái)表示。socket也有一個(gè)類似于打開文件的函數(shù)調(diào)用,該函數(shù)返回一個(gè)整型的socket描述符,隨后的連接建立、數(shù)據(jù)傳輸?shù)炔僮鞫际峭ㄟ^(guò)socket來(lái)實(shí)現(xiàn)的。
2.1 socket類型
2.1.1 流式socket(SOCK_STREAM)
用于TCP通信,流式套接字提供可靠的、面向連接的通信流;它使用TCP協(xié)議,從而保證了數(shù)據(jù)傳輸?shù)恼_性和順序性。
2.1.2 數(shù)據(jù)報(bào)socket(SOCK_DGRAM)
用于UDP通信,數(shù)據(jù)報(bào)套接字定義了一種無(wú)連接的服務(wù),數(shù)據(jù)通過(guò)相互獨(dú)立的報(bào)文進(jìn)行傳輸,是無(wú)序的,并且不保證是可靠、無(wú)差錯(cuò)的。它使用數(shù)據(jù)報(bào)協(xié)議UDP。
2.1.3 原始socket (SOCK_RAW)
用于新的網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)的測(cè)試等,原始套接字允許對(duì)底層協(xié)議如IP或ICMP進(jìn)行直接訪問(wèn),它功能強(qiáng)大但使用較為不便,主要用于一些協(xié)議的開發(fā)。
2.2 socket信息數(shù)據(jù)結(jié)構(gòu)
//頭文件<netinet/in.h> sockaddr和sockaddr_in大小一致
struct sockaddr
{
unsigned short sa_family; /*地址族*/
char sa_data[14]; /*14字節(jié)的協(xié)議地址,包含該socket的IP地址和端口號(hào)。*/
};
struct sockaddr_in
{
short int sa_family; /*地址族 AF_INET IPv4協(xié)議 AF_INET6 IPv6協(xié)議*/
unsigned short int sin_port; /*端口號(hào)*/
struct in_addr sin_addr; /*IP地址*/
unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr同樣大小*/
};
struct in_addr
{
unsigned long int s_addr; /* 32位IPv4地址,網(wǎng)絡(luò)字節(jié)序 */
};
2.3 數(shù)據(jù)存儲(chǔ)優(yōu)先順序的轉(zhuǎn)換
計(jì)算機(jī)數(shù)據(jù)存儲(chǔ)有兩種字節(jié)優(yōu)先順序:高位字節(jié)優(yōu)先(稱為大端模式)和低位字節(jié)優(yōu)先(稱為小端模式)。內(nèi)存的低地址存儲(chǔ)數(shù)據(jù)的低字節(jié),高地址存儲(chǔ)數(shù)據(jù)的高字節(jié)的方式叫小端模式。內(nèi)存的高地址存儲(chǔ)數(shù)據(jù)的低字節(jié),低地址存儲(chǔ)數(shù)據(jù)高字節(jié)的方式稱為大端模式。
eg,對(duì)于內(nèi)存中存放的數(shù)0x12345678來(lái)說(shuō):
- 如果是采用大端模式存放的,則其真實(shí)的數(shù)是:0x12345678;
- 如果是采用小端模式存放的,則其真實(shí)的數(shù)是:0x78563412;
如果稱某個(gè)系統(tǒng)所采用的字節(jié)序?yàn)橹鳈C(jī)字節(jié)序,則它可能是小端模式的,也可能是大端模式的。而端口號(hào)和IP地址都是以網(wǎng)絡(luò)字節(jié)序存儲(chǔ)的,不是主機(jī)字節(jié)序,網(wǎng)絡(luò)字節(jié)序都是大端模式。要把主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序相互對(duì)應(yīng)起來(lái),需要對(duì)這兩個(gè)字節(jié)存儲(chǔ)優(yōu)先順序進(jìn)行相互轉(zhuǎn)化。這里用到四個(gè)函數(shù):htons(),ntohs(),htonl()和ntohl().這四個(gè)地址分別實(shí)現(xiàn)網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)化,這里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口號(hào)用s代表,而IP地址用l來(lái)代表。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //將主機(jī)的無(wú)符號(hào)長(zhǎng)整型數(shù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序
uint16_t htons(uint16_t hostshort); //將主機(jī)的無(wú)符號(hào)短整形數(shù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序
uint32_t ntohl(uint32_t netlong); //將一個(gè)無(wú)符號(hào)長(zhǎng)整型數(shù)從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序
uint16_t ntohs(uint16_t netshort); //將一個(gè)無(wú)符號(hào)短整形數(shù)從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序
2.4 地址格式轉(zhuǎn)化
通常用戶在表達(dá)地址時(shí)采用的是點(diǎn)分十進(jìn)制表示的數(shù)值(或者是為冒號(hào)分開的十進(jìn)制Ipv6地址),而在通常使用的socket編程中使用的則是32位的網(wǎng)絡(luò)字節(jié)序的二進(jìn)制值,這就需要將這兩個(gè)數(shù)值進(jìn)行轉(zhuǎn)換。這里在Ipv4中用到的函數(shù)有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函數(shù)有inet_pton()和inet_ntop()。
2.4.1 IPv4的函數(shù)原型
#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);
函數(shù)inet_aton():將點(diǎn)分十進(jìn)制數(shù)的IP地址轉(zhuǎn)換成為網(wǎng)絡(luò)字節(jié)序的32位二進(jìn)制數(shù)值。返回值:成功,則返回1,不成功返回0.
參數(shù)straddr:存放輸入的點(diǎn)分十進(jìn)制數(shù)IP地址字符串。
參數(shù)addrptr:傳出參數(shù),保存網(wǎng)絡(luò)字節(jié)序的32位二進(jìn)制數(shù)值。
函數(shù)inet_ntoa():將網(wǎng)絡(luò)字節(jié)序的32位二進(jìn)制數(shù)值轉(zhuǎn)換為點(diǎn)分十進(jìn)制的IP地址。
函數(shù)inet_addr():功能與inet_aton相同,但是結(jié)果傳遞的方式不同。inet_addr()若成功則返回32位二進(jìn)制的網(wǎng)絡(luò)字節(jié)序地址。
2.4.2 IPv4和IPv6的函數(shù)原型
#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);
函數(shù)inet_pton跟inet_aton實(shí)現(xiàn)的功能類似,只是多了family參數(shù),該參數(shù)指定為AF_INET,表示是IPv4協(xié)議,如果是AF_INET6,表示IPv6協(xié)議。
函數(shù)inet_ntop跟inet_ntoa類似,其中l(wèi)en表示表示轉(zhuǎn)換之后的長(zhǎng)度(字符串的長(zhǎng)度)。
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. 名字地址轉(zhuǎn)化
通常,人們?cè)谑褂眠^(guò)程中都不愿意記憶冗長(zhǎng)的IP地址,尤其到Ipv6時(shí),地址長(zhǎng)度多達(dá)128位,那時(shí)就更加不可能一次性記憶那么長(zhǎng)的IP地址了。因此,使用主機(jī)名或域名將會(huì)是很好的選擇。主機(jī)名與域名的區(qū)別:主機(jī)名通常在局域網(wǎng)里面使用,通過(guò)/etc/hosts文件,主機(jī)名可以解析到對(duì)應(yīng)的ip,域名通常是再internet上使用。
眾所周知,百度的域名為:www.baidu.com,而這個(gè)域名其實(shí)對(duì)應(yīng)了一個(gè)百度公司的IP地址,那么百度公司的IP地址是多少呢?我們可以利用ping www.baidu.com來(lái)得到百度公司的ip地址。那么,系統(tǒng)是如何將www.baidu.com 這個(gè)域名轉(zhuǎn)化為IP地址220.181.111.148的呢?
在linux中,有一些函數(shù)可以實(shí)現(xiàn)主機(jī)名和地址的轉(zhuǎn)化,最常見的有g(shù)ethostbyname()、gethostbyaddr()等,它們都可以實(shí)現(xiàn)IPv4和IPv6的地址和主機(jī)名之間的轉(zhuǎn)化。其中g(shù)ethostbyname()是將主機(jī)名轉(zhuǎn)化為IP地址,gethostbyaddr()則是逆操作,是將IP地址轉(zhuǎn)化為主機(jī)名。
函數(shù)原型:
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
結(jié)構(gòu)體:
struct hostent
{
char *h_name; /*正式主機(jī)名*/
char **h_aliases; /*主機(jī)別名*/
int h_addrtype; /*主機(jī)IP地址類型 IPv4為AF_INET*/
int h_length; /*主機(jī)IP地址字節(jié)長(zhǎng)度,對(duì)于IPv4是4字節(jié),即32位*/
char **h_addr_list; /*主機(jī)的IP地址列表*/
}
#define h_addr h_addr_list[0] /*保存的是ip地址*/
- gethostbyname():用于將域名(www.baidu.com)或主機(jī)名轉(zhuǎn)換為IP地址。參數(shù)hostname指向存放域名或主機(jī)名的字符串。
- gethostbyaddr():用于將IP地址轉(zhuǎn)換為域名或主機(jī)名。參數(shù)addr是一個(gè)IP地址,此時(shí)這個(gè)ip地址不是普通的字符串,而是要通過(guò)函數(shù)inet_aton()轉(zhuǎn)換。len為IP地址的長(zhǎng)度,AF_INET為4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。
Example:
//test.cpp 將百度的www.baidu.com 轉(zhuǎn)換為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;
}
/* 取得命令后第一個(gè)參數(shù),即要解析的域名或主機(jī)名 */
ptr = argv[1];
/* 調(diào)用gethostbyname()。結(jié)果存在hptr結(jié)構(gòu)中 */
if((hptr = gethostbyname(ptr)) == NULL)
{
printf("gethostbyname error for host:%s\n", ptr);
return 0;
}
else
{
/* 將主機(jī)的規(guī)范名打出來(lái) */
printf("official hostname:%s\n", hptr->h_name);
}
/* 主機(jī)可能有多個(gè)別名,將所有別名分別打出來(lái) */
for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
printf("alias:%s\n", *pptr);
/* 根據(jù)地址類型,將地址打出來(lái) */
switch(hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
pptr = hptr->h_addr_list;
/* 將剛才得到的所有地址都打出來(lái)。其中調(diào)用了inet_ntop()函數(shù) */
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;
}
編譯運(yùn)行
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
本人在簡(jiǎn)書上寫的內(nèi)容均為本人原創(chuàng),轉(zhuǎn)載需經(jīng)本人同意,歡迎轉(zhuǎn)載分享,請(qǐng)注明出處。