0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux Internet Domain應(yīng)用編程簡介

嵌入式應(yīng)用研究院 ? 來源:TLPI編程筆記 ? 2023-05-26 14:05 ? 次閱讀

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é)序:

4ae9ef3a-fb84-11ed-90ce-dac502259ad0.png

網(wǎng)絡(luò)字節(jié)序采用大端。

INADDR_ANY 和 INADDR_LOOPBACK 是主機(jī)字節(jié)序,在將它們存儲進(jìn) socket 地址結(jié)構(gòu)中之前需要將這些值轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序。

主機(jī)序和網(wǎng)絡(luò)字節(jié)序之間的轉(zhuǎn)換:

#include 

uint32_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 地址存儲于結(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 分別是端口號和 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)的符號標(biāo)識符,服務(wù)名是端口號的符號表示。主機(jī)地址和端口的表示有下列兩種:

主機(jī)地址和端口的表示有兩種方法:

主機(jī)地址可以表示為一個(gè)二進(jìn)制值或一個(gè)符號主機(jī)名或展現(xiàn)格式(IPv4 點(diǎn)分十進(jìn)制,IPv6 是十六進(jìn)制字符串)

端口號可以表示為一個(gè)二進(jìn)制值或一個(gè)符號服務(wù)名

inet_pton() 和 inet_ntop() 函數(shù)

#include 

int 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ì):

4af6fd74-fb84-11ed-90ce-dac502259ad0.png

將主機(jī)名組織在一個(gè)層級名空間中,每個(gè)節(jié)點(diǎn)有一個(gè)標(biāo)簽,該標(biāo)簽最多能包含 63 個(gè)字符,層級的根是一個(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)識出了層級中的一臺主機(jī),區(qū)分一個(gè)完全限定域名的方法是看名字是否以.結(jié)尾,但是在很多情況下這個(gè)點(diǎn)會(huì)被省略

沒有一個(gè)組織或系統(tǒng)會(huì)管理整個(gè)層級,相反,存在一個(gè) DNS 服務(wù)器層級,每臺服務(wù)器管理樹的一個(gè)分支(區(qū)域)

當(dāng)一個(gè)程序調(diào)用 getaddrinfo() 來解析一個(gè)域名時(shí),getaddrinfo() 會(huì)使用一組庫函數(shù)來與各地的 DNS 服務(wù)器通信,如果這個(gè)服務(wù)器無法提供所需要的信息,那么它就會(huì)與位于層級中的其他 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ù)器到其中一臺 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 作為其名字。

頂級域

緊跟在匿名根節(jié)點(diǎn)下面的節(jié)點(diǎn)稱為頂級域(TLD),TLD 分為兩類:通用的和國家的。

/etc/services 文件

端口號和服務(wù)名記錄在 /etc/services 中,getaddrinfo() 和 getnameinfo() 會(huì)使用這個(gè)文件中的信息在服務(wù)名和端口號之間進(jìn)行轉(zhuǎn)換。

獨(dú)立于協(xié)議的主機(jī)和服務(wù)轉(zhuǎn)換

getaddrinfo() 將主機(jī)和服務(wù)名轉(zhuǎn)換成 IP 地址和端口號,它作為過時(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è)地址和端口號

成功時(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)制端口號

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 可用

4b02f386-fb84-11ed-90ce-dac502259ad0.png

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ù)值端口號,這個(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ò)誤碼:

4b0e01b8-fb84-11ed-90ce-dac502259ad0.png

#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ì)被存儲在 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)制端口號字符串,這個(gè)標(biāo)記在知道端口號不對應(yīng)于服務(wù)器名時(shí),如它是一個(gè)由內(nèi)核分配給 socket 的臨時(shí)端口號,以及需要避免不必要的搜索 /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()

#include 
extern 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()

#include 
extern 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()

#include 

struct 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() 只是為了獲取端口號,即 s_port 字段

#include 

struct servent *getservbyport(int port, const char *proto);

getservbyport() 執(zhí)行 getservbyname() 的逆操作,它返回一個(gè) servent 記錄,該記錄包含了 /etc/services 文件中端口號與 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)。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 緩沖器
    +關(guān)注

    關(guān)注

    6

    文章

    1904

    瀏覽量

    45329
  • Linux系統(tǒng)
    +關(guān)注

    關(guān)注

    4

    文章

    587

    瀏覽量

    27182
  • DNS
    DNS
    +關(guān)注

    關(guān)注

    0

    文章

    211

    瀏覽量

    19700
  • TCP通信
    +關(guān)注

    關(guān)注

    0

    文章

    146

    瀏覽量

    4184

原文標(biāo)題:Linux Internet Domain應(yīng)用編程

