Linux網路程式設計筆記——第八章 伺服器和客戶端資訊的獲取
技術標籤:Linux
目錄
一,位元組序
位元組序是由於不同的主處理器和作業系統,對大於一個位元組的變數在記憶體中的存放順序不同而產生的。位元組序通常有大端位元組序和小端位元組序兩種分類
1,大端位元組序和小端位元組序
位元組序是由於CPU和OS對多位元組變數的記憶體儲存順序不同而產生的。
1.1 位元組序介紹
例如一個16位的整數,它由兩個位元組構成,在有的系統上會將高位元組放在記憶體的低地址上,而有的系統上則將高位元組放在記憶體的高地址上,所以存在位元組序的問題。
- 小端位元組序(Little Endian,LE):在表示變數的記憶體的起始地址存放低位元組,高位元組順序存放。低位元組在低地址,高位元組在高地址。
- 大端位元組序(Big Endian,BE):在表示變數的記憶體地址的起始地址存放高位元組,低位元組順序存放。低位元組在高地址,高位元組在低地址。
1.2 位元組序的例子
#include <stdio.h> typedef union { unsigned short value; unsigned char byte[2]; }to; int main() { to typeorder; typeorder.value = 0xabcd; //小端位元組序檢查 if ((typeorder.byte[0] == 0xcd) && (typeorder.byte[1] == 0xab)) { printf("Little Eadian byte order, byte[0]:0x%x, byte[1]:0x%x\n", typeorder.byte[0], typeorder.byte[1]); } //大端位元組序檢查 if ((typeorder.byte[0] == 0xab) && (typeorder.byte[1] == 0xcd)) { printf("Big Eadian byte order, byte[0]:0x%x, byte[1]:0x%x\n", typeorder.byte[0], typeorder.byte[1]); } return 0; }
2,位元組序轉換函式
網路位元組序是指多位元組變數在網路傳輸時的表示方法,網路位元組序採用大端位元組序的表示方法。這樣小端位元組序的系統通過網路傳輸變數的時候,需要進行位元組序的轉換,大端位元組序的變數則不需要進行轉換。
2.1 位元組序轉換函式
#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); //網路位元組序到主機位元組序的短整型轉換
函式傳入的變數位需要轉換的變數,返回值為轉換後的值
3,位元組序轉換的例子
該例子是對16位數值和32位數值進行自己序轉換,每種型別的數值進行兩次轉換,最後列印結果。
#include <stdio.h>
#include <arpa/inet.h>
#define BITS16 16
#define BITS32 32
//16位位元組序轉換的結構
typedef union {
unsigned short value;
unsigned char byte[2];
}to16;
//32位位元組序轉換的結構
typedef union{
unsigned int value;
unsigned char byte[4];
}to32;
void showValue(unsigned char *begin, int flag)
{
int num = 0;
int i = 0;
if (flag == BITS16)
{
num = 2;
}
else if(flag == BITS32)
{
num = 4;
}
for (i=0; i<num; i++)
{
printf("%x ", *(begin+i));
}
printf("\n");
}
int main(int argc, char *argv[])
{
to16 v16_orig, v16_turn1, v16_turn2;
to32 v32_orig, v32_turn1, v32_turn2;
v16_orig.value = 0xabcd;
v16_turn1.value = htons(v16_orig.value); //第一次轉換
v16_turn2.value = htons(v16_turn1.value); //第二次轉換
v32_orig.value = 0x12345678;
v32_turn1.value = htonl(v32_orig.value); //第一次轉換
v32_turn2.value = htonl(v32_turn1.value); //第二次轉換
printf("16 host to network byte order change\n");
printf("\torig\t");
showValue(v16_orig.byte, BITS16);
printf("\t1 times:");
showValue(v16_turn1.byte, BITS16);
printf("\t2 times:");
showValue(v16_turn2.byte, BITS16);
printf("32 host to network byte order change\n");
printf("\torig\t");
showValue(v32_orig.byte, BITS32);
printf("\t1 times:");
showValue(v32_turn1.byte, BITS32);
printf("\t2 times:");
showValue(v32_turn2.byte, BITS32);
return 0;
}
二,字串IP地址和二進位制IP地址的轉換
1,inet_xxx()函式
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp); //將點分四段式的IP地址轉為地址結構in_addr值
in_addr_t inet_addr(const char *cp); //將字串轉換為in_addr值
in_addr_t inet_network(const char *cp); //字串地址的網路部分轉為in_addr型別
char *inet_ntoa(struct in_addr in); //將in_addr結構地址轉為字串
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host); //將網路地址和主機地址合成為IP地址
in_addr_t inet_lnaof(struct in_addr in); //獲得地址的主機部分
in_addr_t inet_netof(struct in_addr in); //獲得地址的網路部分
1.1 inet_aton()函式
inet_aton()函式將在cp中儲存的點分十進位制字串型別的IP地址,轉換為二進位制的IP地址,轉換後的值儲存在指標inp指向的結構struct in_addr中。當轉換成功時返回值為非0,當傳入的地址非法時,返回值為0。
1.2 inet_addr()函式
inet_addr()函式將cp中儲存的點分十進位制字串型別的IP地址轉換為二進位制的IP地址,IP地址是以網路位元組序表達的。如果輸入的引數非法,返回值為INADDR_NONE(通常為-1),否則返回為轉換後的IP地址。
不能使用該函式轉換IP地址255.255.255.255
1.3 inet_network()函式
inet_network()函式將cp中儲存的點分十進位制字串型別的IP地址,轉換為二進位制的IP地址,IP地址是以網路位元組序表達的。當成功時返回32位表示IP地址,失敗時返回值為-1。
1.4 inet_ntoa()函式
inet_ntoa()函式將一個引數in所表示的Internet地址結構轉換為點分十進位制的4段式字串IP地址,其形式為a.b.c.d。返回值為轉換後的字串指標,此記憶體區域為靜態的,有可能會被覆蓋,因此函式並不是執行緒安全的。
1.5 inet_makeaddr()函式
一個主機的IP地址分為網路地址和主機地址,inet_makeaddr()函式將主機位元組序的網路地址net和主機地址host合併成一個網路位元組序的IP地址。
1.6 inet_lnaof()函式
inet_lnaof()函式返回IP地址的主機部分
1.7 inet_netof()函式
inet_netof()函式返回IP地址的網路部分
2,inet_pton和inet_ntop()函式
inet_pton和inet_ntop()函式是一套安全的協議無關的地址轉換函式。所謂的“安全”是對於inet_aton()函式的不可重入性來說。這兩個函式都是可以重入的,並且這些函式支援多種地址型別,包括IPv4和IPv6
2.1 inet_pton()函式
該函式將字串型別的IP地址轉換為二進位制型別
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
- af:表示網路型別的協議族,在IPv4下的值為AF_INET
- src:表示需要轉換的字串
- dst:指向轉換後的結果,在IPv4下,dst指向結構struct in_addr的指標
- 返回值:當函式的返回值為-1時,通常是由於af所指定的協議族不支援造成的,當函式的返回值為0時,表示src指向的值不是合法的IP地址,但函式的返回值為正值時,表示轉換成功
2.2 inet_ntop()函式
該函式將二進位制的網路IP地址轉換為字串
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- af:表示網路型別的協議族,在IPv4下的值為AF_INET
- src:表示需要轉換的二進位制IP地址,在IPv4下,src指向結構struct in_addr的指標
- dst:指向儲存結果緩衝區的指標
- cnt:dst緩衝區的大小
- 返回值:inet_ntop()函式返回一個指向dst的指標。當發生錯誤時,返回NULL。當af設定的協議族不支援時,errno設定為EAFNOSUPPORT;當dsf緩衝區過小的時候error的值為ENOSPC
3,inet_pton()和inet_ntop()的例子
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#define ADDRLEN 16
int main(int argc, char *argv[])
{
struct in_addr ip;
char IPSTR[] = "192.168.1.1"; //網路地址字串
char addr[ADDRLEN]; //儲存轉換後的IP地址
const char* str = NULL;
int err = 0;
//測試函式inet_pton
err = inet_pton(AF_INET, IPSTR, &ip);
if (err > 0)
{
printf("inet_pton:ip, %s value is:0x%x\n", IPSTR, ip.s_addr);
}
//測試函式inet_ntop
ip.s_addr = htonl(192<<24|168<<16|12<<8|255); //192.168.12.255
str = (const char*)inet_ntop(AF_INET, (void*)&ip, (char*)&addr[0], ADDRLEN);
if (str)
{
printf("inet_ntop:ip, 0x%x is %s\n", ip.s_addr, str);
}
return 0;
}
三,套接字描述符判定函式issockettype()
套接字檔案描述符從形式上與通用檔案描述符沒有區別,判斷一個檔案描述符是否是一個套接字描述符可以通過如下的方法實現:先呼叫函式fstat()獲得檔案描述符的模式,然後將模式的S_IFMT部分與識別符號S_IFSOCK比較,就可以知道一個檔案描述符是否為套接字描述符
1,套接字檔案描述符判定的例子
issockettype()函式先獲得描述符的狀態,儲存在變數st中,將st的成員st_mode與S_IFMT進行與運算後獲取檔案描述符的模式。判斷上述值是否與S_IFSOCK相等,就可以知道檔案描述符是否為套接字檔案描述符
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
int issockettype(int fd)
{
struct stat st;
int err = fstat(fd, &st); //獲得檔案狀態
if (err < 0)
{
return -1;
}
if ((st.st_mode & S_IFMT) == S_IFSOCK) //比較是否套接字描述符
{
return 1;
}
else
{
return 0;
}
}
int main(int argc, char *argv[])
{
int ret = issockettype(0); //標準輸入
printf("value %d\n", ret);
int s = socket(AF_INET, SOCK_STREAM, 0); //套接字描述符
ret = issockettype(s);
printf("value %d\n", ret);
return 0;
}
四,IP地址與域名之間的相互轉換
在實際的應用中,經常有隻知道主機的域名而不知道主機名對應的IP地址的情況,而socket的API均為基於IP地址,所以如何進行主機域名和IP地址之間的轉換是十分必要的。
1,DNS原理
DNS是"域名系統"的英文縮寫,域名系統是一種樹形結構,按照區域組成層次性的結構,表示計算機名稱和IP地址的對應情況。DNS用於TCP/IP的網路,用比較形象話的友好命名來代替枯燥的IP地址,方便使用者記憶,DNS的功能就是在主機的名稱和IP地址之間擔任翻譯工作。
2,獲取主機資訊的函式
gethostbyname()函式和gethostbyaddr()函式都可以獲得主機的資訊。gethostbyname()函式通過主機的名稱獲得主機的資訊,gethostbyaddr()函式通過IP地址獲得主機的資訊
2.1 gethostbyname()函式
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
- name:要查詢的主機名,通常是DNS的域名
- 返回值:一個指向結構struct hostname型別變數的指標,當為NULL時,表示發生錯誤
2.2 gethostbyaddr()函式
#include <netdb.h>
#include <sys/socket.h>
struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
- addr:在IPv4的情況下指向一個struct in_addr的地址結構,使用者需要查詢主機的IP地址填入到這個引數中
- len:表示第一個引數所指區域的大小,在IPv4情況下為sizeof(struct in_addr),即32位
- type:指定需要查詢主機IP地址的型別,在IPv4的情況下為AF_INET
- 返回值:和gethostbyname()相同
gethostbyname()和gethostbyaddr()函式都是不可重入的,函式返回後,要馬上將結果取出,否則會被後面的函式呼叫過程覆蓋
3,使用主機名獲取主機資訊的例子
查詢www.baidu.com的資訊,並將主機的資訊打印出來
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char host[] = "www.baidu.com";
struct hostent *ht = NULL;
char str[30];
ht = gethostbyname(host);
if (ht)
{
int i = 0;
printf("get the host:%s addr\n", host); //原始域名
printf("name:%s\n", ht->h_name); //名稱
printf("type:%s\n", ht->h_addrtype==AF_INET?"AF_INET":"AF_INET6");
printf("length:%d\n", ht->h_length); //IP地址的長度
//列印IP地址
for(i=0;; i++)
{
if(ht->h_addr_list[i] != NULL)
{
printf("IP:%s\n", inet_ntop(ht->h_addrtype, ht->h_addr_list[i], str, 30));
}
else
{
break;
}
}
//列印域名地址
for(i=0;; i++)
{
if(ht->h_aliases[i] != NULL)
{
printf("alias %d:%s\n", i, ht->h_aliases[i]);
}
else
{
break;
}
}
}
return 0;
}
五,協議名稱處理函式。
為了方便操作,Linux提供了一組用於查詢協議的值及名稱的函式
1,xxxprotoxxx()函式
#include <netdb.h>
struct protoent *getprotoent(void); //從協議文件中讀取一行
struct protoent *getprotobyname(const char *name); //從協議檔案中找到匹配項
struct protoent *getprotobynumber(int proto); //按照協議型別的值獲取匹配項
void setprotoent(int stayopen); //設定協議檔案開啟狀態
void endprotoent(void); //關閉協議檔案
上面的函式對檔案/etc/protocols中的記錄進行操作,檔案中記錄了協議的名稱,值和別名等值,與結構struct protoent的定義一致。
struct protoent
{
char* p_name; //協議的官方名稱
char* p_aliases; //別名列表
short* p_proto; //協議的值
};
2,使用協議族函式的例子
該例子按照名稱查詢一組協議的專案,首先用setprotoent(1)開啟檔案/etc/protocols,然後使用函式getprotobyname()查詢函式並顯示出來,最後使用函式endprotoent()關閉檔案/etc/protocols
#include <netdb.h>
#include <stdio.h>
//顯示協議專案的函式
void display_protocol(struct protoent *pt)
{
int i = 0;
if (pt)
{
printf("protocol name:%s,", pt->p_name);
if (pt->p_aliases)
{
printf("alias name:");
while (pt->p_aliases[i])
{
printf("%s ", pt->p_aliases[i]);
i++;
}
}
printf(", value:%d\n", pt->p_proto);
}
}
int main(int argc, char *argv[])
{
int i = 0;
const char *const protocol_name[] = {
"ip",
"icmp",
"igmp",
"tcp",
"udp",
NULL
};
setprotoent(1); //設定協議檔案開啟狀態
while (protocol_name[i] != NULL)
{
//查詢協議
struct protoent *pt = getprotobyname((const char*)&protocol_name[i][0]);
if (pt)
{
display_protocol(pt);
}
i++;
}
endprotoent(); //關閉檔案
return 0;
}