日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

windows网络编程第二版 第三章 Internet Protocol 读书笔记

發布時間:2024/4/17 windows 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 windows网络编程第二版 第三章 Internet Protocol 读书笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. 本章主要講述IP方面的東西,解釋了IPv4, IPv6。在后面的兩個章節中,講述了地址和名字的解析(Address and Name Resolution),以及如何書寫一個IPv4, IPv6自適應的程序。

2. 簡單摘錄一下IPv4一節的內容:

(1) 可以拿來做私有地址的IP有:

10.0.0.0?10.255.255.255 (10.0.0.0/8)
172.16.0.0?172.31.255.255 (172.16.0.0/12)
192.168.0.0?192.168.255.255 (192.168.0.0/16)

在書寫IP段的時候,經常有/16, /24這樣的寫法。這表示掩碼,/16就表示前16各bit都是1,也就是255.255.0.0。

(2) 如果想在程序中獲得本機的網卡和IP地址的配置的話,要使用WSAIoctl函數,配合SIO_ADDRESS_LIST_QUERY命令。在第七章和第16章有介紹。

(3) IP地址的配置有DHCP和手動配置兩種,如果配置了DHCP,但是DHCP服務器無法reach的話,在超時后,系統會給網卡賦一個 169.254.0.0/16這個區段內的地址。這依據的是APIPA協議(Automatic Private IP Address)

(4) IPv4 Management Protocols. IPv4協議還需要很多其他協議的支撐,最常見的三種協議是ARP, ICMP, IGMP. IGMP不太熟悉,介紹一下。IGMP(Internet Group Management Protocol)是用于多播的。當一臺機器上的某個應用想要加入到一個多播group的時候,它就發出IGMP membership reports,這個消息會通知路由器,這樣路由器就會將這個請求記錄下來,當以后有多播信息發出的時候,路由器就會把多播的信息轉發到這個多播 group中的每個成員了。第九章會詳細討論多播。


3. IPv6。本節沒有看。

4. Address and Name Resolution. 本節主要介紹兩個函數:getaddrinfo, getnameinfo。getaddrinfo函數主要用于將我們給定的IP地址/主機名、端口轉換成一個SOCKADDR的結構,也就是本書中經常提 到的二進制的Addressing。getnameinfo和getaddrinfo正好相反,getnameinfo是給定一個SOCKADDR的實 例,然后生成IP地址/主機名和端口信息。

這里需要解釋一下為什么要用這兩個函數,因為我們在第一章的時候已經看到,我們可以手動申 請一個sockaddr_in的結構,然后在里面填入IP地址/主機名和端口這些信息,然后傳給connect,sendto,bind這些需要 Addressing的函數。理由有這么幾個:

(1) 使用這兩個函數,可以自適應IPv4和IPv6,而以前用sockaddr_in是針對IPv4的,要支持IPv6,還需要另外再寫代碼。比如,用這兩個 函數,當用戶輸入程序連接的主機名和端口的時候,由于我們不知道用戶輸入的主機名對應的IP是IPv4的,還是IPv6的,還是這臺主機v4, v6的地址都有,所以以往我們的代碼要自己來適應這種情況,用這兩個函數,代碼就可以自適應

(2) 使用這兩個函數不用關心主機次序,網絡次序這些東西。也就是說,傳統的函數比如inet_addr, gethostbyname這些函數都可以不用寫了。

(3) 用這兩個函數,代碼其實更好理解了。我們只需要傳入IP地址/主機名,端口這些信息,然后用getaddrinfo,通過設定不同的hint,就可以得到 addrinfo這個結構,這個結構中的東西既可以拿來創建socket,調用bind,connect,sendto等。


所以我們應該盡量用這兩個函數來操作有關Addressing方面的事宜,以前用的inet_addr, gethostbyname, gethostbyaddr這樣的代碼都應該被重寫,之所以在winsock中還保留了這些函數,是為了和舊代碼兼容。

5. OK,現在來看這兩個函數。首先要申明,這兩個函數定義在WS2TCPIP.H中,但是這僅僅是WINXP中是這樣,在其他支持WINSOCK 2的windows系統中,要使用這兩個函數,還需要在include WS2TCPIP.H的前面再include WSPIAPI.H(主要要include在WS2TCPIP.H的前面哦)。

Code: Select all
int getaddrinfo(
? ?? ?const char FAR *nodename,
? ?? ?const char FAR *servname,
? ?? ?const struct addrinfo FAR *hints,
? ?? ?struct addrinfo FAR *FAR *res
);


nodename -- 主機名或IP地址
servname -- service name, 其實就是指定端口,或者填寫ftp這樣的字符串也可以。在Windows NT這樣的系統中,在%WINDOWS%/system32/drivers/etc目錄下有一個services文件,里面填寫了端口和service 的對應關系。
hints -- 一個指向addrinfo結構的指針。
res -- 返回的結果數據,不過這個數據可能是個數組,根據hints中填寫的內容不同,函數可能會返回多個SOCKADDR的數據。

