《UNIX網路程式設計》 第三章 套接字程式設計簡介
一.套接字地址結構
IPv4的地址結構為sockaddr_in,IPv6為sockaddr_in6,鏈路協議sockaddr_dl,Unix域為sockaddr_un,儲存為sockaddr_storage。
**套接字地址結構總是以引用形式來傳遞!
1.IPv4
struct in_addr
{
in_addr_t s_addr; // 32位元的IPv4地址,網路位元組序
// 需要函式將點分十進位制的地址轉化為該值
};
struct sockaddr_in
{
uint8_t sin_len; // 結構長度
sa_family_t sin_family; // 恆為AF_INET
in_port_t sin_port; // 16位元TCP或UDP埠,網路位元組序
struct in_addr sin_addr; // 32位元的IPv4地址,網路位元組序
char sin_zero[8]; // 沒有使用
};
**IPv4地址和TCP,UDP埠號在套接字地址結構中總是以【網路位元組序】來儲存。
2.IPv6
struct in6_addr {
uint8_t s6_addr[16]; /*128-bit IPv6 addr*/
};
#define SIN6_LEN /*FOR TEST*/
struct sockaddr_in6 {
uint8_t
sin6_len; /*len of this
struct(28)*/
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id; /*set of interface for a scope*/
};
**v4的地址族為AF_INET,v6的地址族為AF_INET6。
3.通用套接字地址結構
由於之前沒有void *指標型別,所以在宣告傳遞指標的資料型別時需要用一個通用的套接字地址結構,之後對相關函式的呼叫都要將特定的結構指標強制型別轉換為通用結構指標。
比如:
struct sockaddr_in serv;
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
二.值-結果引數
意思是,既作為函式的引數,又作為函式的一部分結果。(通過引用形式傳遞)
(1)從程序到核心傳遞套接字地址結構的函式:bind, connect, sendto,兩個引數一個某個套接字的指標,另一個該結構的整數大小
(2)從核心到程序的函式:accept, recvfrom, getsockname, getpeername,兩個引數分別指向某套接字的指標,指向表示該結構大小的整數變數的指標。
三.位元組排序
小端:低位元組在起始地址
大端:高位元組在起始地址
**網路協議使用大端位元組序來傳送。
我們把某個給定系統所用的位元組序稱為主機位元組序。
union結構:變數成員公用同一記憶體,其長度為聯合中最大的變數長度。
判斷主機位元組序:
#include <iostream>
using namespace std;
int main()
{
union {
short s;
char bytes[2];
}un;
short sSample = 0x0102;
un.s = sSample;
if (sizeof(un.s) == 2)
{
if (un.bytes[0] == 1 && un.bytes[1] == 2)
{
cout << "big-endian" << endl;
}
else if (un.bytes[0] == 2 && un.bytes[1] == 1)
{
cout << "little-endian" << endl;
}
else
{
cout << "unknow" << endl;
}
}
else
{
cout << "size of short is not 2, but " << sizeof(short) << endl;
}
return 0;
}
在進行網路程式設計時,我們不需要關心機器位元組序和網路位元組序到底是little-endian還是big-endian,我們只需要知道資料在當前機器上的程序處理時,需要使用本機位元組序,當資料在網路上傳遞時,需要使用網路位元組序。通過下面這四個函式,可以方便的進行本機與網路位元組序之間的轉換:
#include <netinet/in.h>
uint16_t htons(uint16_t host_16_bit_value);
uint32_t htonl(uint32 _t host_32_bit_value);
//both return: value in network byte order
uint16_t ntohs(uint16_t net_16_bit_value);
uint32_t ntohl(uint32_t net_32_bit_value);
//both return: value in host byte order
h代表host, n代表network;
s代表short,l代表long。
(s視為一個16位的值比如埠號,l視為一個32位的值比如IPv4地址)
四.位元組操縱函式
Unix下有兩組位元組操作函式,一組以b(byte)開頭,是socket庫提供的自己操作函式,一種以mem(memory)開頭,由ANSI C提供。
#include <strings.h> // 注意這裡不是<string.h>,多了一個s
void bzero(void* dest, size_t nbyte);
void bcopy(const void* src, void* dest, size_t nbyte);
int bcmp(const void* ptr1,void* ptr2, size_t nbyte);
我們通常使用bzero而不是memset,因為bzero只有兩個引數!
五.函式inet_aton,inet_addr和inet_ntoa
這幾個函式都是用於點分十進位制IP和網路位元組序二進位制IP相互轉換。
#include <arpa/inet.h>
/*
* 注意:
* 1 下面一對函式,相互轉換,a代表字串,n代表網路位元組序
* 2 沒有長度引數,因為點分十進位制額字串長度比較固定,程式可以分析
* 3 inet_ntoa接收的引數是值,而不是指標,比較少見,而且返回的值是在靜態內容中,
* 且執行修改,因為不是const
* 4 inet_aton 返回1代表成功轉換,0代表失敗,可以理解為true和false,
* 與一般的0標識成功有點不同
*/
int inet_aton(const char* strptr, struct in_addr* addrptr);
char* inet_ntoa(struct in_addr inaddr);
/*
* 注意:
* 此方法過時,因為文件不全,更主要的是對於“255.255.255.255”轉換得到結果與錯誤碼相同,有缺陷,所以不建議使用。雖然使用起來,比inet_aton方便,但是隱患較多,所以最好不要使用。
*/
in_addr_t inet_addr(const char* strptr);
六.函式inet_pton和inet_ntop
這一對函式與inet_aton/inet_ntoa類似,但是支援Ipv6,添加了一個family引數接受AF_INET對應IPV4,AF_INET6對應Ipv6。”p”和”n”分別代表presentation和numeric。inet_pton/inet_ntop的函式介面更為一致。我們應在程式中使用它們。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, struct in_addr *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); // error, return NULL
// addrptr是數值格式指標,strptr是表達格式地址,如果成功返回strptr,如果失敗返回NULL
//**inet_ntop的strptr引數不可以是空指標,呼叫者應該為目標儲存單元分配記憶體並指定其大小
七.readn,writen和readline函式
位元組流套接字上呼叫read和write獲得的位元組數可能比請求的數量少(因為核心中用於套接字的緩衝區可能滿了),此時需要我們多次呼叫read和write函式。
因此,我們使用readn,writen來代替,從而避免讓呼叫者來處理不足的位元組計數值。
readline函式每次讀取一行。
慢速版本:每讀一個位元組呼叫一次read。
快速版本:提供緩衝,用my_read代替read,my_read每次最多讀MAXLINE個字元,然後每次返回一個字元。