Internet domain socket
Internet domain 流 socket 是基于 TCP 的,它們提供了可靠的雙向字節(jié)流通信信道。
Internet domain 數(shù)據(jù)報(bào) socket 是基于 UDP 的:
UNIX domain 數(shù)據(jù)報(bào) socket 是可靠的,但是 UDP socket 則不是可靠的,數(shù)據(jù)報(bào)可能會(huì)丟失,重復(fù),亂序
在一個(gè) UNIX domain 數(shù)據(jù)報(bào) socket 上發(fā)送數(shù)據(jù)會(huì)在接收 socket 的數(shù)據(jù)隊(duì)列為滿時(shí)阻塞,與之不同的是,使用 UDP 時(shí)如果進(jìn)入的數(shù)據(jù)報(bào)會(huì)使接收者的隊(duì)列溢出,那么數(shù)據(jù)報(bào)就會(huì)靜默地被丟棄
網(wǎng)絡(luò)字節(jié)序
2 字節(jié)和 4 字節(jié)整數(shù)的大端和小端字節(jié)序:
網(wǎng)絡(luò)字節(jié)序采用大端。
INADDR_ANY 和 INADDR_LOOPBACK 是主機(jī)字節(jié)序,在將它們存儲(chǔ)進(jìn) socket 地址結(jié)構(gòu)中之前需要將這些值轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序。
主機(jī)序和網(wǎng)絡(luò)字節(jié)序之間的轉(zhuǎn)換:
#includeuint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
數(shù)據(jù)表示
readLine() 從文件描述符 fd 引用的文件中讀取字節(jié)直到碰到換行符為止。
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 地址存儲(chǔ)于結(jié)構(gòu)體 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 分別是端口號(hào)和 IP 地址,它們都是網(wǎng)絡(luò)字節(jié)序,分別是 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 結(jié)構(gòu)
IPv6 socket API 中新引入了一個(gè)通用的 sockaddr_storage 結(jié)構(gòu),這個(gè)結(jié)構(gòu)的空間足以容納任意類型的 socket:
#define __ss_aligntype uint32_t struct sockaddr_storage{ sa_family ss_family; __ss_aligntype __ss_slign; char __ss_padding[SS_PADSIZE]; };
主機(jī)和服務(wù)轉(zhuǎn)換函數(shù)概述
主機(jī)名和連接在網(wǎng)絡(luò)上的一個(gè)系統(tǒng)的符號(hào)標(biāo)識(shí)符,服務(wù)名是端口號(hào)的符號(hào)表示。主機(jī)地址和端口的表示有下列兩種:
主機(jī)地址和端口的表示有兩種方法:
主機(jī)地址可以表示為一個(gè)二進(jìn)制值或一個(gè)符號(hào)主機(jī)名或展現(xiàn)格式(IPv4 點(diǎn)分十進(jìn)制,IPv6 是十六進(jìn)制字符串)
端口號(hào)可以表示為一個(gè)二進(jìn)制值或一個(gè)符號(hào)服務(wù)名
inet_pton() 和 inet_ntop() 函數(shù)
#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 表示展現(xiàn) presentation ,n 表示 網(wǎng)絡(luò) network
inet_pton() 將 src 包含的展現(xiàn)字符串轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序的二進(jìn)制 IP 地址,af 被指定為 AF_INET 或者 AF_INET6
inet_ntop() 執(zhí)行逆向轉(zhuǎn)換, size 被指定為緩沖器的大小,如果 size 太小,那么 inet_ntop() 會(huì)返回 NULL 并將 errno 設(shè)置成 ENOSPC
緩沖器大小可以使用下面兩個(gè)宏指定:
#include#define INET_ADDRSTRLEN 16 #define INET6_ADDRSTRLEN 46
數(shù)據(jù)報(bào) socket 客戶端/服務(wù)器示例
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); }
域名系統(tǒng)(DNS)
DNS 出現(xiàn)以前,主機(jī)名和 IP 地址之間的映射關(guān)系是在一個(gè)手工維護(hù)的本地文件 /etc/hosts 中進(jìn)行定義的:
127.0.0.1 localhost ::1 ip6-localhost ip6-loopback
gethostbyname() 或者 getaddrinfo() 通過搜索這個(gè)文件并找出與規(guī)范主機(jī)名或其中一個(gè)別名匹配的記錄來獲取一個(gè) IP 地址。
DNS 設(shè)計(jì):
將主機(jī)名組織在一個(gè)層級(jí)名空間中,每個(gè)節(jié)點(diǎn)有一個(gè)標(biāo)簽,該標(biāo)簽最多能包含 63 個(gè)字符,層級(jí)的根是一個(gè)無名的節(jié)點(diǎn),稱為 "匿名節(jié)點(diǎn)"
一個(gè)節(jié)點(diǎn)的域名由該節(jié)點(diǎn)到根節(jié)點(diǎn)的路徑中所有節(jié)點(diǎn)的名字連接而成,各個(gè)名字之間使用 . 分隔
完全限定域名,如 www.kernel.org. ,標(biāo)識(shí)出了層級(jí)中的一臺(tái)主機(jī),區(qū)分一個(gè)完全限定域名的方法是看名字是否以.結(jié)尾,但是在很多情況下這個(gè)點(diǎn)會(huì)被省略
沒有一個(gè)組織或系統(tǒng)會(huì)管理整個(gè)層級(jí),相反,存在一個(gè) DNS 服務(wù)器層級(jí),每臺(tái)服務(wù)器管理樹的一個(gè)分支(區(qū)域)
當(dāng)一個(gè)程序調(diào)用 getaddrinfo() 來解析一個(gè)域名時(shí),getaddrinfo() 會(huì)使用一組庫函數(shù)來與各地的 DNS 服務(wù)器通信,如果這個(gè)服務(wù)器無法提供所需要的信息,那么它就會(huì)與位于層級(jí)中的其他 DNS 服務(wù)器進(jìn)行通信以便獲取信息,這個(gè)過程可能花費(fèi)很多時(shí)間,DNS 采用了緩存技術(shù)以節(jié)省時(shí)間
遞歸和迭代的解析請求
DNS 解析請求可以分為:遞歸和迭代。
遞歸請求:請求者要求服務(wù)器處理整個(gè)解析任務(wù),包括在必要時(shí)候與其它 DNS 服務(wù)器進(jìn)行通信任務(wù)。當(dāng)位于本地主機(jī)上的一個(gè)應(yīng)用程序調(diào)用 getaddrinfo() 時(shí),該函數(shù)會(huì)與本地 DNS 服務(wù)器發(fā)起一個(gè)遞歸請求,如果本地 DNS 服務(wù)器自己沒有相關(guān)信息來完成解析,那么它就會(huì)迭代地解析這個(gè)域名。
迭代解析:假設(shè)要解析 www.otago.ac.nz,首先與每個(gè) DNS 服務(wù)器都知道的一小組根名字服務(wù)器中的一個(gè)進(jìn)行通信,根名字服務(wù)器會(huì)告訴本 DNS 服務(wù)器到其中一臺(tái) nz DNS 服務(wù)器上查詢,然后本地 DNS 服務(wù)器會(huì)在 nz 服務(wù)器上查詢名字 www.otago.ac.nz,并收到一個(gè)到 ac.nz 服務(wù)器上查詢的響應(yīng),之后本地 DNS 服務(wù)器會(huì)在 ac.nz 服務(wù)器上查詢名字 www.otago.ac.nz 并告知查詢 otago.ac.nz 服務(wù)器,最后本地 DNS 服務(wù)器會(huì)在 otago.ac.nz 服務(wù)器上查詢 www.otago.ac.nz 并獲取所需的 IP 地址。
向 gethostbyname() 傳遞一個(gè)不完整的域名,那么解析器在解析之前會(huì)嘗試補(bǔ)齊。域名補(bǔ)全規(guī)則在 /etc/resolv.conf 中定義,默認(rèn)情況下,至少會(huì)使用本機(jī)的域名來補(bǔ)全,例如,登錄機(jī)器 oghma.otago.ac.nz 并輸入 ssh octavo 得到的 DNS 查詢將會(huì)以 octavo.otago.ac.nz 作為其名字。
頂級(jí)域
緊跟在匿名根節(jié)點(diǎn)下面的節(jié)點(diǎn)稱為頂級(jí)域(TLD),TLD 分為兩類:通用的和國家的。
/etc/services 文件
端口號(hào)和服務(wù)名記錄在 /etc/services 中,getaddrinfo() 和 getnameinfo() 會(huì)使用這個(gè)文件中的信息在服務(wù)名和端口號(hào)之間進(jìn)行轉(zhuǎn)換。
獨(dú)立于協(xié)議的主機(jī)和服務(wù)轉(zhuǎn)換
getaddrinfo() 將主機(jī)和服務(wù)名轉(zhuǎn)換成 IP 地址和端口號(hào),它作為過時(shí)的 gethostbyname() 和 getservername() 接替者。
getnameinfo() 是 getaddrinfo() 的逆函數(shù),將一個(gè) socket 地址結(jié)構(gòu)轉(zhuǎn)換成包含對應(yīng)主機(jī)和服務(wù)名的字符串,是過時(shí)的 gethostbyaddr() 和 getserverbyport() 的等價(jià)物。
getaddrinfo() 函數(shù)
#include#include #include int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints,struct addrinfo **res);
給定一個(gè)主機(jī)名和服務(wù)器名,getaddrinfo() 返回一個(gè) socket 地址結(jié)構(gòu)列表,每個(gè)結(jié)構(gòu)都包含一個(gè)地址和端口號(hào)
成功時(shí)返回0,失敗時(shí)返回非 0
host 包含一個(gè)主機(jī)名或者一個(gè)以 IPv4 點(diǎn)分十進(jìn)制標(biāo)記或者 IPv6 十六進(jìn)制字符串標(biāo)記的數(shù)值地址字符串
service 包含一個(gè)服務(wù)名或一個(gè)十進(jìn)制端口號(hào)
hints 指向一個(gè) addrinfo 結(jié)構(gòu):
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 返回一個(gè)結(jié)構(gòu)列表而不是單個(gè)結(jié)構(gòu),因?yàn)榕c在 host、 service、 hints、 指定的標(biāo)準(zhǔn)對應(yīng)的主機(jī)和服務(wù)組合可能有多個(gè)。例如,查詢多個(gè)網(wǎng)絡(luò)接口的主機(jī)時(shí)可能返回多個(gè)地址結(jié)構(gòu),此外,如果將 hints.ai_sockettype 指定 為0,那么可能返回兩個(gè)結(jié)構(gòu):一個(gè)用于 SOCK_DGRAM socket 和 SOCKET_STREAM socket,前提是給定的 service 同時(shí)對 TCP 和 UDP 可用
hints 參數(shù)
hints 參數(shù)為如何選擇 getaddrinfo() 返回的 socket 地址結(jié)構(gòu)指定了更多標(biāo)準(zhǔn)。當(dāng)用作 hints 參數(shù)時(shí)只能設(shè)置 addrinfo 結(jié)構(gòu)的 ai_flags、ai_family、ai_socktype、ai_protocol 字段,其他字段未使用,并將根據(jù)具體情況初始化為 0 或者 NULL。
hints.ai_family 返回的 socket 地址結(jié)構(gòu)的域,取值可以是 AF_INET 或者 AF_INET6。如果需要獲取所有種類 socket 地址,那么可以將這個(gè)字段設(shè)置為 AF_UNSPEC。
hints.ai_socktype 字段指定了使用返回的 socket 地址結(jié)構(gòu)的 socket 類型。如果將這個(gè)字段指定為 SOCK_DGRAM,那么查詢將會(huì)在 UDP 服務(wù)上執(zhí)行,如果指定了 SOCK_STREAM,那么將返回一個(gè) TCP 服務(wù)查詢,如果將其指定為 0,那么任意類型的 socket 都是可接受的。
hints.ai_protocol 字段為返回的地址結(jié)構(gòu)選擇了 socket 協(xié)議,這個(gè)字段設(shè)置為 0,表示調(diào)用者接受任何協(xié)議。
hints.ai_flags 字段是一個(gè)位掩碼,它會(huì)改變 getaddrinfo() 的行為,取值為:
AI_ADDRCONFIG:在本地系統(tǒng)上至少配置一個(gè) IPv4 地址時(shí)返回 IPv4 地址(不是 IPv4 回環(huán)地址),在本地系統(tǒng)上至少配置一個(gè) IPv6 地址時(shí)返回 IPv6 地址(不是 IPv6 回環(huán)地址)
AI_ALL:參見 AI_V4MAPPED
AI_CANONNAME:如果 host 不為 NULL,返回一個(gè)指向 null 結(jié)尾的字符串,該字符串包含了主機(jī)的規(guī)范名,這個(gè)指針會(huì)在通過 result 返回的第一個(gè) addrinfo 結(jié)構(gòu)中 ai_canoname 字段指向的緩沖器中返回
AI_NUMERICHOST:強(qiáng)制將 host 解釋成一個(gè)數(shù)值地址字符串,這個(gè)常量用于在不必要解析名字時(shí)防止進(jìn)行名字解析,因?yàn)槊纸馕隹赡軙?huì)花費(fèi)比較長的時(shí)間
AI_NUMERICSERV:將 service 解釋成一個(gè)數(shù)值端口號(hào),這個(gè)標(biāo)記用于防止調(diào)用任意的名字解析服務(wù),因?yàn)楫?dāng) service 為一個(gè)數(shù)值字符串時(shí)這種調(diào)用是沒有必要的
AI_PASSIVE:返回一個(gè)適合進(jìn)行被動(dòng)式打開(即一個(gè)監(jiān)聽 socket)的 socket 地址結(jié)構(gòu),此時(shí),host 應(yīng)該是 NULL,通過 result 返回的 socket 地址結(jié)構(gòu) IP 地址部分將會(huì)包含一耳光通配 IP 地址(INADDR_ANY 或者 IN6ADDR_ANY_INIT)。如果沒有設(shè)置這個(gè)標(biāo)記,那么通過 res 返回的地址結(jié)構(gòu)中的 IP 地址將會(huì)被設(shè)置成回環(huán) IP 地址(INADDR_LOOPBACK 或者 IN6ADDR_LOOPBACK_INIT)
AI_V4MAPPED:如果在 hints 的 ai_family 中指定了 AF_INET6,那么在沒有找到匹配的 IPv6 地址時(shí)應(yīng)該在 res 返回 IPv4 映射的 IPv6 地址。如果同時(shí)指定了AI_ALL 和 AI_V4MAPPED,那么 res 中同時(shí)返回 IPv6 和 IPv4 地址,IPv4 地址會(huì)被返回成 IPv4 映射的 IPv6 地址
釋放 addrinfo 列表 freeaddrinfo()
#include#include #include void freeaddrinfo(struct addrinfo *res);
getaddrinfo() 函數(shù)會(huì)動(dòng)態(tài)地為 res 引用的所有結(jié)構(gòu)分配內(nèi)存,其結(jié)果是調(diào)用者必須要在不需要使用這些結(jié)構(gòu)時(shí)釋放它們,使用 freeaddrinfo() 來執(zhí)行釋放的任務(wù)
錯(cuò)誤診斷 gai_strerror()
getaddrinfo() 在發(fā)生錯(cuò)誤時(shí)返回下面的一個(gè)錯(cuò)誤碼:
#include#include #include const char *gai_strerror(int errcode);
gai_strerror() 返回一個(gè)描述錯(cuò)誤的字符串
getnameinfo() 函數(shù)
getnameinfo() 是 getaddrinfo() 的逆函數(shù)。給定一個(gè) socket 地址結(jié)構(gòu)它會(huì)返回一個(gè)包含對應(yīng)的主機(jī)和服務(wù)名的字符串或者無法解析名字時(shí)返回一個(gè)等價(jià)的數(shù)值。
#include#include int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,char *host, socklen_t hostlen,char *service, socklen_t servlen, int flags);
addr 指向待轉(zhuǎn)換的 socket 地址結(jié)構(gòu),長度為 addrlen
得到的主機(jī)和服務(wù)名是以 null 結(jié)尾的字符串,它們會(huì)被存儲(chǔ)在 host 和 service 指向的緩沖器中,調(diào)用者必須要為這些緩沖器分配空間并將它們的大小傳入 hostlen 和 servlen ,NI_MAXHOST 指出了返回的主機(jī)名字符串的最大字節(jié)數(shù),其取值為 1025,NI_MAXSERV 指出了返回服務(wù)名字符串的最大字節(jié)數(shù),其取值為 32
如果不想獲取主機(jī)名,可以將 host 指定為 NULL 并且將 hostlen 指定為 0,如果不想獲取服務(wù)名,可以將 service 指定為 NULL 并且將 servlen 指定為 0,但是 host 和 service 中至少有一個(gè)必須非 NULL
flags 是一個(gè)掩碼,控制著 getnameinfo() 的行為,取值為:
NI_DGRAM:默認(rèn)情況下,getnameinfo() 返回 TCP 服務(wù)對應(yīng)的名字,NI_DGRAM 標(biāo)記強(qiáng)制返回 UDP 服務(wù)的名字
NI_NAMEREQD:默認(rèn)情況下,如果無法解析主機(jī)名,那么在 host 中返回一個(gè)數(shù)值地址字符串,如果指定了 NI_NAMEREQD,就會(huì)返回一個(gè)錯(cuò)誤 EAI_NONAME
NI_NOFQDN:在默認(rèn)情況下會(huì)返回主機(jī)的完全限定域名,指定 NI_NOFQDN 標(biāo)記會(huì)導(dǎo)致當(dāng)主機(jī)位于局域網(wǎng)中時(shí)只返回名字的第一部分(即主機(jī)名)
NI_NUMERICHOST:強(qiáng)制在 host 中返回一個(gè)數(shù)值地址字符串,這個(gè)標(biāo)記在需要避免可能耗時(shí)較長的 DNS 服務(wù)器調(diào)用時(shí)是比較有用的
NI_NUMERICSERV:強(qiáng)制在 service 中返回一個(gè)十進(jìn)制端口號(hào)字符串,這個(gè)標(biāo)記在知道端口號(hào)不對應(yīng)于服務(wù)器名時(shí),如它是一個(gè)由內(nèi)核分配給 socket 的臨時(shí)端口號(hào),以及需要避免不必要的搜索 /etc/service 的低效性時(shí)是比較有用的
流式 socket 客戶端/服務(wù)器示例
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; }
過時(shí)的主機(jī)和服務(wù)轉(zhuǎn)換 API
inet_aton() 和 inet_ntoa()
#include#include #include int inet_aton(const char *str, struct in_addr *addr);
inet_aton() 將 str 指向的點(diǎn)分十進(jìn)制字符串轉(zhuǎn)換成一個(gè)網(wǎng)絡(luò)字節(jié)序的 IPv4 地址,轉(zhuǎn)換得到的地址將會(huì)返回 addr 指向的結(jié)構(gòu)
成功時(shí)返回 1,str 無效時(shí)返回 0
str 字符串的數(shù)值部分無需是十進(jìn)制的,它可以是八進(jìn)制的,也可以是十六進(jìn)制的
#include#include #include char *inet_ntoa(struct in_addr in);
inet_ntoa() 返回一個(gè)指向包含用點(diǎn)分十進(jìn)制標(biāo)記法標(biāo)記的地址的字符串指針
inet_ntoa() 返回的字符串是靜態(tài)分配的,因此會(huì)被后續(xù)調(diào)用所覆蓋
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 給出的主機(jī)名,并返回一個(gè)指向靜態(tài)分配的包含了主機(jī)名相關(guān)信息的 hostent 結(jié)構(gòu)的指針
gethostbyaddr() 執(zhí)行 gethostbyname() 的逆操作,給定一個(gè)二進(jìn)制 IP 地址,它會(huì)返回一個(gè)包含與配置了該地址的主機(jī)相關(guān)的信息的 hostent 結(jié)構(gòu)
發(fā)生錯(cuò)誤時(shí),或者無法解析一個(gè)名字時(shí),gethostbyname() 和 gethostbyaddr() 都會(huì)返回 NULL,并設(shè)置全局變量 h_errno。這個(gè)變量類似于 errno,herror() 和 hstrerror() 類似于 perror() 和 strerror()
#includeextern int h_errno; void herror(const char *s); const char *hstrerror(int err);
hostent 結(jié)構(gòu):
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 返回主機(jī)的官方名字,是一個(gè)以 null 結(jié)尾的字符串
h_aliases 指向一個(gè)指針數(shù)組,數(shù)組中的指針指向以 null 結(jié)尾的包含了主機(jī)名的別名的字符串
h_addr_list 是一個(gè)指針數(shù)組,數(shù)組中的指針指向這個(gè)主機(jī)的 IP 地址結(jié)構(gòu),這個(gè)列表由 in_addr 和 in6_addr 結(jié)構(gòu)構(gòu)成,通過 h_addrtype 字段可以確定這些結(jié)構(gòu)的類型,其取值為 AF_INET 或 AF_INET6,h_length 字段可以確定這些結(jié)構(gòu)的長度
getservbyname() 和 getservbyport()
#includestruct servent *getservbyname(const char *name, const char *proto);
getservbyname() 和 getservbyport() 都是從 /etc/services 文件中獲取記錄,現(xiàn)在已經(jīng)被 getaddrinfo() 和 getnameinfo() 取代
getservbyname() 查詢服務(wù)名或者其中一個(gè)別名與 name 匹配以及協(xié)議與 proto 匹配的記錄,proto 可以是 TCP 或者 UDP,或者設(shè)置為 NULL
如果找到了一個(gè)匹配的記錄,那么 getservbyname() 會(huì)返回一個(gè)指向靜態(tài)分配的結(jié)構(gòu)指針:
struct servent { char *s_name; /* official service name */ char **s_aliases; /* alias list */ int s_port; /* port number */ char *s_proto; /* protocol to use */ };
一般調(diào)用 getservbyname() 只是為了獲取端口號(hào),即 s_port 字段
#includestruct servent *getservbyport(int port, const char *proto);
getservbyport() 執(zhí)行 getservbyname() 的逆操作,它返回一個(gè) servent 記錄,該記錄包含了 /etc/services 文件中端口號(hào)與 port 匹配的記錄相關(guān)的信息
UNIX 與 Internet domain socket 比較
編寫只使用 Internet domain socket 的應(yīng)用程序即可以運(yùn)行在同一主機(jī)上,也可以運(yùn)行在網(wǎng)絡(luò)中的不同主機(jī)上。
UNIX domain socket 只能用于同一系統(tǒng)上的應(yīng)用程序間通信,使用 UNIX domain socket 的幾個(gè)原因:
在一些實(shí)現(xiàn)上,UNIX domain socket 速度要比 Internet domain socket 快
可以使用目錄權(quán)限來控制對 UNIX domain socket 的訪問,這樣只有運(yùn)行于指定的用戶或組 ID 下的應(yīng)用程序才能夠連接到一個(gè)監(jiān)聽流 socket 或向一個(gè)數(shù)據(jù)報(bào) socket 發(fā)送一個(gè)數(shù)據(jù)報(bào)。
審核編輯:劉清
-
緩沖器
+關(guān)注
關(guān)注
6文章
1922瀏覽量
45507 -
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
594瀏覽量
27409 -
DNS
+關(guān)注
關(guān)注
0文章
218瀏覽量
19859 -
TCP通信
+關(guān)注
關(guān)注
0文章
146瀏覽量
4224
原文標(biāo)題:Linux Internet Domain應(yīng)用編程
文章出處:【微信號(hào):嵌入式應(yīng)用研究院,微信公眾號(hào):嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論