getaddrinfo執行成功返回0,返回不是0就是出錯,此時的返回值就是出錯碼(不需要用WSAGetLastError)

所以,關鍵就是hints的填法,addrinfo結構如下:

Code: Select all
struct addrinfo {
? ?? ?int? ?? ?ai_flags;
? ?? ?int? ?? ?ai_family;
? ?? ?int? ?? ?ai_socktype;
? ?? ?int? ?? ?ai_protocol;
? ?? ?size_t? ?? ?ai_addrlen;
? ?? ?char? ?? ?*ai_canonname;
? ?? ?struct sockaddr *ai_addr;
? ?? ?struct addrinfo *ai_next;
};


ai_flags -- 只能取下列三個值中的一個:AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST. AI_CANONNAME表示getaddrinfo函數中,nodename一項填寫的是主機名,例如 www.microsoft.com;AI_NUMERICHOST表示getaddrinfo函數中,nodename填寫的是一個IP地 址;AI_PASSIVE后面會講,主要是給bind用的

ai_family -- AF_INET, AF_INET6, AF_UNSPEC。如果填寫AF_UNSPEC,則getaddrinfo可能會返回一個IPv4的SOCKADDR,或IPV6的SOCKADDR, 或者兩者都返回(所以res是一個數組結構了),關鍵看主機是不是支持IPv6

ai_socktype -- 填寫socket type,比如SOCK_DGRAM, SOCK_STREAM。當getaddrinfo函數中servname一項填寫的是一個服務的名字而不是端口數字的時候,根據這一項的不同,則返回不 同的端口,因為我們知道有些服務可以使用TCP,也可以使用UDP,他們的端口是不一樣的

ai_protocol -- 指定protocol,比如IPPROTO_TCP,一樣的,當getaddrinfo中填寫的servname是一個service的名字的時候起作用。

ai_next -- 當返回多個addressing信息的時候,這是指向下一個addrinfo的指針。注意getaddrinfo函數的返回res也是一個指向addrinfo結構數組的指針

ai_addr -- 返回的sockaddr信息

如果我們在調用getaddrinfo的時候,hint沒有設定,那么,getaddrinfo就認為是一個空的hint structure被傳入,而且ai_family一項是設定成AF_UNSPEC的。

下面來看代碼例子:

Code: Select all
SOCKET? ?? ?? ?? ?s;
struct addrinfo? ?? ?hints,
? ?? ?? ?? ?*result;
int? ?? ?? ?? ?rc;

memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo("foobar", "5001", &hints, &result);
if (rc != 0) {
? ?? ?// unable to resolve the name
}
s = socket(result->ai_family, result->ai_socktype,
result->ai_protocol);
if (s == INVALID_SOCKET) {
? ?? ?// socket API failed
}
rc = connect(s, result->ai_addr, result->ai_addrlen);
if (rc == SOCKET_ERROR) {
? ?? ?// connect API failed
}
freeaddrinfo(result);


上面的代碼中,幾個注意點:

(1) 上面的代碼嘗試連接foobar這臺機器的5001端口。這里我們可以看到,我們不關心foobar這臺機器是IPv4的還是IPv6的,這完全看網絡中 foobar這個名字解析到什么機器上。如果我們只想連接IPv4的foobar這臺機器的話,那么我們在設置hint這個結構的時候,就可以把 ai_family設成AF_INET。

(2) 千萬注意,由于getaddrinfo返回的result,是動態分配的,所以我們在用完之后一定要記得調用freeaddrinfo函數來釋放。

上面的例子中,我們嘗試連接foobar這臺機器,我們也可以嘗試連接一個IP地址(可以是IPv4的,也可以是IPv6的):

Code: Select all
struct addrinfo? ?? ?hints,
? ? ?? ?? ?? ?? ?*result;
int? ?? ?? ?? ?rc;

memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AI_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo("172.17.7.1", "5001", &hints, &result);
if (rc != 0) {
? ?? ?// invalid literal address
}
// Use the result
freeaddrinfo(result);


例子很簡單。如果我們在調用getaddrinfo函數的時候沒有給hint,那么我們可以在返回的結果數據中(也就是addrinfo結構)查看flags的值,根據這個值來判斷返回的sockaddr結構中是主機名還是IP地址

在 addrinfo的flags中,我們還有一個沒有介紹,就是AI_PASSIVE,這個flag是用來取得bind函數所需要的信息的。對于IPv4來 說,bind需要的是INADDR_ANY(0.0.0.0),對于IPv6來說,bind需要的是IN6ADDR_ANY(::)。OK,所以我們在調 用getaddrinfo的時候,nodename設成NULL,servname設成我們需要綁定的端口或服務名,在hint中,要設定 ai_family,是IPv4的還是IPv6的,或者干脆設成AF_UNSPEC,此時getaddrinfo就會把兩個版本的信息都返回出來。

