Internet domain socket
Internet domain 流 socket 是基于 TCP 的,它們提供了可靠的雙向字節流通信信道。
Internet domain 數據報 socket 是基于 UDP 的:
UNIX domain 數據報 socket 是可靠的,但是 UDP socket 則不是可靠的,數據報可能會丟失,重復,亂序
在一個 UNIX domain 數據報 socket 上發送數據會在接收 socket 的數據隊列為滿時阻塞,與之不同的是,使用 UDP 時如果進入的數據報會使接收者的隊列溢出,那么數據報就會靜默地被丟棄
網絡字節序
2 字節和 4 字節整數的大端和小端字節序:
網絡字節序采用大端。
INADDR_ANY 和 INADDR_LOOPBACK 是主機字節序,在將它們存儲進 socket 地址結構中之前需要將這些值轉換成網絡字節序。
主機序和網絡字節序之間的轉換:
#includeuint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
數據表示
readLine() 從文件描述符 fd 引用的文件中讀取字節直到碰到換行符為止。
ssize_t readLine(int fd, void *buffer, size_t n) { ssize_t numRead; size_t toRead; char *buf; char ch; if(n <= 0 || buffer == NULL) ? { ? ? ? errno = EINVAL; ? ? ? return -1; ? } ? buf = buffer; ? toRead = 0; ? for (;;) ? { ? ? ? numRead = read(fd,&ch,1); ? ? ? if(numRead == -1) ? ? ? { ? ? ? ? ? if(errno == EINTR) ? ? ? ? ? ? ? continue; ? ? ? ? ? else ? ? ? ? ? ? ? return -1; ? ? ? } ? ? ? else if(numRead == 0) ? ? ? { ? ? ? ? ? if(toRead == 0) ? ? ? ? ? ? ? return 0; ? ? ? ? ? else ? ? ? ? ? ? ? break; ? ? ? } ? ? ? ? else ? ? ? { ? ? ? ? ? if(toRead < n-1) ? ? ? ? ? { ? ? ? ? ? ? ? toRead++; ? ? ? ? ? ? ? *buf++ = ch; ? ? ? ? ? } ? ? ? ? ? if(ch == ' ') ? ? ? ? ? ? ? break; ? ? ? } ? ? ? ? } ? *buf = '?'; ? return toRead; }
Internet socket 地址
Internet domain socket 地址有兩種:IPv4 和 IPv6。
IPv4 socket 地址
IPv4 地址存儲于結構體 sockaddr_in 中:
struct in_addr { uint32_t s_addr; /* address in network byte order */ }; struct sockaddr_in{ sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; unsigned char __pad[X]; };
sin_family 總是 AF_INET
in_port_t 和 in_addr 分別是端口號和 IP 地址,它們都是網絡字節序,分別是 16 位和 32 位
IPv6 socket 地址
struct in6_addr{ uint8_t s6_addr[16]; }; struct sockaddr_in6{ sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; };
IPv6 的通配地址 0::0,換回地址為 ::1。
sockaddr_storage 結構
IPv6 socket API 中新引入了一個通用的 sockaddr_storage 結構,這個結構的空間足以容納任意類型的 socket:
#define __ss_aligntype uint32_t struct sockaddr_storage{ sa_family ss_family; __ss_aligntype __ss_slign; char __ss_padding[SS_PADSIZE]; };
主機和服務轉換函數概述
主機名和連接在網絡上的一個系統的符號標識符,服務名是端口號的符號表示。主機地址和端口的表示有下列兩種:
主機地址和端口的表示有兩種方法:
主機地址可以表示為一個二進制值或一個符號主機名或展現格式(IPv4 點分十進制,IPv6 是十六進制字符串)
端口號可以表示為一個二進制值或一個符號服務名
inet_pton() 和 inet_ntop() 函數
#includeint inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
p 表示展現 presentation ,n 表示 網絡 network
inet_pton() 將 src 包含的展現字符串轉換成網絡字節序的二進制 IP 地址,af 被指定為 AF_INET 或者 AF_INET6
inet_ntop() 執行逆向轉換, size 被指定為緩沖器的大小,如果 size 太小,那么 inet_ntop() 會返回 NULL 并將 errno 設置成 ENOSPC
緩沖器大小可以使用下面兩個宏指定:
#include#define INET_ADDRSTRLEN 16 #define INET6_ADDRSTRLEN 46
數據報 socket 客戶端/服務器示例
server
int main(int argc,char* argv[]) { struct sockaddr_in6 svaddr, claddr; int sfd, j; ssize_t numBytes; socklen_t len; char buf[BUF_SIZE]; char claddrStr[INET_ADDRSTRLEN]; sfd = socket(AF_INET6,SOCK_DGRAM,0); if(sfd ==-1) errExit("socket()"); memset(&svaddr,0,sizeof(struct sockaddr_in6)); svaddr.sin6_family = AF_INET6; svaddr.sin6_addr = in6addr_any; svaddr.sin6_port = htons(PROT_NUM); if(bind(sfd,(struct sockaddr_in6*)&svaddr,sizeof(struct sockaddr_in6)) == -1) errExit("bind()"); for (;;) { len = sizeof(struct sockaddr_in6); numBytes = recvfrom(sfd,buf,BUF_SIZE,0,(struct sockaddr*)&claddr,&len); if(numBytes == -1) errExit("recvfrom()"); if (inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr,INET6_ADDRSTRLEN) == NULL) printf("could not convert client address to string "); else printf("Sever received %ld bytes from (%s,%u) ",(long)numBytes,claddr,ntohs(claddr.sin6_port)); if(sendto(sfd,buf,numBytes,0,(struct sockaddr*)&claddr,len) != numBytes) errExit("sendto()"); } }
client
int main(int argc,char* argv[]) { struct sockaddr_in6 svaddr, claddr; int sfd, j; size_t msgLen; ssize_t numBytes; char resp[BUF_SIZE]; if(argc < 3 | strcmp(argv[1],"--help") == 0) ? { ? ? ? printf("%s host-address msg...",argv[0]); ? ? ? exit(EXIT_SUCCESS); ? } ? sfd = socket(AF_INET6,SOCK_DGRAM,0); ? if(sfd ==-1) ? ? ? errExit("socket()"); ? memset(&svaddr,0,sizeof(struct sockaddr_in6)); ? svaddr.sin6_family = AF_INET6; ? svaddr.sin6_port = htons(PROT_NUM); ? if(inet_pton(AF_INET6,argv[1],&svaddr.sin6_addr) <= 0) ? ? ? errExit("inet_pton()"); ? for (j = 2; j < argc;j++) ? { ? ? ? msgLen = strlen(argv[j]); ? ? ? if (sendto(sfd,argv[j],msgLen,0,(struct sockaddr*)&svaddr,sizeof(struct sockaddr_in6)) != msgLen) ? ? ? ? ? errExit("sendto()"); ? ? ? numBytes = recvfrom(sfd,resp,BUF_SIZE,0,NULL,NULL); ? ? ? if(numBytes == -1) ? ? ? ? ? errExit("recvfrom()"); ? ? ? printf("Respone %d : %.*s ",j-1,(int)numBytes,resp); ? } ? exit(EXIT_SUCCESS); }
域名系統(DNS)
DNS 出現以前,主機名和 IP 地址之間的映射關系是在一個手工維護的本地文件 /etc/hosts 中進行定義的:
127.0.0.1 localhost ::1 ip6-localhost ip6-loopback
gethostbyname() 或者 getaddrinfo() 通過搜索這個文件并找出與規范主機名或其中一個別名匹配的記錄來獲取一個 IP 地址。
DNS 設計:
將主機名組織在一個層級名空間中,每個節點有一個標簽,該標簽最多能包含 63 個字符,層級的根是一個無名的節點,稱為 "匿名節點"
一個節點的域名由該節點到根節點的路徑中所有節點的名字連接而成,各個名字之間使用 . 分隔
完全限定域名,如 www.kernel.org. ,標識出了層級中的一臺主機,區分一個完全限定域名的方法是看名字是否以.結尾,但是在很多情況下這個點會被省略
沒有一個組織或系統會管理整個層級,相反,存在一個 DNS 服務器層級,每臺服務器管理樹的一個分支(區域)
當一個程序調用 getaddrinfo() 來解析一個域名時,getaddrinfo() 會使用一組庫函數來與各地的 DNS 服務器通信,如果這個服務器無法提供所需要的信息,那么它就會與位于層級中的其他 DNS 服務器進行通信以便獲取信息,這個過程可能花費很多時間,DNS 采用了緩存技術以節省時間
遞歸和迭代的解析請求
DNS 解析請求可以分為:遞歸和迭代。
遞歸請求:請求者要求服務器處理整個解析任務,包括在必要時候與其它 DNS 服務器進行通信任務。當位于本地主機上的一個應用程序調用 getaddrinfo() 時,該函數會與本地 DNS 服務器發起一個遞歸請求,如果本地 DNS 服務器自己沒有相關信息來完成解析,那么它就會迭代地解析這個域名。
迭代解析:假設要解析 www.otago.ac.nz,首先與每個 DNS 服務器都知道的一小組根名字服務器中的一個進行通信,根名字服務器會告訴本 DNS 服務器到其中一臺 nz DNS 服務器上查詢,然后本地 DNS 服務器會在 nz 服務器上查詢名字 www.otago.ac.nz,并收到一個到 ac.nz 服務器上查詢的響應,之后本地 DNS 服務器會在 ac.nz 服務器上查詢名字 www.otago.ac.nz 并告知查詢 otago.ac.nz 服務器,最后本地 DNS 服務器會在 otago.ac.nz 服務器上查詢 www.otago.ac.nz 并獲取所需的 IP 地址。
向 gethostbyname() 傳遞一個不完整的域名,那么解析器在解析之前會嘗試補齊。域名補全規則在 /etc/resolv.conf 中定義,默認情況下,至少會使用本機的域名來補全,例如,登錄機器 oghma.otago.ac.nz 并輸入 ssh octavo 得到的 DNS 查詢將會以 octavo.otago.ac.nz 作為其名字。
頂級域
緊跟在匿名根節點下面的節點稱為頂級域(TLD),TLD 分為兩類:通用的和國家的。
/etc/services 文件
端口號和服務名記錄在 /etc/services 中,getaddrinfo() 和 getnameinfo() 會使用這個文件中的信息在服務名和端口號之間進行轉換。
獨立于協議的主機和服務轉換
getaddrinfo() 將主機和服務名轉換成 IP 地址和端口號,它作為過時的 gethostbyname() 和 getservername() 接替者。
getnameinfo() 是 getaddrinfo() 的逆函數,將一個 socket 地址結構轉換成包含對應主機和服務名的字符串,是過時的 gethostbyaddr() 和 getserverbyport() 的等價物。
getaddrinfo() 函數
#include#include #include int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints,struct addrinfo **res);
給定一個主機名和服務器名,getaddrinfo() 返回一個 socket 地址結構列表,每個結構都包含一個地址和端口號
成功時返回0,失敗時返回非 0
host 包含一個主機名或者一個以 IPv4 點分十進制標記或者 IPv6 十六進制字符串標記的數值地址字符串
service 包含一個服務名或一個十進制端口號
hints 指向一個 addrinfo 結構:
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; };
res 返回一個結構列表而不是單個結構,因為與在 host、 service、 hints、 指定的標準對應的主機和服務組合可能有多個。例如,查詢多個網絡接口的主機時可能返回多個地址結構,此外,如果將 hints.ai_sockettype 指定 為0,那么可能返回兩個結構:一個用于 SOCK_DGRAM socket 和 SOCKET_STREAM socket,前提是給定的 service 同時對 TCP 和 UDP 可用
hints 參數
hints 參數為如何選擇 getaddrinfo() 返回的 socket 地址結構指定了更多標準。當用作 hints 參數時只能設置 addrinfo 結構的 ai_flags、ai_family、ai_socktype、ai_protocol 字段,其他字段未使用,并將根據具體情況初始化為 0 或者 NULL。
hints.ai_family 返回的 socket 地址結構的域,取值可以是 AF_INET 或者 AF_INET6。如果需要獲取所有種類 socket 地址,那么可以將這個字段設置為 AF_UNSPEC。
hints.ai_socktype 字段指定了使用返回的 socket 地址結構的 socket 類型。如果將這個字段指定為 SOCK_DGRAM,那么查詢將會在 UDP 服務上執行,如果指定了 SOCK_STREAM,那么將返回一個 TCP 服務查詢,如果將其指定為 0,那么任意類型的 socket 都是可接受的。
hints.ai_protocol 字段為返回的地址結構選擇了 socket 協議,這個字段設置為 0,表示調用者接受任何協議。
hints.ai_flags 字段是一個位掩碼,它會改變 getaddrinfo() 的行為,取值為:
AI_ADDRCONFIG:在本地系統上至少配置一個 IPv4 地址時返回 IPv4 地址(不是 IPv4 回環地址),在本地系統上至少配置一個 IPv6 地址時返回 IPv6 地址(不是 IPv6 回環地址)
AI_ALL:參見 AI_V4MAPPED
AI_CANONNAME:如果 host 不為 NULL,返回一個指向 null 結尾的字符串,該字符串包含了主機的規范名,這個指針會在通過 result 返回的第一個 addrinfo 結構中 ai_canoname 字段指向的緩沖器中返回
AI_NUMERICHOST:強制將 host 解釋成一個數值地址字符串,這個常量用于在不必要解析名字時防止進行名字解析,因為名字解析可能會花費比較長的時間
AI_NUMERICSERV:將 service 解釋成一個數值端口號,這個標記用于防止調用任意的名字解析服務,因為當 service 為一個數值字符串時這種調用是沒有必要的
AI_PASSIVE:返回一個適合進行被動式打開(即一個監聽 socket)的 socket 地址結構,此時,host 應該是 NULL,通過 result 返回的 socket 地址結構 IP 地址部分將會包含一耳光通配 IP 地址(INADDR_ANY 或者 IN6ADDR_ANY_INIT)。如果沒有設置這個標記,那么通過 res 返回的地址結構中的 IP 地址將會被設置成回環 IP 地址(INADDR_LOOPBACK 或者 IN6ADDR_LOOPBACK_INIT)
AI_V4MAPPED:如果在 hints 的 ai_family 中指定了 AF_INET6,那么在沒有找到匹配的 IPv6 地址時應該在 res 返回 IPv4 映射的 IPv6 地址。如果同時指定了AI_ALL 和 AI_V4MAPPED,那么 res 中同時返回 IPv6 和 IPv4 地址,IPv4 地址會被返回成 IPv4 映射的 IPv6 地址
釋放 addrinfo 列表 freeaddrinfo()
#include#include #include void freeaddrinfo(struct addrinfo *res);
getaddrinfo() 函數會動態地為 res 引用的所有結構分配內存,其結果是調用者必須要在不需要使用這些結構時釋放它們,使用 freeaddrinfo() 來執行釋放的任務
錯誤診斷 gai_strerror()
getaddrinfo() 在發生錯誤時返回下面的一個錯誤碼:
#include#include #include const char *gai_strerror(int errcode);
gai_strerror() 返回一個描述錯誤的字符串
getnameinfo() 函數
getnameinfo() 是 getaddrinfo() 的逆函數。給定一個 socket 地址結構它會返回一個包含對應的主機和服務名的字符串或者無法解析名字時返回一個等價的數值。
#include#include int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,char *host, socklen_t hostlen,char *service, socklen_t servlen, int flags);
addr 指向待轉換的 socket 地址結構,長度為 addrlen
得到的主機和服務名是以 null 結尾的字符串,它們會被存儲在 host 和 service 指向的緩沖器中,調用者必須要為這些緩沖器分配空間并將它們的大小傳入 hostlen 和 servlen ,NI_MAXHOST 指出了返回的主機名字符串的最大字節數,其取值為 1025,NI_MAXSERV 指出了返回服務名字符串的最大字節數,其取值為 32
如果不想獲取主機名,可以將 host 指定為 NULL 并且將 hostlen 指定為 0,如果不想獲取服務名,可以將 service 指定為 NULL 并且將 servlen 指定為 0,但是 host 和 service 中至少有一個必須非 NULL
flags 是一個掩碼,控制著 getnameinfo() 的行為,取值為:
NI_DGRAM:默認情況下,getnameinfo() 返回 TCP 服務對應的名字,NI_DGRAM 標記強制返回 UDP 服務的名字
NI_NAMEREQD:默認情況下,如果無法解析主機名,那么在 host 中返回一個數值地址字符串,如果指定了 NI_NAMEREQD,就會返回一個錯誤 EAI_NONAME
NI_NOFQDN:在默認情況下會返回主機的完全限定域名,指定 NI_NOFQDN 標記會導致當主機位于局域網中時只返回名字的第一部分(即主機名)
NI_NUMERICHOST:強制在 host 中返回一個數值地址字符串,這個標記在需要避免可能耗時較長的 DNS 服務器調用時是比較有用的
NI_NUMERICSERV:強制在 service 中返回一個十進制端口號字符串,這個標記在知道端口號不對應于服務器名時,如它是一個由內核分配給 socket 的臨時端口號,以及需要避免不必要的搜索 /etc/service 的低效性時是比較有用的
流式 socket 客戶端/服務器示例
server
int main(int argc,char* argv[]) { uint32_t seqNum; char reqLenStr[INT_LEN]; char seqNumStr[INT_LEN]; struct sockaddr_storage claddr; int lfd, cfd, optval, reqLen; socklen_t addrlen; struct addrinfo hints; struct addrinfo *result, *rp; #define ADDRSTRLEN (NI_MAXHOST + NI_MAXSERV +10) char addrStr[ADDRSTRLEN]; char host[NI_MAXHOST]; char service[NI_MAXSERV]; if(argc > 1 && strcmp(argv[1],"--help") == 0) { printf("%s [init-seq-num] ",argv[0]); exit(EXIT_SUCCESS); } seqNum = (argc > 1) ? atoi(argv[1]) : 0; if(signal(SIGPIPE,SIG_IGN) == SIG_ERR) errExit("signal()"); memset(&hints,0,sizeof(struct addrinfo)); hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; if(getaddrinfo(NULL,PORT_NUM,&hints,&result) != 0) errExit("getaddrinfo()"); optval = 1; for (rp = result; rp != NULL;rp = rp->ai_next) { lfd = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol); if(lfd == -1) continue; if(setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1) errExit("setsockopt()"); if(bind(lfd,rp->ai_addr,rp->ai_addrlen) == 0) break; close(lfd); } if(rp == NULL) errExit("could not bind socket any address"); if(listen(lfd,BACKLOG) == -1) errExit("listen()"); freeaddrinfo(result); for (;;) { addrlen = sizeof(struct sockaddr_storage); cfd = accept(lfd,(struct sockaddr*)&claddr,&addrlen); if(cfd == -1) { printf("accept "); continue; } if(getnameinfo((struct sockaddr*)&claddr,addrlen,host,NI_MAXHOST,service,NI_MAXSERV,0) == 0) snprintf(addrStr,ADDRSTRLEN,"(%s,%s)",host,service); else snprintf(addrStr,ADDRSTRLEN,"(?UNKNOWN?)"); if(readLine(cfd,reqLenStr,INT_LEN) <= 0) ? ? ? { ? ? ? ? ? close(cfd); ? ? ? ? ? continue; ? ? ? } ? ? ? snprintf(seqNumStr,INT_LEN,"%d ",seqNum); ? ? ? if(write(cfd,&seqNumStr,strlen(seqNumStr)) != strlen(seqNumStr)) ? ? ? ? ? printf("Error on write "); ? ? ? seqNum += reqLen; ? ? ? if(close(cfd) == -1) ? ? ? ? ? printf("Error on close"); ? } }
client
int main(int argc,char* argv[]) { char *reqLenStr; char seqNumStr[INT_LEN]; int cfd; ssize_t numRead; struct addrinfo hints; struct addrinfo *result, *rp; if(argc < 2 || strcmp(argv[1],"--help") == 0) ? { ? ? ? printf("%s server-host [sequence-len] ",argv[0]); ? ? ? exit(EXIT_SUCCESS); ? } ? memset(&hints,0,sizeof(struct addrinfo)); ? hints.ai_canonname = NULL; ? hints.ai_addr = NULL; ? hints.ai_next = NULL; ? hints.ai_socktype = SOCK_STREAM; ? hints.ai_family = AF_UNSPEC; ? hints.ai_flags = AI_NUMERICSERV; ? if(getaddrinfo(NULL,PORT_NUM,&hints,&result) != 0) ? ? ? errExit("getaddrinfo()"); ? for (rp = result; rp != NULL;rp = rp->ai_next) { cfd = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol); if(cfd == -1) continue; if(connect(cfd,rp->ai_addr,rp->ai_addrlen) != -1) break; close(cfd); } if(rp == NULL) errExit("could not bind socket any address"); freeaddrinfo(result); reqLenStr = (argc > 2) ? argv[2] : "1"; if(write(cfd,reqLenStr,strlen(reqLenStr)) != strlen(reqLenStr)) errExit("write()"); if(write(cfd," ",1) != 1) errExit("write()"); numRead = readLine(cfd, seqNumStr, INT_LEN); if(numRead == -1) errExit("readLine()"); if(numRead == 0) errExit("Unexpected EOF from server"); printf("Sequence number: %s ",seqNumStr); exit(EXIT_SUCCESS); }
Internet domain socket 庫
int inetConnect(const char *host, const char *service, int type) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s; memset(&hints,0,sizeof(struct addrinfo)); hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; hints.ai_socktype = type; hints.ai_family = AF_UNSPEC; s = getaddrinfo(host, service, &hints, &result); if(s != 0) { errno = ENOSYS; return -1; } for (rp = result; rp != NULL;rp = rp->ai_next) { sfd = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol); if(sfd == -1) continue; if(connect(sfd,rp->ai_addr,rp->ai_addrlen) != -1) break; close(sfd); } freeaddrinfo(result); return rp == NULL ? -1 : sfd; } static int inetPassiveSocket(const char* service,int type,socklen_t* addrLen,Boolean doListen,int backlog) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s, optval; memset(&hints,0,sizeof(struct addrinfo)); hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; hints.ai_socktype = type; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE; s = getaddrinfo(NULL, service, &hints, &result); if(s != 0) { return -1; } optval = 1; for (rp = result; rp != NULL;rp = rp->ai_next) { sfd = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol); if(sfd == -1) continue; if(doListen) { if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1) { close(sfd); freeaddrinfo(result); return -1; } } if(bind(sfd,rp->ai_addr,rp->ai_addrlen) ==0) break; close(sfd); } if(rp != NULL && addrLen != NULL) { *addrLen = rp->ai_addrlen; } freeaddrinfo(result); return rp == NULL ? -1 : sfd; } int inetListen(const char *service, int backlog, socklen_t *addrlen) { return inetPassiveSocket(service,SOCK_STREAM,addrlen,True,backlog); } int inetBind(const char *service, int type, socklen_t *addrlen) { return inetPassiveSocket(service,type,addrlen,False,0); } char *inetAddressStr(const struct sockaddr *addr, socklen_t addrLen, char *addrStr, int addrStrLen) { char host[NI_MAXHOST], service[NI_MAXSERV]; if(getnameinfo(addr,addrLen,host,NI_MAXHOST,service,NI_MAXSERV,NI_NUMERICSERV) == 0) snprintf(addrStr,addrStrLen,("%s,%s"),host,service); else snprintf(addrStr,addrStrLen,"?UNKNOWN?"); addrStr[addrLen - 1] = '?'; return addrStr; }
過時的主機和服務轉換 API
inet_aton() 和 inet_ntoa()
#include#include #include int inet_aton(const char *str, struct in_addr *addr);
inet_aton() 將 str 指向的點分十進制字符串轉換成一個網絡字節序的 IPv4 地址,轉換得到的地址將會返回 addr 指向的結構
成功時返回 1,str 無效時返回 0
str 字符串的數值部分無需是十進制的,它可以是八進制的,也可以是十六進制的
#include#include #include char *inet_ntoa(struct in_addr in);
inet_ntoa() 返回一個指向包含用點分十進制標記法標記的地址的字符串指針
inet_ntoa() 返回的字符串是靜態分配的,因此會被后續調用所覆蓋
gethostbyname() 和 gethostbyaddr()
#includeextern int h_errno; struct hostent *gethostbyname(const char *name); struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
gethostbyname() 解析由 name 給出的主機名,并返回一個指向靜態分配的包含了主機名相關信息的 hostent 結構的指針
gethostbyaddr() 執行 gethostbyname() 的逆操作,給定一個二進制 IP 地址,它會返回一個包含與配置了該地址的主機相關的信息的 hostent 結構
發生錯誤時,或者無法解析一個名字時,gethostbyname() 和 gethostbyaddr() 都會返回 NULL,并設置全局變量 h_errno。這個變量類似于 errno,herror() 和 hstrerror() 類似于 perror() 和 strerror()
#includeextern int h_errno; void herror(const char *s); const char *hstrerror(int err);
hostent 結構:
struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */
h_name 返回主機的官方名字,是一個以 null 結尾的字符串
h_aliases 指向一個指針數組,數組中的指針指向以 null 結尾的包含了主機名的別名的字符串
h_addr_list 是一個指針數組,數組中的指針指向這個主機的 IP 地址結構,這個列表由 in_addr 和 in6_addr 結構構成,通過 h_addrtype 字段可以確定這些結構的類型,其取值為 AF_INET 或 AF_INET6,h_length 字段可以確定這些結構的長度
getservbyname() 和 getservbyport()
#includestruct servent *getservbyname(const char *name, const char *proto);
getservbyname() 和 getservbyport() 都是從 /etc/services 文件中獲取記錄,現在已經被 getaddrinfo() 和 getnameinfo() 取代
getservbyname() 查詢服務名或者其中一個別名與 name 匹配以及協議與 proto 匹配的記錄,proto 可以是 TCP 或者 UDP,或者設置為 NULL
如果找到了一個匹配的記錄,那么 getservbyname() 會返回一個指向靜態分配的結構指針:
struct servent { char *s_name; /* official service name */ char **s_aliases; /* alias list */ int s_port; /* port number */ char *s_proto; /* protocol to use */ };
一般調用 getservbyname() 只是為了獲取端口號,即 s_port 字段
#includestruct servent *getservbyport(int port, const char *proto);
getservbyport() 執行 getservbyname() 的逆操作,它返回一個 servent 記錄,該記錄包含了 /etc/services 文件中端口號與 port 匹配的記錄相關的信息
UNIX 與 Internet domain socket 比較
編寫只使用 Internet domain socket 的應用程序即可以運行在同一主機上,也可以運行在網絡中的不同主機上。
UNIX domain socket 只能用于同一系統上的應用程序間通信,使用 UNIX domain socket 的幾個原因:
在一些實現上,UNIX domain socket 速度要比 Internet domain socket 快
可以使用目錄權限來控制對 UNIX domain socket 的訪問,這樣只有運行于指定的用戶或組 ID 下的應用程序才能夠連接到一個監聽流 socket 或向一個數據報 socket 發送一個數據報。
審核編輯:劉清
-
緩沖器
+關注
關注
6文章
1921瀏覽量
45473 -
Linux系統
+關注
關注
4文章
593瀏覽量
27392 -
DNS
+關注
關注
0文章
218瀏覽量
19828 -
TCP通信
+關注
關注
0文章
146瀏覽量
4221
原文標題:Linux Internet Domain應用編程
文章出處:【微信號:嵌入式應用研究院,微信公眾號:嵌入式應用研究院】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論