Beej网络编程指南《三》
9手冊
在Unix世界里,有很多手冊。它們有小部分描述了你可以使用的單個函數。
當然,手動的東西太難打了。我的意思是,在Unix世界里,沒有人,包括我自己,喜歡打那么多。事實上,我可以長篇大論地說我有多喜歡簡潔,但我會簡潔,不會用冗長的抨擊來煩擾你,說我幾乎在所有情況下都非常喜歡簡潔。
[掌聲]
謝謝。我要說的是,這些頁面在Unix世界中被稱為“手冊頁”,為了讓你閱讀愉快,我在這里包含了我自己的截斷變體。問題是,這些函數中的許多比我所說的更通用,但我將只介紹與互聯網套接字編程相關的部分。
但是等等!這不是我男人頁面的全部問題:
-
它們不完整,只顯示了指南中的基礎知識。
-
現實世界中有比這更多的人文頁面。
-
它們與您系統上的不同。
-
對于系統上的某些功能,頭文件可能不同。
-
對于系統上的某些函數,函數參數可能不同。
如果你想了解真正的信息,可以在本地的Unix手冊頁面上輸入man any,其中的“不管”是你非常感興趣的東西,比如“接受”。(我肯定微軟Visual Studio在他們的幫助部分也有類似的東西。但是“人”更好,因為它比“幫助”簡潔一個字節。Unix又贏了!)
那么,如果這些有如此大的缺陷,為什么還要在指南中包含它們呢?嗯,有幾個原因,但最好的是(a)這些版本是專門面向網絡編程的,比真正的版本更容易消化,(b)這些版本包含示例!
哦!說到這些例子,我不傾向于插入所有的錯誤檢查,因為它確實增加了代碼的長度。但是你絕對應該在你進行任何系統調用的時候進行錯誤檢查,除非你完全100%確定它不會失敗,你甚至應該這樣做!
9.1接受()
在偵聽套接字上接受傳入連接
9.1.0.1簡介
#include <sys/types.h>#include <sys/socket.h>int accept(int s, struct sockaddr *addr, socklen_t *addrlen);9.1.0.2說明
一旦您經歷了獲取SOCK_STREAM套接字并用listk()為傳入連接設置它的麻煩,那么您將調用接受()來實際獲取一個新的套接字描述符,用于與新連接的客戶端的后續通信。
您用于監聽的舊套接字仍然存在,并且將在它們進來時用于進一步接受()調用。
ParameterDescriptionsThe listen()ing socket descriptor.addrThis is filled in with the address of the site that’s connecting to you.addrlenThis is filled in with the sizeof() the structure returned in the addr parameter. You can safely ignore it if you assume you’re getting a struct sockaddr_in back, which you know you are, because that’s the type you passed in for addr.
接受()通常會阻塞,您可以使用選擇()提前查看偵聽套接字描述符是否“準備好讀取”。如果是,那么有一個新的連接等待接受()編輯!耶。或者,您可以使用fcntl()在偵聽套接字上設置O_NONBLOCK標志,然后它永遠不會阻塞,而是選擇返回-1,并將errno設置為EWOULDBLOCK。
接受()返回的套接字描述符是一個真正的套接字描述符,打開并連接到遠程主機。完成后必須關閉()。
9.1.0.3返回值
接受()返回新連接的套接字描述符,或-1開錯誤,并適當設置errno。
9.1.0.4實例
struct sockaddr_storage their_addr; socklen_t addr_size; struct addrinfo hints, *res; int sockfd, new_fd;// first, load up address structs with getaddrinfo():memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // fill in my IP for megetaddrinfo(NULL, MYPORT, &hints, &res);// make a socket, bind it, and listen on it:sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); bind(sockfd, res->ai_addr, res->ai_addrlen); listen(sockfd, BACKLOG);// now accept an incoming connection:addr_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);// ready to communicate on socket descriptor new_fd!9.1.0.5也看
套接字``(),getaddrinfo(),``聽()``,``結構sockaddr_in9.2綁定()
將套接字與IP地址和端口號關聯起來
9.2.0.1簡介
#include <sys/types.h>#include <sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);9.2.0.2描述
當遠程計算機想要連接到您的服務器程序時,它需要兩條信息:IP地址和端口號。bind()調用允許您這樣做。
首先,我們調用getaddrinfo()來加載一個帶有目標地址和端口信息的結構Sockaddr。然后我們調用套接字()來獲取一個套接字描述符,然后將套接字和地址傳遞給bind(),然后IP地址和端口神奇地(使用實際的魔法)綁定到套接字上!
如果您不知道您的IP地址,或者您知道您在機器上只有一個IP地址,或者您不關心使用了哪個機器的IP地址,您可以簡單地將hint參數中的AI_PASSIVE標志傳遞給getaddrinfo()。這樣做的目的是用一個特殊的值填入結構體sokaddr的IP地址部分,該值告訴bind()它應該自動填入主機的IP地址。
什么是什么?是什么特殊值被加載到struct sokaddr的IP地址,導致它自動填充地址與當前主機?我會告訴你,但請記住,這是只有當你填寫的結構sokaddr手工;如果不是,使用getaddrinfo()的結果,如上所述。在IPv4中,sin_addr。s_addr結構sockaddr_in結構的字段被設置為INADDR_ANY。在IPv6中,結構sockaddr_in6結構的sin6_addr字段被分配到從全局變量in6addr_any。或者,如果要in6_addr聲明一個新結構,可以將其初始化為IN6ADDR_ANY_INIT。
最后,addrlen參數應該設置為sizeofmy_addr。
9.2.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.2.0.4實例
// modern way of doing things with getaddrinfo()struct addrinfo hints, *res; int sockfd;// first, load up address structs with getaddrinfo():memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // fill in my IP for megetaddrinfo(NULL, "3490", &hints, &res);// make a socket: // (you should actually walk the "res" linked list and error-check!)sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);// bind it to the port we passed in to getaddrinfo():bind(sockfd, res->ai_addr, res->ai_addrlen); // example of packing a struct by hand, IPv4struct sockaddr_in myaddr; int s;myaddr.sin_family = AF_INET; myaddr.sin_port = htons(3490);// you can specify an IP address: inet_pton(AF_INET, "63.161.169.137", &(myaddr.sin_addr));// or you can let it automatically select one: myaddr.sin_addr.s_addr = INADDR_ANY;s = socket(PF_INET, SOCK_STREAM, 0); bind(s, (struct sockaddr*)&myaddr, sizeof myaddr);9.2.0.5也看
getaddrinfo(),套接字``(),結構sockaddr_in,結構in_addr
9.3連接()
將套接字連接到服務器
9.3.0.1簡介
#include <sys/types.h>#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *serv_addr,socklen_t addrlen);9.3.0.2說明
一旦您已經建立了一個套接字描述符與套接字()調用,您可以連接()該套接字到遠程服務器使用良好命名的連接()系統調用。所有您需要做的是傳遞套接字描述符和服務器的地址,你有興趣更好地了解。(哦,還有地址的長度,這通常是傳遞給這樣的函數。)
通常,這些信息是調用getaddrinfo()的結果,但是如果你愿意,你可以填寫你自己的結構。
如果您還沒有在套接字描述符上調用bind(),它會自動綁定到您的IP地址和一個隨機的本地端口。如果您不是服務器,這通常很好,因為您真的不在乎您的本地端口是什么;你只關心遠程端口是什么,所以你可以把它放在serv_addr參數中。如果您真的希望您的客戶端套接字位于特定的IP地址和端口上,您可以調用bind(),但這種情況非常罕見。
一旦套接字被連接(),你就可以自由地發送()和recv()數據。
特別注意:如果您連接()一個SOCK_DGRAMUDP套接字到一個遠程主機,您可以使用mail()和recv()以及sendto()和recvfrom()。如果您愿意的話。
9.3.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.3.0.4例子
// connect to www.example.com port 80 (http)struct addrinfo hints, *res; int sockfd;// first, load up address structs with getaddrinfo():memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM;// we could put "80" instead on "http" on the next line: getaddrinfo("www.example.com", "http", &hints, &res);// make a socket:sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);// connect it to the address and port we passed in to getaddrinfo():connect(sockfd, res->ai_addr, res->ai_addrlen);9.3.0.5另見
套接字`(),`綁定()9.4關閉()
關閉套接字描述符
9.4.0.1簡介
#include <unistd.h>int close(int s);9.4.0.2描述
在您完成了使用套接字的任何瘋狂的計劃,你已經編造,你不想發送()或recv()或,事實上,做任何其他與套接字,你可以關閉()它,它將被釋放,永遠不會再使用。
如果遠程端調用recv(),它將返回0;如果遠程端調用了mail(),它將接收到一個信號SIGPIPE,而mail()將返回-1,errno將被設置為EPIPE。
Windows用戶:你需要使用的函數叫做關閉(),而不是關閉()。如果你試圖在套接字描述符上使用關閉(),Windows可能會生氣…你不喜歡它生氣的時候。
9.4.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.4.0.4例子
s = socket(PF_INET, SOCK_DGRAM, 0); . . . // a whole lotta stuff...*BRRRONNNN!* . . . close(s); // not much to it, really.9.4.0.5查看
套接字`(),`關機()9.5 getaddrinfo(),freaddrinfo(),gai_strerror()
獲取有關主機名和/或服務的信息,并加載一個帶有結果的結構sokaddr。
9.5.0.1簡介
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res); void freeaddrinfo(struct addrinfo *ai); const char *gai_strerror(int ecode); struct addrinfo { int ai_flags; // AI_PASSIVE, AI_CANONNAME, ... int ai_family; // AF_xxx int ai_socktype; // SOCK_xxx int ai_protocol; // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP socklen_t ai_addrlen; // length of ai_addr char *ai_canonname; // canonical name for nodename struct sockaddr *ai_addr; // binary address struct addrinfo *ai_next; // next structure in linked list };9.5.0.2描述
getaddrinfo()是一個非常好的函數,它可以返回特定主機名的信息(比如它的IP地址),并為您加載一個構建的Sockaddr,處理一些細節(比如它是IPv4還是IPv6)。它取代了舊的函數gethstatbyname()和getservbyname()。下面的描述包含了很多可能有點令人生畏的信息,但是實際使用非常簡單。首先看看這些例子可能是值得的。您感興趣的主機名位于nodename參數中。地址可以是主機名,如“www.example.com”,也可以是IPv4或IPv6地址(作為字符串傳遞)。如果您使用AI_PASSIVE標志,該參數也可以為NULL(見下文)。
servname參數基本上是端口號。它可以是端口號(作為字符串傳遞,如“80”),也可以是服務名稱,如“超文本傳輸協議”或“tftp”或“smtp”或“pop”等。眾所周知的服務名稱可以在IANA端口列表48或您的 /etc/services文件中找到。
最后,對于輸入參數,我們有一些提示。這是您真正定義getaddrinfo()函數要做什么的地方。在使用memset()之前,將整個結構歸零。讓我們看看在使用前需要設置的字段。
ai_flags可以設置為多種內容,但這里有幾個重要的內容。(可以通過與|運算符一起按位ORing來指定多個標志)。查看手冊頁面以獲取完整的標志列表。
AI_CANONNAME將結果的ai_canonname填充為主機的規范(真實)名稱。AI_PASSIVE將結果的IP地址填充為INADDR_ANY(IPv4)或in6addr_any(IPv6);這將導致后續調用bind(),以用當前主機的地址自動填充struct solkaddr的IP地址。當您不想對地址進行硬編碼時,這非常適合設置服務器。
如果您使用AI_PASSIVE,標志,那么您可以在節點名稱中傳遞NULL(因為bind()稍后會為您填寫)。
繼續輸入參數,您可能需要將ai_family設置為AF_UNSPEC,這告訴getaddrinfo()查找IPv4和IPv6地址。您也可以通過AF_INET或AF_INET6將自己限制在其中一個。
接下來,ocktype字段應該設置為SOCK_STREAM或SOCK_DGRAM,這取決于您想要哪種類型的套接字。
最后,只需將ai_protocol保持在0即可自動選擇協議類型。
現在,在你把所有的東西都放進去之后,你終于可以打電話給getaddrinfo()了!
當然,這就是樂趣的開始。res現在將指向一個結構地址的鏈接列表,您可以通過這個列表獲得所有與您傳遞的提示相匹配的地址。現在,有可能得到一些地址因為這樣或那樣的原因而不起作用,所以Linux手冊頁所做的就是循環遍歷列表,調用套接字()和連接()(或者綁定(),如果您正在設置帶有AI_PASSIVE標志的服務器),直到它成功。
最后,當你完成了鏈接列表,你需要調用freaddrinfo()來釋放內存(否則它會泄露,有些人會不高興)。
9.5.0.3返回值
成功時返回零,錯誤時返回非零。如果返回非零,您可以使用函數gai_strerror()在返回值中獲取錯誤代碼的可打印版本。
9.5.0.4實例
// code for a client connecting to a server // namely a stream socket to www.example.com on port 80 (http) // either IPv4 or IPv6int sockfd; struct addrinfo hints, *servinfo, *p; int rv;memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6 hints.ai_socktype = SOCK_STREAM;if ((rv = getaddrinfo("www.example.com", "http", &hints, &servinfo)) != 0) {fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));exit(1); }// loop through all the results and connect to the first we can for(p = servinfo; p != NULL; p = p->ai_next) {if ((sockfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol)) == -1) {perror("socket");continue;}if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {perror("connect");close(sockfd);continue;}break; // if we get here, we must have connected successfully }if (p == NULL) {// looped off the end of the list with no connectionfprintf(stderr, "failed to connect\n");exit(2); }freeaddrinfo(servinfo); // all done with this structure // code for a server waiting for connections // namely a stream socket on port 3490, on this host's IP // either IPv4 or IPv6.int sockfd; struct addrinfo hints, *servinfo, *p; int rv;memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6 hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // use my IP addressif ((rv = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));exit(1); }// loop through all the results and bind to the first we can for(p = servinfo; p != NULL; p = p->ai_next) {if ((sockfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol)) == -1) {perror("socket");continue;}if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {close(sockfd);perror("bind");continue;}break; // if we get here, we must have connected successfully }if (p == NULL) {// looped off the end of the list with no successful bindfprintf(stderr, "failed to bind socket\n");exit(2); }freeaddrinfo(servinfo); // all done with this structure9.5.0.5也看
#####################################################################################``#########9.6地名()
返回系統的名稱
9.6.0.1簡介
#include <sys/unistd.h> int gethostname(char *name, size_t len);9.6.0.2說明
你的系統有一個名字。他們都有。這是一個比我們談論的其他網絡東西稍微更Unixy的東西,但是它仍然有它的用途。
例如,您可以獲取您的主機名,然后調用gethostbyname()來查找您的IP地址。
參數name應該指向一個將保存主機名的緩沖區,len是該緩沖區的大小(以字節為單位)。gethostname()不會覆蓋緩沖區的結尾(它可能會返回錯誤,或者它可能會停止寫入),如果緩沖區中有空間,它將NUL終止字符串。
9.6.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.6.0.4例子
char hostname[128];gethostname(hostname, sizeof hostname);printf("My hostname: %s\n", hostname);9.6.0.5另見
地名()9.7 gethokbyname(), gethokbyaddr()
獲取主機名的IP地址,反之亦然
9.7.0.1簡介
#include <sys/socket.h> #include <netdb.h> struct hostent *gethostbyname(const char *name); // DEPRECATED! struct hostent *gethostbyaddr(const char *addr, int len, int type);9.7.0.2描述
請注意:這兩個函數被*getaddrinfo()和getnameinfo()所取代!特別是,gethostby*name()不能很好地與IPv6一起工作。
這些函數在主機名和IP地址之間來回映射。例如,如果您有www.example.com,您可以使用gethostbyname()獲取其IP地址并將其存儲在結構in_addr中。
相反,如果你有一個結構in_addr或結構in6_addr,你可以使用gethstatbyaddr()來獲取主機名。gethstatbyaddr()與IPv6兼容,但``*是*``你應該使用更新的getnameinfo()來代替。
(如果你有一個包含點和數字格式的IP地址的字符串,你想查找的主機名,你最好使用帶有AI_CANONNAME標志的getaddrinfo()。)
gethstatbyname()接受一個類似www.yahoo.com的字符串,并返回一個包含大量信息的結構宿主,包括IP地址。(其他信息是官方主機名、別名列表、地址類型、地址長度和地址列表——這是一個通用結構,一旦你看到了如何使用,就很容易用于我們的特定目的。)
gethstatbyaddr()接受一個結構in_addr或結構in6_addr并帶給你一個相應的主機名(如果有的話),所以它有點像gethstatbyname()的相反。至于參數,即使addr是一個char*,你實際上想要傳遞一個指向結構in_addr的指針。len應該是sizeof(結構in_addr),類型應該是AF_INET。
那么返回的這個結構宿主是什么呢?它有許多字段包含有關所討論宿主的信息。
FieldDescriptionchar h_nameThe real canonical host name.char **h_aliasesA list of aliases that can be accessed with arrays—the last element is NULLint h_addrtypeThe result’s address type, which really should be AF_INET for our purposes.int lengthThe length of the addresses in bytes, which is 4 for IP (version 4) addresses.char h_addr_listA list of IP addresses for this host. Although this is a char, it’s really an array of struct in_addrs in disguise. The last array element is NULL.h_addrA commonly defined alias for h_addr_list[0]. If you just want any old IP address for this host (yeah, they can have more than one) just use this field.
9.7.0.3返回值
成功時返回指向結果結構宿主的指針,錯誤時返回NULL。
這些函數不是通常的perror()和所有通常用于錯誤報告的東西,而是在變量h_errno中具有并行結果,可以使用函數herror()或hstrirror()打印。這些函數的工作原理就像您習慣的經典errno、perror()和strarror()函數一樣。
9.7.0.4例子
// THIS IS A DEPRECATED METHOD OF GETTING HOST NAMES // use getaddrinfo() instead!#include <stdio.h> #include <errno.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>int main(int argc, char *argv[]) {int i;struct hostent *he;struct in_addr **addr_list;if (argc != 2) {fprintf(stderr,"usage: ghbn hostname\n");return 1;}if ((he = gethostbyname(argv[1])) == NULL) { // get the host infoherror("gethostbyname");return 2;}// print information about this host:printf("Official name is: %s\n", he->h_name);printf(" IP addresses: ");addr_list = (struct in_addr **)he->h_addr_list;for(i = 0; addr_list[i] != NULL; i++) {printf("%s ", inet_ntoa(*addr_list[i]));}printf("\n");return 0; } // THIS HAS BEEN SUPERCEDED // use getnameinfo() instead!struct hostent *he; struct in_addr ipv4addr; struct in6_addr ipv6addr;inet_pton(AF_INET, "192.0.2.34", &ipv4addr); he = gethostbyaddr(&ipv4addr, sizeof ipv4addr, AF_INET); printf("Host name: %s\n", he->h_name);inet_pton(AF_INET6, "2001:db8:63b3:1::beef", &ipv6addr); he = gethostbyaddr(&ipv6addr, sizeof ipv6addr, AF_INET6); printf("Host name: %s\n", he->h_name);9.7.0.5另見
getaddrinfo``(),getnameinfo``(),gethstatname``(),``errno,perror(),``strrror()``,``結構``in_addr9.8 getnameinfo()
查找給定結構體ockaddr的主機名和服務名信息。
9.8.0.1簡介
#include <sys/socket.h>#include <netdb.h>int getnameinfo(const struct sockaddr *sa, socklen_t salen,char *host, size_t hostlen,char *serv, size_t servlen, int flags);9.8.0.2描述
這個函數與getaddrinfo()相反,也就是說,這個函數接受一個已經加載的結構sokaddr,并對其進行名稱和服務名稱查找。它取代了舊的gethostbyaddr()和getservbyport()函數。
您必須在sa參數中傳遞一個指向struct solkaddr的指針(實際上可能是一個結構sockaddr_in或您已經轉換的結構sockaddr_in6),以及salen中該結構的長度。
生成的主機名和服務名將被寫入主機和服務器參數指向的區域。當然,您必須在host len和servlen中指定這些緩沖區的最大長度。
最后,有幾個標志可以傳遞,但這里有幾個好的標志。NI_NOFQDN將導致主機只包含主機名,而不是整個域名。如果在DNS查找中找不到名稱,NI_NAMEREQD將導致函數失敗(如果不指定此標志并且找不到名稱,getnameinfo()將在主機中放置IP地址的字符串版本)。
像往常一樣,查看您當地的手冊頁面以獲取完整的獨家新聞。
9.8.0.3返回值
成功時返回零,錯誤時返回非零。如果返回值為非零,則可以將其傳遞給gai_strerror()以獲得人類可讀的字符串。有關更多信息,請參閱getaddrinfo。
9.8.0.4例子
struct sockaddr_in6 sa; // could be IPv4 if you want char host[1024]; char service[20];// pretend sa is full of good information about the host and port...getnameinfo(&sa, sizeof sa, host, sizeof host, service, sizeof service, 0);printf(" host: %s\n", host); // e.g. "www.example.com" printf("service: %s\n", service); // e.g. "http"9.8.0.5另見
Getaddrinfo(),gethokbyaddr(``)9.9 getpeername()
返回連接遠程端的地址信息
9.9.0.1簡介
#include <sys/socket.h>int getpeername(int s, struct sockaddr *addr, socklen_t *len);9.9.0.2說明
一旦你接受了一個遠程連接,或者連接了一個服務器,你現在就有了一個所謂的對等點。你的對等點就是你連接的計算機,由一個IP地址和一個端口來識別。所以…
getpeername()簡單地返回一個結構sockaddr_in填充有關連接到的機器的信息。
為什么它被稱為“名稱”?嗯,有很多不同類型的套接字,不僅僅是像我們在本指南中使用的互聯網套接字,所以“名稱”是一個很好的通用術語,涵蓋了所有情況。然而,在我們的例子中,對等方的“名稱”是它的IP地址和端口。
雖然該函數返回len中結果地址的大小,但您必須用addr的大小預加載len。
9.9.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.9.0.4實例
// assume s is a connected socketsocklen_t len;struct sockaddr_storage addr;char ipstr[INET6_ADDRSTRLEN];int port;len = sizeof addr;getpeername(s, (struct sockaddr*)&addr, &len);// deal with both IPv4 and IPv6:if (addr.ss_family == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in *)&addr; port = ntohs(s->sin_port); inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);} else { // AF_INET6 struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; port = ntohs(s->sin6_port); inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);}printf("Peer IP address: %s\n", ipstr);printf("Peer port : %d\n", port);9.9.0.5也看
gethokbyname``()``, gethokbyname(), gethokbyaddr()
9.10errno
保存上次系統調用的錯誤代碼
9.10.0.1簡介
#include <errno.h> int errno;9.10.0.2描述
這個變量保存了許多系統調用的錯誤信息。如果你記得的話,像套接字()和監聽()這樣的東西在錯誤時返回-1,它們設置errno的確切值來讓你知道具體發生了哪個錯誤。頭文件errno. h列出了一堆錯誤的常量符號名稱,如EADDRINUSE、EPIPE、ECONNREFUSED等。您的本地手冊頁面將告訴您哪些代碼可以作為錯誤返回,您可以在運行時使用這些代碼以不同的方式處理不同的錯誤。
或者,更常見的是,您可以調用perror()或strirror()來獲取該錯誤的可讀版本。
對于多線程愛好者來說,有一點需要注意的是,在大多數系統中errno是以線程安全的方式定義的。(也就是說,它實際上不是一個全局變量,但它的行為就像單線程環境中的全局變量一樣。)
9.10.0.3返回值
變量的值是最近發生的錯誤,如果最后一個操作成功,這可能是“成功”的代碼。
9.10.0.4例子
s = socket(PF_INET, SOCK_STREAM, 0); if (s == -1) {perror("socket"); // or use strerror() }tryagain: if (select(n, &readfds, NULL, NULL) == -1) {// an error has occurred!!// if we were only interrupted, just restart the select() call:if (errno == EINTR) goto tryagain; // AAAA! goto!!!// otherwise it's a more serious error:perror("select");exit(1); }9.10.0.5另見
錯誤的``選擇9.11 fcntl()
控制套接字描述符
9.11.0.1簡介
#include <sys/unistd.h>#include <sys/fcntl.h>int fcntl(int s, int cmd, long arg);9.11.0.2說明
此函數通常用于執行文件鎖定和其他面向文件的操作,但它也有一些與套接字相關的函數,您可能會不時看到或使用這些函數。
參數s是您希望操作的套接字描述符,cmd應該設置為F_SETFL,arg可以是以下命令之一。(就像我說的,fcntl()比我在這里說的要多,但我試圖保持面向套接字。)
cmdDescriptionO_NONBLOCKSet the socket to be non-blocking. See the section on blocking for more details.O_ASYNCSet the socket to do asynchronous I/O. When data is ready to be recv()’d on the socket, the signal SIGIO will be raised. This is rare to see, and beyond the scope of the guide. And I think it’s only available on certain systems.
9.11.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
fcntl()系統調用的不同用法實際上有不同的返回值,但我在這里沒有討論它們,因為它們與套接字無關。有關更多信息,請參閱本地fcntl()手冊頁。
9.11.0.4實例
int s = socket(PF_INET, SOCK_STREAM, 0);fcntl(s, F_SETFL, O_NONBLOCK); // set to non-blockingfcntl(s, F_SETFL, O_ASYNC); // set to asynchronous I/O9.11.0.5也看
阻塞,發送()
9.12 hton(), htonl(), ntohs(), ntohl()
將多字節整數類型從主機字節順序轉換為網絡字節順序
9.12.0.1簡介
#include <netinet/in.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);9.12.0.2描述
只是讓你不高興的是,不同的計算機在內部對它們的多字節整數(即任何大于char的整數)使用不同的字節順序。結果是,如果你從英特爾盒子向蘋果電腦發送()一個兩字節的短int(在它們變成英特爾盒子之前,我的意思是),一臺電腦認為是數字1,另一臺電腦認為是數字256,反之亦然。
解決這個問題的方法是,每個人都把分歧放在一邊,同意摩托羅拉和IBM是對的,而英特爾是以奇怪的方式做到的,所以我們都在發送字節順序之前將其轉換為“大端”。由于英特爾是一臺“小端”機器,將我們首選的字節順序稱為“網絡字節順序”在政治上要正確得多。所以這些函數從你的本機字節順序轉換為網絡字節順序,然后再轉換回來。
(這意味著在英特爾上,這些函數交換周圍的所有字節,而在PowerPC上,它們什么也不做,因為字節已經處于網絡字節順序。但是無論如何,你應該在你的代碼中始終使用它們,因為有人可能想在英特爾機器上構建它,并且仍然讓事情正常工作。)
請注意,所涉及的類型是32位(4字節,可能是int)和16位(2字節,很可能是短)數字。64位機器可能有一個用于64位int的htonll(),但我沒有見過它。你只需要寫你自己的。
不管怎樣,這些函數的工作方式是,您首先決定是從主機(您的機器)字節順序還是從網絡字節順序轉換。如果是“主機”,您要調用的函數的第一個字母是“h”。否則是“網絡”的“n”。函數名稱的中間總是“to”,因為您正在從一個“轉換”到“另一個”,倒數第二個字母顯示您正在轉換的內容。最后一個字母是數據的大小,短的“s”,長的“l”。因此:
FunctionDescriptionhtons()host to network shorthtonl()host to network longntohs()network to host shortntohl()network to host long
9.12.0.3返回值
每個函數返回轉換后的值。
9.12.0.4實例
uint32_t some_long = 10;uint16_t some_short = 20;uint32_t network_byte_order;// convert and sendnetwork_byte_order = htonl(some_long);send(s, &network_byte_order, sizeof(uint32_t), 0);some_short == ntohs(htons(some_short)); // this expression is true9.13inet_ntoa()、inet_aton()、inet_addr
將IP地址從點和數字字符串轉換為結構in_addr
9.13.0.1簡介
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // ALL THESE ARE DEPRECATED! Use inet_pton() or inet_ntop() instead!! char *inet_ntoa(struct in_addr in); int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp);9.13.0.2描述
這些函數不建議使用,因為它們不處理IPv6!使用*inet_ntop()*或*inet_pton()*代替!它們包含在這里是因為它們仍然可以在野外找到。
所有這些函數都從結構in_addr(最有可能是結構sockaddr_in的一部分)轉換成點和數字格式的字符串(例如“192.168.5.10”),反之亦然。如果你有一個在命令行上傳遞的IP地址,這是獲得一個結構in_addr連接()或其他什么的最簡單方法。如果你需要更多的權力,嘗試一些DNS函數,比如gethostbyname()或者在你的本地國家嘗試一次政變。
函數inet_ntoa()將結構in_addr中的網絡地址轉換為點和數字格式的字符串。“ntoa”中的“n”代表網絡,“a”代表ASCII,這是因為歷史原因(所以它是“網絡到ASCII”——“toa”后綴在C庫中有一個類似的朋友,叫做atoi(),它將ASCII字符串轉換為整數)。
函數inet_aton()是相反的,從點和數字字符串轉換成in_addr_t(這是s_addr結構in_addr的字段類型)。
最后,函數inet_addr()是一個較舊的函數,它的功能基本上與inet_aton()相同。理論上它是不建議使用的,但是你會經常看到它,如果你使用它,警察不會來找你。
9.13.0.3返回值
inet_aton()如果地址是有效的,則返回非零;如果地址無效,則返回零。
inet_ntoa()返回靜態緩沖區中的點和數字字符串,該字符串在每次調用函數時被覆蓋。
inet_addr()將地址作為in_addr_t返回,如果有錯誤,則返回-1。(這與試圖將字符串255.255.255.255轉換為有效的IP地址的結果相同。這就是inet_aton()更好的原因。)
9.13.0.4例子
struct sockaddr_in antelope;char *some_addr;inet_aton("10.0.0.1", &antelope.sin_addr); // store IP in antelopesome_addr = inet_ntoa(antelope.sin_addr); // return the IPprintf("%s\n", some_addr); // prints "10.0.0.1"// and this call is the same as the inet_aton() call, above:antelope.sin_addr.s_addr = inet_addr("10.0.0.1");9.13.0.5另見
inet_ntop()`,`inet_pton``()`,`gethokbyname(),gethokbyaddr()9.14inet_ntop()、inet_pton()
將IP地址轉換為人類可讀的形式并返回。
9.14.0.1簡介
#include <arpa/inet.h> const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); int inet_pton(int af, const char *src, void *dst);9.14.0.2描述
這些函數用于處理人類可讀的IP地址,并將其轉換為二進制表示,用于各種函數和系統調用。“n”代表“網絡”,“p”代表“呈現”。或者“文本呈現”。但是你可以把它想象成“可打印的”。“ntop”是“網絡到可打印的”。看到了嗎?
有時,在查看IP地址時,您不想查看一堆二進制數字。您希望它以可打印的形式顯示,如192.0.2.180或2001:db8:8714:3a90::12。在這種情況下,inet_ntop()適合您。
inet_ntop()接受af參數中的地址系列(AF_INET或AF_INET6)。src參數應該是指向結構in_addr或結構in6_addr的指針,其中包含您希望轉換為字符串的地址。最后,dst和size是指向目標字符串的指針以及該字符串的最大長度。
dst字符串的最大長度應該是多少?IPv4和IPv6地址的最大長度是多少?幸運的是,有幾個宏可以幫助你。最大長度是:INET_ADDRSTRLEN和INET6_ADDRSTRLEN。
其他時候,您可能有一個包含可讀形式的IP地址的字符串,并且您希望將其打包到結構sockaddr_in或結構sockaddr_in6中。在這種情況下,相反的funcioninet_pton()是您要尋找的。
inet_pton()在af參數中也接受一個地址系列(AF_INET或AF_INET6)。src參數是指向包含可打印形式的IP地址的字符串的指針。最后,dst參數指向結果應該存儲的地方,這可能是一個結構in_addr或結構in6_addr。
這些函數不做DNS查找-為此您需要getaddrinfo()。
9.14.0.3返回值
inet_ntop()在成功時返回dst參數,在失敗時返回NULL(并設置errno)。
inet_pton()在成功時返回1。如果有錯誤(errno被設置),它返回-1;如果輸入不是有效的IP地址,它返回0。
9.14.0.4例子
// IPv4 demo of inet_ntop() and inet_pton()struct sockaddr_in sa;char str[INET_ADDRSTRLEN];// store this IP address in sa:inet_pton(AF_INET, "192.0.2.33", &(sa.sin_addr));// now get it back and print itinet_ntop(AF_INET, &(sa.sin_addr), str, INET_ADDRSTRLEN);printf("%s\n", str); // prints "192.0.2.33"// IPv6 demo of inet_ntop() and inet_pton()// (basically the same except with a bunch of 6s thrown around)struct sockaddr_in6 sa;char str[INET6_ADDRSTRLEN];// store this IP address in sa:inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &(sa.sin6_addr));// now get it back and print itinet_ntop(AF_INET6, &(sa.sin6_addr), str, INET6_ADDRSTRLEN);printf("%s\n", str); // prints "2001:db8:8714:3a90::12"// Helper function you can use://Convert a struct sockaddr address to a string, IPv4 and IPv6:char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen){ switch(sa->sa_family) { case AF_INET: inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), s, maxlen); break; case AF_INET6: inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), s, maxlen); break; default: strncpy(s, "Unknown AF", maxlen); return NULL; } return s;}9.14.0.5查看
#####################################################################################9.15聽()
告訴套接字監聽傳入連接
9.15.0.1簡介
#include <sys/socket.h> int listen(int s, int backlog);9.15.0.2描述
您可以使用套接字描述符(由套接字()系統調用生成)并告訴它偵聽傳入的連接。這就是服務器和客戶端的區別,伙計們。
積壓參數可能意味著不同的東西,這取決于你所在的系統,但大致上是指在內核開始拒絕新連接之前,你可以有多少個掛起的連接。所以當新連接進來時,你應該很快接受它們,這樣積壓就不會被填滿。試著把它設置為10左右,如果你的客戶端在重載下開始收到“連接拒絕”,把它設置得更高。
在調用listk()之前,服務器應該調用bind()將自己附加到一個特定的端口號。該端口號(在服務器的IP地址上)將是客戶端連接到的端口號。
9.15.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.15.0.4例子
struct addrinfo hints, *res;int sockfd;// first, load up address structs with getaddrinfo():memset(&hints, 0, sizeof hints);hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whicheverhints.ai_socktype = SOCK_STREAM;hints.ai_flags = AI_PASSIVE; // fill in my IP for megetaddrinfo(NULL, "3490", &hints, &res);// make a socket:sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);// bind it to the port we passed in to getaddrinfo():bind(sockfd, res->ai_addr, res->ai_addrlen);listen(sockfd, 10); // set s up to be a server (listening) socket// then have an accept() loop down here somewhere9.15.0.5另見
接受(),綁定(),套接字()
9.16 perror()
將錯誤打印為可讀字符串
9.16.0.1簡介
#include <stdio.h> #include <string.h> // for strerror() void perror(const char *s); char *strerror(int errnum);9.16.0.2說明
由于許多函數在錯誤時返回-1,并將變量errno的值設置為某個數字,如果您可以輕松地以對您有意義的形式打印該值,那將是非常好的。
幸運的是,perror()做到了這一點。如果您希望在錯誤之前打印更多的描述,您可以將參數s指向它(或者您可以將s保留為NULL,并且不會打印任何其他內容)。
簡而言之,這個函數接受errno值,如ECONNRESET,并很好地打印它們,如“對等連接重置”
函數strirror()與perror()非常相似,只是它返回一個指向給定值的錯誤消息字符串的指針(通常會傳入變量errno)。
9.16.0.3返回值
Strerror()返回指向錯誤消息字符串的指針。
9.16.0.4實例
int s;s = socket(PF_INET, SOCK_STREAM, 0);if (s == -1) { // some error has occurred // prints "socket error: " + the error message: perror("socket error");}// similarly:if (listen(s, 10) == -1) { // this prints "an error: " + the error message from errno: printf("an error: %s\n", strerror(errno));}9.16.0.5也看
伊爾諾9.17投票()
同時測試多個套接字上的事件
9.17.0.1簡介
#include <sys/poll.h> int poll(struct pollfd *ufds, unsigned int nfds, int timeout);9.17.0.2描述
這個函數與Select()非常相似,因為它們都監視事件的文件描述符集,例如傳入數據準備好recv()、套接字準備好發送()數據、帶外數據準備好recv()、錯誤等。
其基本思想是,在ufds中傳遞一個nfds結構的數組,以及一個以毫秒為單位的超時(每秒1000毫秒)。如果您想永遠等待,超時可以是負的。如果超時時沒有任何事件發生在任何套接字描述符上,將返回投票()。
每一個元素都代表一個套接字描述符,并包含以下字段: struct pollfd { int fd; // the socket descriptor short events; // bitmap of events we're interested in short revents; // when poll() returns, bitmap of events that occurred };在調用輪詢()之前,用套接字描述符加載fd(如果您將fd設置為負數,則忽略此結構圖,并將其Revents字段設置為零),然后通過按位ORing以下宏來構造事件字段:MacroDescriptionPOLLINAlert me when data is ready to recv() on this socket.POLLOUTAlert me when I can send() data to this socket without blocking.POLLPRIAlert me when out-of-band data is ready to recv() on this socket.
一旦調用返回,Revents字段將被構造為上述字段的按位或,告訴您哪些描述符實際發生了該事件。此外,這些其他字段可能存在:MacroDescriptionPOLLERRAn error has occurred on this socket.POLLHUPThe remote side of the connection hung up.POLLNVALSomething was wrong with the socket descriptor fd—maybe it’s uninitialized?
9.17.0.3返回值
返回ufds數組中發生事件的元素數;如果超時發生,這可以是零。在錯誤時也返回-1(errno將相應地設置)。
9.17.0.4例子
int s1, s2;int rv;char buf1[256], buf2[256];struct pollfd ufds[2];s1 = socket(PF_INET, SOCK_STREAM, 0);s2 = socket(PF_INET, SOCK_STREAM, 0);// pretend we've connected both to a server at this point//connect(s1, ...)...//connect(s2, ...)...// set up the array of file descriptors. in this example, we want to know when there's normal or out-of-band// data ready to be recv()'d...ufds[0].fd = s1;ufds[0].events = POLLIN | POLLPRI; // check for normal or out-of-bandufds[1].fd = s2;ufds[1].events = POLLIN; // check for just normal data// wait for events on the sockets, 3.5 second timeoutrv = poll(ufds, 2, 3500);if (rv == -1) { perror("poll"); // error occurred in poll()} else if (rv == 0) { printf("Timeout occurred! No data after 3.5 seconds.\n");} else { // check for events on s1: if (ufds[0].revents & POLLIN) { recv(s1, buf1, sizeof buf1, 0); // receive normal data } if (ufds[0].revents & POLLPRI) { recv(s1, buf1, sizeof buf1, MSG_OOB); // out-of-band data } // check for events on s2: if (ufds[1].revents & POLLIN) { recv(s1, buf2, sizeof buf2, 0); }}9.17.0.5另見
選擇()9.18 recv(), recvfrom()
在套接字上接收數據
9.18.0.1簡介
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int s, void *buf, size_t len, int flags); ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);9.18.0.2說明
一旦建立并連接了套接字,就可以使用recv()(用于TCPSOCK_STREAM套接字)和recvfrom()(用于UDPSOCK_DGRAM套接字)從遠程端讀取傳入數據。
這兩個函數都使用套接字描述符s、緩沖區buf的指針、緩沖區len的大小(以字節為單位)以及一組控制函數工作方式的標志。
另外,recvfrom()需要一個struct solkaddr*,它將告訴您數據來自哪里,并將在fromlen中填入struct solkaddr的大小。(您還必須將fromlen初始化為from或structsokaddr的大小。)
那么,您可以將哪些奇妙的標志傳遞給這個函數呢?這里有一些,但是您應該檢查您的本地手冊頁面以獲得更多信息以及您的系統實際上支持什么。您可以按位或將這些放在一起,或者如果您希望它是常規的香草recv(),只需將標志設置為0。
MacroDescriptionMSG_OOBReceive Out of Band data. This is how to get data that has been sent to you with the MSG_OOB flag in send(). As the receiving side, you will have had signal SIGURG raised telling you there is urgent data. In your handler for that signal, you could call recv() with this MSG_OOB flag.MSG_PEEKIf you want to call recv() “just for pretend”, you can call it with this flag. This will tell you what’s waiting in the buffer for when you call recv() “for real” (i.e. without the MSG_PEEK flag. It’s like a sneak preview into the next recv() call.MSG_WAITALLTell recv() to not return until all the data you specified in the len parameter. It will ignore your wishes in extreme circumstances, however, like if a signal interrupts the call or if some error occurs or if the remote side closes the connection, etc. Don’t be mad with it.
當您調用recv()時,它會阻塞,直到有一些數據要讀取。如果您不想阻塞,請將套接字設置為非阻塞,或者在調用recv()或recvfrom()之前檢查是否有傳入數據。
9.18.0.3返回值
返回實際接收的字節數(可能小于len參數中請求的字節數),或-1(并相應地設置errno)。
如果遠程端關閉了連接,recv()將返回0。這是確定遠程端是否關閉連接的正常方法。正常是好的,叛逆!
9.18.0.4實例
// stream sockets and recv()struct addrinfo hints, *res;int sockfd;char buf[512];int byte_count;// get host info, make socket, and connect itmemset(&hints, 0, sizeof hints);hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whicheverhints.ai_socktype = SOCK_STREAM;getaddrinfo("www.example.com", "3490", &hints, &res);sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);connect(sockfd, res->ai_addr, res->ai_addrlen);// all right! now that we're connected, we can receive some data!byte_count = recv(sockfd, buf, sizeof buf, 0);printf("recv()'d %d bytes of data in buf\n", byte_count);// datagram sockets and recvfrom()struct addrinfo hints, *res;int sockfd;int byte_count;socklen_t fromlen;struct sockaddr_storage addr;char buf[512];char ipstr[INET6_ADDRSTRLEN];// get host info, make socket, bind it to port 4950memset(&hints, 0, sizeof hints);hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whicheverhints.ai_socktype = SOCK_DGRAM;hints.ai_flags = AI_PASSIVE;getaddrinfo(NULL, "4950", &hints, &res);sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);bind(sockfd, res->ai_addr, res->ai_addrlen);// no need to accept(), just recvfrom():fromlen = sizeof addr;byte_count = recvfrom(sockfd, buf, sizeof buf, 0, &addr, &fromlen);printf("recv()'d %d bytes of data in buf\n", byte_count);printf("from IP address %s\n", inet_ntop(addr.ss_family, addr.ss_family == AF_INET? ((struct sockadd_in *)&addr)->sin_addr: ((struct sockadd_in6 *)&addr)->sin6_addr, ipstr, sizeof ipstr);9.18.0.5也看
發送``()``,發送(),``選擇()``,輪詢(),阻塞
9.19選擇()
檢查套接字描述符是否已準備好讀/寫
9.19.0.1簡介
#include <sys/select.h> int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); FD_SET(int fd, fd_set *set); FD_CLR(int fd, fd_set *set); FD_ISSET(int fd, fd_set *set); FD_ZERO(fd_set *set);9.19.0.2描述
Select()函數為您提供了一種同時檢查多個套接字的方法,看看它們是否有數據等待recv()d,或者您是否可以在不阻塞的情況下向它們發送()數據,或者是否發生了一些異常。
您可以使用上面的FD_SET()宏填充套接字描述符集。一旦您有了套接字,您可以將它作為以下參數之一傳遞給函數:如果您想知道套接字中的任何一個何時準備好recv()數據,請讀取;如果任何套接字準備好發送()數據,請寫入;如果您需要知道任何套接字何時發生異常(錯誤),請執行exceptfds。如果您對這些類型的事件不感興趣,這些參數中的任何一個或所有參數都可以為NULL。選擇()返回后,集合中的值將被更改,以顯示哪些可以讀取或寫入,哪些有異常。
第一個參數,n是最高編號的套接字描述符(它們只是int,記得嗎?)加1。
最后,最后是結構時間值,超時,這讓您可以告訴selc()檢查這些集合的時間。它將在超時后返回,或者當事件發生時返回,以先發生者為準。結構時間值有兩個字段:tv_sec是秒數,tv_usec加上微秒數(每秒1,000,000微秒)。
輔助宏執行以下操作:
MacroDescriptionFD_SET(int fd, fd_set *set);Add fd to the set.FD_CLR(int fd, fd_set *set);Remove fd from the set.FD_ISSET(int fd, fd_set *set);Return true if fd is in the set.FD_ZERO(fd_set *set);Clear all entries from the set.
Linux用戶請注意:Linux的Select()可以返回“準備好閱讀”,然后實際上沒有準備好閱讀,從而導致后續的read()調用被阻止。您可以通過在接收套接字上設置O_NONBLOCK標志來解決這個問題,這樣它就會出現EWOULDBLOCK錯誤,然后在出現錯誤時忽略這個錯誤。有關將套接字設置為非阻塞的更多信息,請參閱fcntl()參考頁面。
9.19.0.3返回值
成功時返回集合中描述符的數量,如果達到超時則返回0,如果出錯則返回-1(并相應地設置errno)。此外,還會修改集合以顯示哪些套接字已準備就緒。
9.19.0.4例子
int s1, s2, n;fd_set readfds;struct timeval tv;char buf1[256], buf2[256];// pretend we've connected both to a server at this point//s1 = socket(...);//s2 = socket(...);//connect(s1, ...)...//connect(s2, ...)...// clear the set ahead of timeFD_ZERO(&readfds);// add our descriptors to the setFD_SET(s1, &readfds);FD_SET(s2, &readfds);// since we got s2 second, it's the "greater", so we use that for// the n param in select()n = s2 + 1;// wait until either socket has data ready to be recv()d (timeout 10.5 secs)tv.tv_sec = 10;tv.tv_usec = 500000;rv = select(n, &readfds, NULL, NULL, &tv);if (rv == -1) { perror("select"); // error occurred in select()} else if (rv == 0) { printf("Timeout occurred! No data after 10.5 seconds.\n");} else { // one or both of the descriptors have data if (FD_ISSET(s1, &readfds)) { recv(s1, buf1, sizeof buf1, 0); } if (FD_ISSET(s2, &readfds)) { recv(s2, buf2, sizeof buf2, 0); }}9.19.0.5查看
投票()9.20 setsokopt(),getsokopt()
為套接字設置各種選項
9.20.0.1簡介
#include <sys/types.h>#include <sys/socket.h>int getsockopt(int s, int level, int optname, void *optval,socklen_t *optlen);int setsockopt(int s, int level, int optname, const void *optval,socklen_t optlen);9.20.0.2描述
套接字是相當可配置的野獸。事實上,它們是如此可配置,我甚至不打算在這里涵蓋所有這些。不管怎樣,它可能依賴于系統。但是我將談談基礎知識。
顯然,這些函數獲取并設置套接字上的某些選項。在Linux框中,所有套接字信息都在第7節的套接字手冊頁面中。(鍵入“man 7套接字”以獲取所有這些好東西。)
至于參數,s是您正在談論的套接字,級別應該設置為SOL_SOCKET。然后您將optname設置為您感興趣的名稱。同樣,請參閱您的手冊頁面了解所有選項,但以下是一些最有趣的選項:
optnameDescriptionSO_BINDTODEVICEBind this socket to a symbolic device name like eth0 instead of using bind() to bind it to an IP address. Type the command ifconfig under Unix to see the device names.SO_REUSEADDRAllows other sockets to bind() to this port, unless there is an active listening socket bound to the port already. This enables you to get around those “Address already in use” error messages when you try to restart your server after a crash.SOCK_DGRAMAllows UDP datagram (SOCK_DGRAM) sockets to send and receive packets sent to and from the broadcast address. Does nothing—NOTHING!!—to TCP stream sockets! Hahaha!
至于參數optval,它通常是指向一個int的指針,指示所討論的值。對于布爾值,零是假的,非零是真的。這是絕對的事實,除非在你的系統上有所不同。如果沒有要傳遞的參數,optval可以是NULL。
最后一個參數optlen應該設置為optval的長度,可能是sizeof(int),但是根據選項的不同而變化。請注意,在get門選擇()的情況下,這是一個指向socklen_t的指針,它指定了將存儲在optval中的最大大小對象(以防止緩沖區溢出)。get門選擇()將修改optlen的值以反映實際設置的字節數。警告:在某些系統(特別是Sun和Windows)上,選項可以是char而不是int,并且被設置為,例如,字符值為'1'而不是int值為1。同樣,請使用“man setsokpt”和“man 7套接字”查看您自己的手冊頁面以獲取更多信息!
9.20.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.20.0.4例子
int optval; int optlen; char *optval2;// set SO_REUSEADDR on a socket to true (1): optval = 1; setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);// bind a socket to a device name (might not work on all systems): optval2 = "eth1"; // 4 bytes long, so 4, below: setsockopt(s2, SOL_SOCKET, SO_BINDTODEVICE, optval2, 4);// see if the SO_BROADCAST flag is set: getsockopt(s3, SOL_SOCKET, SO_BROADCAST, &optval, &optlen); if (optval != 0) {print("SO_BROADCAST enabled on s3!\n"); }9.20.0.5另見
FCNTL()9.21發送(),發送()
通過套接字發送數據
9.21.0.1簡介
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int s, const void *buf, size_t len, int flags); ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);9.21.0.2說明
這些函數將數據發送到套接字。一般來說,sen()用于TCPSOCK_STREAM連接的套接字,sendto()用于UDPSOCK_DGRAM未連接的數據報套接字。對于未連接的套接字,每次發送數據包時都必須指定數據包的目的地,這就是為什么sendto()的最后一個參數定義數據包的去向。
對于mail()和sendto(),參數s是套接字,buf是要發送數據的指針,len是要發送的字節數,標志允許您指定有關數據如何發送的更多信息。如果您希望它是“正常”數據,請將標志設置為零。以下是一些常用的標志,但請查看您本地的“發送()手冊”頁面了解更多詳細信息:
MacroDescriptionMSG_OOBSend as “out of band” data. TCP supports this, and it’s a way to tell the receiving system that this data has a higher priority than the normal data. The receiver will receive the signal SIGURG and it can then receive this data without first receiving all the rest of the normal data in the queue.MSG_DONTROUTEDon’t send this data over a router, just keep it local.MSG_DONTWAITIf send() would block because outbound traffic is clogged, have it return EAGAIN. This is like a “enable non-blocking just for this send.” See the section on blocking for more details.MSG_NOSIGNALIf you send() to a remote host which is no longer recv()ing, you’ll typically get the signal SIGPIPE. Adding this flag prevents that signal from being raised.
9.21.0.3返回值
返回實際發送的字節數,或錯誤時的-1(errno將相應設置)。請注意,實際發送的字節數可能小于您要求它發送的字節數!有關幫助函數的解決方法,請參閱處理部分發送()的部分。
此外,如果任何一方都關閉了套接字,調用發送()的進程將獲得信號SIGPIPE。(除非用MSG_NOSIGNAL標志調用了發送()。)
9.21.0.4實例
int spatula_count = 3490; char *secret_message = "The Cheese is in The Toaster";int stream_socket, dgram_socket; struct sockaddr_in dest; int temp;// first with TCP stream sockets:// assume sockets are made and connected //stream_socket = socket(... //connect(stream_socket, ...// convert to network byte order temp = htonl(spatula_count); // send data normally: send(stream_socket, &temp, sizeof temp, 0);// send secret message out of band: send(stream_socket, secret_message, strlen(secret_message)+1, MSG_OOB);// now with UDP datagram sockets: //getaddrinfo(... //dest = ... // assume "dest" holds the address of the destination //dgram_socket = socket(...// send secret message normally: sendto(dgram_socket, secret_message, strlen(secret_message)+1, 0, (struct sockaddr*)&dest, sizeof dest);9.21.0.5也看
Recv(), recvfrom()
9.22關機()
停止在套接字上的進一步發送和接收
9.22.0.1簡介
#include <sys/socket.h>int shutdown(int s, int how);9.22.0.2描述
就是這樣!我受夠了!這個套接字上不允許再發送()了,但是我仍然想在上面接收()數據!反之亦然!我該怎么做呢?
當您關閉()一個套接字描述符時,它關閉了用于讀寫的套接字的兩邊,并釋放了套接字描述符,如果您只是想關閉一邊或另一邊,您可以使用這個關閉()調用。
至于參數,s顯然是您要執行此操作的套接字,可以使用how參數指定該操作。如何SHUT_RD以防止進一步recv()s,SHUT_WR禁止進一步發送()s,或者SHUT_RDWR兩者兼而有之。
請注意,Shutdown()不會釋放套接字描述符,因此即使套接字已經完全關閉,您最終仍必須關閉()套接字。
這是一個很少使用的系統調用。
9.22.0.3返回值
成功時返回零,錯誤時返回-1(errno將相應地設置)。
9.22.0.4例子
int s = socket(PF_INET, SOCK_STREAM, 0);// ...do some send()s and stuff in here...// and now that we're done, don't allow any more sends()s: shutdown(s, SHUT_WR);9.22.0.5另見
關閉()9.23套接字()
分配套接字描述符
9.23.0.1簡介
#include <sys/types.h>#include <sys/socket.h>int socket(int domain, int type, int protocol);9.23.0.2說明
返回一個新的套接字描述符,您可以用它來做一些特殊的事情。這通常是編寫套接字程序的龐大過程中的第一次調用,您可以使用這個結果進行后續調用,以監聽()、綁定()、接受()或各種其他函數。
在通常的使用中,您可以通過調用getaddrinfo()來獲得這些參數的值,如下面的示例所示。但是如果您真的想的話,您可以手工填寫它們。
MacroDescriptiondomaindomain describes what kind of socket you’re interested in. This can, believe me, be a wide variety of things, but since this is a socket guide, it’s going to be PF_INET for IPv4, and PF_INET6 for IPv6.typeAlso, the type parameter can be a number of things, but you’ll probably be setting it to either SOCK_STREAM for reliable TCP sockets (send(), recv()) or SOCK_DGRAM for unreliable fast UDP sockets (sendto(), recvfrom()). (Another interesting socket type is SOCK_RAW which can be used to construct packets by hand. It’s pretty cool.)protocolFinally, the protocol parameter tells which protocol to use with a certain socket type. Like I’ve already said, for instance, SOCK_STREAM uses TCP. Fortunately for you, when using SOCK_STREAM or SOCK_DGRAM, you can just set the protocol to 0, and it’ll use the proper protocol automatically. Otherwise, you can use getprotobyname() to look up the proper protocol number.
9.23.0.3返回值
將在后續調用中使用的新套接字描述符,或-1 on false(并且errno將相應地設置)。
9.23.0.4實例
struct addrinfo hints, *res; int sockfd;// first, load up address structs with getaddrinfo():memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // AF_INET, AF_INET6, or AF_UNSPEC hints.ai_socktype = SOCK_STREAM; // SOCK_STREAM or SOCK_DGRAMgetaddrinfo("www.example.com", "3490", &hints, &res);// make a socket using the information gleaned from getaddrinfo(): sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);9.23.0.5也看
接受``()``,``綁定()``,getaddrinfo(),``聽(``)9.24結構sokaddr和朋友
處理互聯網地址的結構
9.24.0.1簡介
#include <netinet/in.h>// All pointers to socket address structures are often cast to pointers// to this type before use in various functions and system calls:struct sockaddr {unsigned short sa_family; // address family, AF_xxxchar sa_data[14]; // 14 bytes of protocol address};// IPv4 AF_INET sockets:struct sockaddr_in {short sin_family; // e.g. AF_INET, AF_INET6unsigned short sin_port; // e.g. htons(3490)struct in_addr sin_addr; // see struct in_addr, belowchar sin_zero[8]; // zero this if you want to};struct in_addr {unsigned long s_addr; // load with inet_pton()};// IPv6 AF_INET6 sockets:struct sockaddr_in6 {u_int16_t sin6_family; // address family, AF_INET6u_int16_t sin6_port; // port number, Network Byte Orderu_int32_t sin6_flowinfo; // IPv6 flow informationstruct in6_addr sin6_addr; // IPv6 addressu_int32_t sin6_scope_id; // Scope ID};struct in6_addr {unsigned char s6_addr[16]; // load with inet_pton()};// General socket address holding structure, big enough to hold either// struct sockaddr_in or struct sockaddr_in6 data:struct sockaddr_storage {sa_family_t ss_family; // address family// all this is padding, implementation specific, ignore it:char __ss_pad1[_SS_PAD1SIZE];int64_t __ss_align;char __ss_pad2[_SS_PAD2SIZE];};9.24.0.2描述
這些是所有處理互聯網地址的系統和函數的基本結構。通常您會使用getaddrinfo()來填寫這些結構,然后在必要時閱讀它們。
在內存中,結構體sockaddr_in和結構體sockaddr_in6與結構體sokaddr共享相同的開始結構,您可以自由地將一種類型的指針投擲到另一種類型,而不會受到任何傷害,除了可能的宇宙末日。
在宇宙末日的事情上開玩笑…如果宇宙真的在你向一個結構sockaddr_in*投下一個結構時結束了,我向你保證這純粹是巧合,你甚至不應該擔心。
因此,記住這一點,請記住,每當一個函數說它需要一個結構sokaddr*時,您可以輕松安全地將您的結構sockaddr_in*、結構sockaddr_in6*或結構sockadd_storage*轉換為該類型。
結構sockaddr_in是用于IPv4地址(例如192.0.2.10)的結構。它持有地址系列(AF_INET)、sin_port端口和sin_addrIPv4地址。
結構體sockaddr_in中還有一個sin_zero字段,有些人聲稱必須將其設置為零。其他人對此一無所知(Linux文檔甚至根本沒有提到它),并且將其設置為零似乎實際上沒有必要。所以,如果你喜歡,使用memset()將其設置為零。
現在,這個結構in_addr在不同的系統上是一個奇怪的野獸。有時它是一個包含各種#定義和其他廢話的瘋狂聯盟。但是你應該做的是只在這個結構中使用s_addr字段,因為許多系統只實現那個。
結構sockadd_in6和結構in6_addr非常相似,只是它們用于IPv6。
結構sockaddr_storage是一個結構,當您試圖編寫與IP版本無關的代碼,并且您不知道新地址是IPv4還是IPv6時,您可以通過該結構接受()或recvfrom()。結構sockaddr_storage結構足夠大,可以容納這兩種類型,不像最初的小結構sokaddr。
9.24.0.3實例
// IPv4:struct sockaddr_in ip4addr; int s;ip4addr.sin_family = AF_INET; ip4addr.sin_port = htons(3490); inet_pton(AF_INET, "10.0.0.1", &ip4addr.sin_addr);s = socket(PF_INET, SOCK_STREAM, 0); bind(s, (struct sockaddr*)&ip4addr, sizeof ip4addr); // IPv6:struct sockaddr_in6 ip6addr; int s;ip6addr.sin6_family = AF_INET6; ip6addr.sin6_port = htons(4950); inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &ip6addr.sin6_addr);s = socket(PF_INET6, SOCK_STREAM, 0); bind(s, (struct sockaddr*)&ip6addr, sizeof ip6addr);9.24.0.4也看
接受(),綁定(),連接(),inet_aton()``,inet_ntoa()
10更多參考資料
你已經走了這么遠,現在你尖叫著要更多!你還能去哪里了解更多這些東西?
10.1書籍
對于真正的老式紙質書,試試以下幾本好書。這些書會重定向到受歡迎書商的會員鏈接,給我豐厚的回扣。如果你只是覺得慷慨,你可以向beej@beej.us捐款。:-)
Unix網絡編程,W. Richard Stevens的第1-2卷。Addison-Wesley專業和PrenticeHall出版。1-2: 978-013141155549, 978-013081081650****卷的ISBN。
**與TCP/IP聯網,**Douglas E. Comer第一卷,Pearson出版社,ISBN978-013608530051。
TCP/IP插圖,第1-3卷,W. Richard Stevens和Gary R. Wright著。Addison Wesley出版。第1、2和3卷的ISBN(和一套3卷的ISBN):978-020163346752, 978-020163354253, 978-020163495254, (978-020177631755)。
Craig Hunt的TCP/IP網絡管理。O’Reilly&Associates,Inc.出版。ISBN978-059600297856。
UNIX環境中的高級編程,W. Richard Stevens著,Addison Wesley出版社,ISBN978-032163773457。
10.2網站參考
在網絡上:
BSD套接字:一個快速和骯臟的****Primer58(Unix系統編程信息,太!)
Unix套接字常見問題59
TCP/IP****常見問題
Winsock常見問題61
以下是一些相關的維基百科頁面:
伯克利****Sockets62
網絡之間互連的協議****(IP)63
傳輸控制協議****(TCP)64
用戶數據報協議****(UDP)65
客戶端服務器
序列化67(**包裝**和拆包數據)
10.3 RFC
RFCs68——真正的污垢!這些是描述分配號碼、編程API和互聯網上使用的協議的文檔。我在這里提供了其中一些的鏈接,供你享受,所以拿一桶爆米花,戴上你的思考帽:
RFC**** 169——第一個RFC;這讓你了解了“互聯網”剛剛誕生時的樣子,并深入了解了它是如何從頭開始設計的。(顯然,這個RFC已經完全過時了!)
RFC**** 76870-用戶數據報協議(UDP)
RFC**** 79171-網絡之間互連的協議(IP)
RFC**** 79372-傳輸控制協議(TCP)
RFC**** 85473-Telnet協議
RFC**** 95974-文件傳輸協議(FTP)
RFC**** 135075-瑣碎的文件傳輸協議(TFTP)
RFC**** 145976-互聯網中繼聊天協議(IRC)
RFC**** 191877-專用互聯網的地址分配
RFC**** 213178-動態主機配置協議(DHCP)
RFC**** 261679-超文本傳輸協議(HTTP)
RFC**** 282180-簡單郵件傳輸協議(SMTP)
RFC**** 333081-特殊用途IPv4地址
RFC**** 349382-IPv6的基本套接字接口擴展
RFC**** 354283-用于IPv6的高級套接字應用程序接口(API)
RFC**** 384984-為文檔保留的IPv6地址前綴
RFC**** 392085-可擴展消息和存在協議(XMPP)
RFC**** 397786-網絡新聞傳輸協議(NNTP)
唯一的本地IPv6單播地址
RFC**** 450688-外部數據表示標準(XDR)
IETF有一個很好的在線搜索和瀏覽RFCs89的工具。
總結
以上是生活随笔為你收集整理的Beej网络编程指南《三》的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 作者:吴城文,男,清华大学计算机科学与技
- 下一篇: 成员函数在外部调用的方式总结-函数指针强