6. getnameinfo:
Code: Select all
int getnameinfo(
? ?? ?const struct sockaddr FAR *sa,
? ?? ?socklen_t salen,
? ?? ?char FAR *host,
? ?? ?DWORD hostlen,
? ?? ?char FAR *serv,
? ?? ?DWORD servlen,
? ?? ?int flags
);


這個函數是給定sockaddr數據,返回hostname和servname。參數很好理解,sa是給定的sockaddr信息,host,serv就是hostname和端口,hostname是FQDN的。最后的一個flags,他的取值如下:

NI_NOFQDN -- 返回的hostname不帶域名
NI_NUMERICHOST -- 返回IP地址而不是主機名
NI_NAMEREQD -- 如果sockaddr不能解析出FQDN的hostname,則返回失敗
NI_NUMERICSERV -- 返回數字端口,而不是一個service name。注意,如果我們不指定這個flag時,如果端口無法被解析成一個service name,那么函數會返回錯誤WSANO_DATA
NI_DGRAM -- 用來區分datagram service和stream services

7. Simple Address Conversion.
Code: Select all
INT WSAStringToAddress(
? ?? ?LPTSTR AddressString,
? ?? ?INT AddressFamily,
? ?? ?LPWSAPROTOCOL_INFO lpProtocolInfo,
? ?? ?LPSOCKADDR lpAddress,
? ?? ?LPINT lpAddressLength
);

INT WSAAddressToString(
? ?? ?LPSOCKADDR lpsaAddress,
? ?? ?DWORD dwAddressLength,
? ?? ?LPWSAPROTOCOL_INFO lpProtocolInfo,
? ?? ?LPTSTR lpszAddressString,
? ?? ?LPDWORD lpdwAddressStringLength
);


這 兩個函數是單純用來做IP地址和Addressing信息轉換的。也就是說,只能從一個IP地址+端口轉換成一個sockaddr或者是反過來轉換。比如 WSAStringToAddress能接受類似"192.168.0.1:1200"這樣的字符串,然后轉換成addressing數據。而 且,WSAStringToAddress也沒有getaddrinfo函數那么聰明,他必須指定IP地址是IPv4 的還是IPv6的。

8. 傳統的IPv4的處理address和name的函數。

inet_addr -- 把一個IPv4的地址轉換成網絡次序的32位long型數
inet_ntoa -- 把一個long型的網絡次序的數轉換成一個IPv4地址

gethostbyname, WSAAsyncGetHostByName, gethostbyaddr, WSAAsyncGetHostByAddr,這些函數具體看書中的描述吧,也可以看MSDN。 WSAAsyncGetXXX這樣的函數挺有意思,是異步的,在調用這個函數的時候需要給定一個buffer(這個buffer中將來會被函數填入我們想 要的東西),此外還要給定一個hwnd和msg,這樣當函數完成的時候,會給指定的hwnd窗口發送msg的消息,從而我們就可以處理了。


9. Writing IP Version-Independent Programs.

本 節就是對上一節的getaddrinfo,getnameinfo函數的一個代碼示例。在代碼中,還有一點沒有說道的就是,由于使用了這兩個函數,我們不 需要care IPv4,IPv6這些東西了,而且我們也無需自己手動申明一個SOCKADDR_IN, SOCKADDR_IN6這樣的結構變量了,因為getaddrinfo這個函數返回的addrinfo結構中,就有sockaddr的變量,直接拿來用 就行了。如果我們一定要手動申明SOCKADDR類型的變量的話,那也不要用SOCKADDR_IN, SOCKADDR_IN6這樣的結構,而是要用SOCKADDR_STORAGE這個結構,這個結構被設計成能和任何協議的SOCKADDR結構兼容,用 這個結構,能保證我們寫出來的程序不綁定在特定的網絡協議上。此外,在使用bind等函數的時候用到的地址常量,在winsock的頭文件中都有常量定 義,不需要手動hardcode。這里書中寫了一個IPv6的程序的例子,用到了上一節中WSAStringToAddress這個簡易函數來示范書寫 IP版本無關的程序:

Code: Select all
SOCKADDR_STORAGE? ?? ?? ?? ?saDestination;
SOCKET? ?? ?? ?? ?? ?s;
int? ?? ?? ?? ?? ?addrlen,
? ?? ?? ?? ?? ?rc;