文章出處:【微信號:嵌入式應(yīng)用研究院,微信公眾號:嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    [推薦]linux下的c語言編程簡介

    第一章本章將簡要介紹一下什么是Linux,C語言的特點(diǎn),程序開發(fā)的預(yù)備知識,Linux下C語言開發(fā)的環(huán)境,程序設(shè)計(jì)的特點(diǎn)和原則以及編碼風(fēng)格等。通過本章的學(xué)習(xí),可以對在Linux下使用C語言編程
    發(fā)表于 04-29 13:50

    Linux網(wǎng)絡(luò)編程教材

    /Linux 模型.....172.1 UNIX/Linux 基本結(jié)構(gòu).......172.2 輸入和輸出..192.2.1 UNIX/Linux 文件系統(tǒng)簡介192.2.2 流和標(biāo)準(zhǔn)
    發(fā)表于 01-20 16:49

    精通嵌入式Linux編程

    ....................................................................... 26第 2 章 LINUX 高級程序設(shè)計(jì)簡介
    發(fā)表于 11-06 14:57

    Voice over Internet Protocol

    Voice over Internet Protocol  VoIP簡介 英文版 Voice over Internet Protocol (VoIP) is a technology
    發(fā)表于 04-23 18:02 ?21次下載

    Time Domain Reflectometry Theo

    The most general approach to evaluating the time domain responseof any electromagnetic system
    發(fā)表于 07-11 17:09 ?5次下載

    EMC宣布收購Data Domain

    EMC宣布收購Data Domain EMC公司宣布,已購得Data Domain的多數(shù)股權(quán)。預(yù)計(jì)今年7月底完成Data Domain的收購程序后,EMC便將以Data Domain
    發(fā)表于 07-28 07:46 ?869次閱讀

    Internet簡介

    Internet簡介 Internet的定義 Internet的定義(1)?Internet是全球最大的、開放的、由眾多網(wǎng)絡(luò)互聯(lián)而成的計(jì)
    發(fā)表于 01-27 11:24 ?1070次閱讀

    什么是Domain Name

    什么是Domain Name 英文縮寫: Domain Name 中文譯名: 域名 分  類: IP與多媒體 解
    發(fā)表于 02-22 17:38 ?1417次閱讀

    ATM網(wǎng)絡(luò)和Internet融合技術(shù)簡介

    ATM網(wǎng)絡(luò)和Internet融合技術(shù)簡介 在數(shù)據(jù)信息爆炸式增長的同時(shí),作為信息傳輸媒介的計(jì)算機(jī)網(wǎng)絡(luò)發(fā)展也可謂一日千里。以計(jì)算機(jī)為主開
    發(fā)表于 04-06 16:16 ?1280次閱讀

    LINUX網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——LINUX網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Linux網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    關(guān)于LinuxInternet安全漏洞與防范措施詳解

    LINUX是一種當(dāng)今世界上廣為流行的免費(fèi)操作系統(tǒng),它與UNIX完全兼容,但以其開放性的平臺,吸引著無數(shù)高等院校的學(xué)生和科研機(jī)構(gòu)的人員紛紛把它作為學(xué)習(xí)和研究的對象。這些編程高手在不斷完善LINUX版本中網(wǎng)絡(luò)安全功能。下面介紹
    發(fā)表于 07-16 09:20 ?878次閱讀

    LINUX系統(tǒng)教程之如何在Linux系統(tǒng)下進(jìn)行編程

    本文檔的主要內(nèi)容詳細(xì)介紹的是LINUX系統(tǒng)教程之如何在Linux系統(tǒng)下進(jìn)行編程主要內(nèi)容包括了:程序開發(fā)過程 ,Linux編程環(huán)境和開發(fā)工具
    發(fā)表于 12-18 19:09 ?9次下載

    Linux教程之Linux命令、編程器、Shell編程、實(shí)例大全pdf免費(fèi)下載

    的實(shí)用程序。全書分上、中、下3篇,共20章,內(nèi)容涵蓋了Linux簡介、Red Hat Linux基礎(chǔ)知識、系統(tǒng)管理與設(shè)置、用戶和用戶組管理、磁盤管理、文件和目錄管理、備份與壓縮、網(wǎng)絡(luò)管理、正則表達(dá)式、vim編輯器、cmacs、g
    發(fā)表于 01-08 14:55 ?20次下載
    <b class='flag-5'>Linux</b>教程之<b class='flag-5'>Linux</b>命令、<b class='flag-5'>編程</b>器、Shell<b class='flag-5'>編程</b>、實(shí)例大全pdf免費(fèi)下載

    IRQ domain支持幾種映射方式

    IRQ domain IRQ domain用于將硬件的中斷號,轉(zhuǎn)換成Linux系統(tǒng)中的中斷號(virtual irq, virq),來張圖: 每個(gè)中斷控制器都對應(yīng)一個(gè)IRQ Domain
    的頭像 發(fā)表于 09-28 15:21 ?544次閱讀
    IRQ <b class='flag-5'>domain</b>支持幾種映射方式