s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) {
? ?? ?// socket failed
}
addrlen = sizeof(saDestination);
rc = WSAStringToAddress(
? ?? ?? ?"3ffe:2900:d005:f28d:250:8bff:fea0:92ed",
? ?? ?? ?AF_INET6,
? ?? ?? ?NULL,
? ?? ?? ?(SOCKADDR *)&saDestination,
? ?? ?? ?&addrlen
? ?? ?? ?);
if (rc == SOCKET_ERROR) {
? ?? ?// conversion failed
}
rc = connect(s, (SOCKADDR *)&saDestination, sizeof(saDestination));
if (rc == SOCKET_ERROR) {
? ?? ?// connect failed
}


程序不難理解,下面我們來看以前寫過的TCP的程序,這次我們用getaddrinfo函數來把Client和Server端的程序都改寫成IP版本無關的代碼。首先來看Client端的代碼:

Code: Select all
SOCKET? ?? ?? ?? ? s;
struct addrinfo hints,
? ? ? ? ?? ?? ?*res=NULL
char? ?? ?? ?*szRemoteAddress=NULL,
? ? ? ? ?? ?? ?*szRemotePort=NULL;
int? ?? ?? ?rc;

// Parse the command line to obtain the remote server's
// hostname or address along with the port number, which are contained
// in szRemoteAddress and szRemotePort.
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// first resolve assuming string is a string literal address
rc = getaddrinfo(
? ?? ?? ?szRemoteAddress,
? ?? ?? ?szRemotePort,
? ? ?? ?? ?&hints,
? ? ?? ?? ?&res
? ? ? ? ?);
if (rc == WSANO_DATA) {
? ?? ?// Unable to resolve name - bail out
? ?}
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (s == INVALID_SOCKET) {
? ?? ?// socket failed
}
rc = connect(s, res->ai_addr, res->ai_addrlen);
if (rc == SOCKET_ERROR) {
? ?? ?// connect failed
}
freeaddrinfo(res);


在 上面的代碼中,我們看到:如果這是一個完整的程序的話,那么我們可以從命令行中得到szRemoteAddress, szRemotePort這兩個信息,而且我們根本不用管這兩項是IPv4的還是IPv6的,只需要把hint中的family設成AF_UNSPEC, 然后去調用getaddrinfo即可。很方便。

而且,如果我們在connect或sendto之前,需要bind的話,也很簡單,前面 說過了,bind唯一需要的就是本機地址和端口的描述。我們只需要把前面一次調用getaddrinfo生成的addrinfo結構中的family, socket type, protocol這三項設到一個新的hint中去(不能手動設定哦,一定要用上一次getaddrinfo的返回值,手動設定的話又會牽涉到IPv4, IPv6這樣的family設定了,用上次返回的信息,這就是根據我們的szRemoteAddress生成的正確family),同時把 hint.ai_flags設成AI_PASSIVE,然后再調用一次getaddrinfo,這一次調用getaddrinfo,將nodename設 成NULL,servname填上我們想要的端口,然后調用getaddrinfo,就能返回我們bind需要的Addressing信息了。


Server端代碼:

Code: Select all
SOCKET? ?? ?? ?? ?slisten[16];
char? ?? ?? ?? ?*szPort="5150";
struct addrinfo? ?? ?? ?? ?? hints,
? ?? ?? ?? ?* res=NULL,
? ?? ?? ?? ?* ptr=NULL;
int? ?? ?? ?? ?? count=0,
? ?? ?? ?? ?? rc;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
rc = getaddrinfo(NULL, szPort, &hints, &res);
if (rc != 0) {
? ?? ?// failed for some reason
}
ptr = res;
while (ptr)
{
? ?? ?slisten[count] = socket(ptr->ai_family,
? ?? ?ptr->ai_socktype, ptr->ai_protocol);
? ?? ?if (slisten[count] == INVALID_SOCKET) {
? ?? ?? ?// socket failed
? ?? ?}
? ?? ?rc = bind(slisten[count], ptr->ai_addr, ptr->ai_addrlen);
? ?? ?if (rc == SOCKET_ERROR) {
? ?? ?? ?// bind failed
? ?? ?}
? ?? ?rc = listen(slisten[count], 7);
? ?? ?if (rc == SOCKET_ERROR) {
? ?? ?? ?// listen failed
? ?? ?}
? ?? ?count++;
? ?? ?ptr = ptr->ai_next;
}


OK,上面的代碼很好理解,我們看到Server不需要去connect別人,只需要自己 bind然后listen(TCP)。所以,我們設置了hint.ai_flags為 AI_PASSIVE,然后,由于hint.ai_family設成了AF_UNSPEC,所以getaddrinfo會返回IPv4和IPv6兩種 Addressing信息。既然這樣,我們索性就用循環,在getaddrinfo返回的兩個address上都創建socket,都bind,都 listen。

轉載于:https://www.cnblogs.com/super119/archive/2011/04/10/2011306.html

總結

以上是生活随笔為你收集整理的windows网络编程第二版 第三章 Internet Protocol 读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。