0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(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ù)報 socket 是基于 UDP 的:

UNIX domain 數(shù)據(jù)報 socket 是可靠的,但是 UDP socket 則不是可靠的,數(shù)據(jù)報可能會丟失,重復(fù),亂序

在一個 UNIX domain 數(shù)據(jù)報 socket 上發(fā)送數(shù)據(jù)會在接收 socket 的數(shù)據(jù)隊列為滿時阻塞,與之不同的是,使用 UDP 時如果進入的數(shù)據(jù)報會使接收者的隊列溢出,那么數(shù)據(jù)報就會靜默地被丟棄

網(wǎng)絡(luò)字節(jié)序

2 字節(jié)和 4 字節(jié)整數(shù)的大端和小端字節(jié)序:

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

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

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

主機序和網(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 中新引入了一個通用的 sockaddr_storage 結(jié)構(gòu),這個結(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];
};

主機和服務(wù)轉(zhuǎn)換函數(shù)概述

主機名和連接在網(wǎng)絡(luò)上的一個系統(tǒng)的符號標(biāo)識符,服務(wù)名是端口號的符號表示。主機地址和端口的表示有下列兩種:

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

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

端口號可以表示為一個二進制值或一個符號服務(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é)序的二進制 IP 地址,af 被指定為 AF_INET 或者 AF_INET6

inet_ntop() 執(zhí)行逆向轉(zhuǎn)換, size 被指定為緩沖器的大小,如果 size 太小,那么 inet_ntop() 會返回 NULL 并將 errno 設(shè)置成 ENOSPC

緩沖器大小可以使用下面兩個宏指定:

#include 

#define INET_ADDRSTRLEN      16
#define INET6_ADDRSTRLEN     46

數(shù)據(jù)報 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)以前,主機名和 IP 地址之間的映射關(guān)系是在一個手工維護的本地文件 /etc/hosts 中進行定義的:

127.0.0.1    localhost
::1   ip6-localhost ip6-loopback

gethostbyname() 或者 getaddrinfo() 通過搜索這個文件并找出與規(guī)范主機名或其中一個別名匹配的記錄來獲取一個 IP 地址。

DNS 設(shè)計:

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

將主機名組織在一個層級名空間中,每個節(jié)點有一個標(biāo)簽,該標(biāo)簽最多能包含 63 個字符,層級的根是一個無名的節(jié)點,稱為 "匿名節(jié)點"

一個節(jié)點的域名由該節(jié)點到根節(jié)點的路徑中所有節(jié)點的名字連接而成,各個名字之間使用 . 分隔

完全限定域名,如 www.kernel.org. ,標(biāo)識出了層級中的一臺主機,區(qū)分一個完全限定域名的方法是看名字是否以.結(jié)尾,但是在很多情況下這個點會被省略

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

當(dāng)一個程序調(diào)用 getaddrinfo() 來解析一個域名時,getaddrinfo() 會使用一組庫函數(shù)來與各地的 DNS 服務(wù)器通信,如果這個服務(wù)器無法提供所需要的信息,那么它就會與位于層級中的其他 DNS 服務(wù)器進行通信以便獲取信息,這個過程可能花費很多時間,DNS 采用了緩存技術(shù)以節(jié)省時間

遞歸和迭代的解析請求

DNS 解析請求可以分為:遞歸和迭代。

遞歸請求:請求者要求服務(wù)器處理整個解析任務(wù),包括在必要時候與其它 DNS 服務(wù)器進行通信任務(wù)。當(dāng)位于本地主機上的一個應(yīng)用程序調(diào)用 getaddrinfo() 時,該函數(shù)會與本地 DNS 服務(wù)器發(fā)起一個遞歸請求,如果本地 DNS 服務(wù)器自己沒有相關(guān)信息來完成解析,那么它就會迭代地解析這個域名。

迭代解析:假設(shè)要解析 www.otago.ac.nz,首先與每個 DNS 服務(wù)器都知道的一小組根名字服務(wù)器中的一個進行通信,根名字服務(wù)器會告訴本 DNS 服務(wù)器到其中一臺 nz DNS 服務(wù)器上查詢,然后本地 DNS 服務(wù)器會在 nz 服務(wù)器上查詢名字 www.otago.ac.nz,并收到一個到 ac.nz 服務(wù)器上查詢的響應(yīng),之后本地 DNS 服務(wù)器會在 ac.nz 服務(wù)器上查詢名字 www.otago.ac.nz 并告知查詢 otago.ac.nz 服務(wù)器,最后本地 DNS 服務(wù)器會在 otago.ac.nz 服務(wù)器上查詢 www.otago.ac.nz 并獲取所需的 IP 地址。

向 gethostbyname() 傳遞一個不完整的域名,那么解析器在解析之前會嘗試補齊。域名補全規(guī)則在 /etc/resolv.conf 中定義,默認(rèn)情況下,至少會使用本機的域名來補全,例如,登錄機器 oghma.otago.ac.nz 并輸入 ssh octavo 得到的 DNS 查詢將會以 octavo.otago.ac.nz 作為其名字。

頂級域

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

/etc/services 文件

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

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

getaddrinfo() 將主機和服務(wù)名轉(zhuǎn)換成 IP 地址和端口號,它作為過時的 gethostbyname() 和 getservername() 接替者。

getnameinfo() 是 getaddrinfo() 的逆函數(shù),將一個 socket 地址結(jié)構(gòu)轉(zhuǎn)換成包含對應(yīng)主機和服務(wù)名的字符串,是過時的 gethostbyaddr() 和 getserverbyport() 的等價物。

getaddrinfo() 函數(shù)

#include 
#include 
#include 

int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints,struct addrinfo **res);

給定一個主機名和服務(wù)器名,getaddrinfo() 返回一個 socket 地址結(jié)構(gòu)列表,每個結(jié)構(gòu)都包含一個地址和端口號

成功時返回0,失敗時返回非 0

host 包含一個主機名或者一個以 IPv4 點分十進制標(biāo)記或者 IPv6 十六進制字符串標(biāo)記的數(shù)值地址字符串

service 包含一個服務(wù)名或一個十進制端口號

hints 指向一個 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 返回一個結(jié)構(gòu)列表而不是單個結(jié)構(gòu),因為與在 host、 service、 hints、 指定的標(biāo)準(zhǔn)對應(yīng)的主機和服務(wù)組合可能有多個。例如,查詢多個網(wǎng)絡(luò)接口的主機時可能返回多個地址結(jié)構(gòu),此外,如果將 hints.ai_sockettype 指定 為0,那么可能返回兩個結(jié)構(gòu):一個用于 SOCK_DGRAM socket 和 SOCKET_STREAM socket,前提是給定的 service 同時對 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è)置 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 地址,那么可以將這個字段設(shè)置為 AF_UNSPEC。

hints.ai_socktype 字段指定了使用返回的 socket 地址結(jié)構(gòu)的 socket 類型。如果將這個字段指定為 SOCK_DGRAM,那么查詢將會在 UDP 服務(wù)上執(zhí)行,如果指定了 SOCK_STREAM,那么將返回一個 TCP 服務(wù)查詢,如果將其指定為 0,那么任意類型的 socket 都是可接受的。

hints.ai_protocol 字段為返回的地址結(jié)構(gòu)選擇了 socket 協(xié)議,這個字段設(shè)置為 0,表示調(diào)用者接受任何協(xié)議。

hints.ai_flags 字段是一個位掩碼,它會改變 getaddrinfo() 的行為,取值為:

AI_ADDRCONFIG:在本地系統(tǒng)上至少配置一個 IPv4 地址時返回 IPv4 地址(不是 IPv4 回環(huán)地址),在本地系統(tǒng)上至少配置一個 IPv6 地址時返回 IPv6 地址(不是 IPv6 回環(huán)地址)

AI_ALL:參見 AI_V4MAPPED

AI_CANONNAME:如果 host 不為 NULL,返回一個指向 null 結(jié)尾的字符串,該字符串包含了主機的規(guī)范名,這個指針會在通過 result 返回的第一個 addrinfo 結(jié)構(gòu)中 ai_canoname 字段指向的緩沖器中返回

AI_NUMERICHOST:強制將 host 解釋成一個數(shù)值地址字符串,這個常量用于在不必要解析名字時防止進行名字解析,因為名字解析可能會花費比較長的時間

AI_NUMERICSERV:將 service 解釋成一個數(shù)值端口號,這個標(biāo)記用于防止調(diào)用任意的名字解析服務(wù),因為當(dāng) service 為一個數(shù)值字符串時這種調(diào)用是沒有必要的

AI_PASSIVE:返回一個適合進行被動式打開(即一個監(jiān)聽 socket)的 socket 地址結(jié)構(gòu),此時,host 應(yīng)該是 NULL,通過 result 返回的 socket 地址結(jié)構(gòu) IP 地址部分將會包含一耳光通配 IP 地址(INADDR_ANY 或者 IN6ADDR_ANY_INIT)。如果沒有設(shè)置這個標(biāo)記,那么通過 res 返回的地址結(jié)構(gòu)中的 IP 地址將會被設(shè)置成回環(huán) IP 地址(INADDR_LOOPBACK 或者 IN6ADDR_LOOPBACK_INIT)

AI_V4MAPPED:如果在 hints 的 ai_family 中指定了 AF_INET6,那么在沒有找到匹配的 IPv6 地址時應(yīng)該在 res 返回 IPv4 映射的 IPv6 地址。如果同時指定了AI_ALL 和 AI_V4MAPPED,那么 res 中同時返回 IPv6 和 IPv4 地址,IPv4 地址會被返回成 IPv4 映射的 IPv6 地址

釋放 addrinfo 列表 freeaddrinfo()

#include 
#include 
#include 

void freeaddrinfo(struct addrinfo *res);

getaddrinfo() 函數(shù)會動態(tài)地為 res 引用的所有結(jié)構(gòu)分配內(nèi)存,其結(jié)果是調(diào)用者必須要在不需要使用這些結(jié)構(gòu)時釋放它們,使用 freeaddrinfo() 來執(zhí)行釋放的任務(wù)

錯誤診斷 gai_strerror()

getaddrinfo() 在發(fā)生錯誤時返回下面的一個錯誤碼:

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

#include 
#include 
#include 

const char *gai_strerror(int errcode);

gai_strerror() 返回一個描述錯誤的字符串

getnameinfo() 函數(shù)

getnameinfo() 是 getaddrinfo() 的逆函數(shù)。給定一個 socket 地址結(jié)構(gòu)它會返回一個包含對應(yīng)的主機和服務(wù)名的字符串或者無法解析名字時返回一個等價的數(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

得到的主機和服務(wù)名是以 null 結(jié)尾的字符串,它們會被存儲在 host 和 service 指向的緩沖器中,調(diào)用者必須要為這些緩沖器分配空間并將它們的大小傳入 hostlen 和 servlen ,NI_MAXHOST 指出了返回的主機名字符串的最大字節(jié)數(shù),其取值為 1025,NI_MAXSERV 指出了返回服務(wù)名字符串的最大字節(jié)數(shù),其取值為 32

如果不想獲取主機名,可以將 host 指定為 NULL 并且將 hostlen 指定為 0,如果不想獲取服務(wù)名,可以將 service 指定為 NULL 并且將 servlen 指定為 0,但是 host 和 service 中至少有一個必須非 NULL

flags 是一個掩碼,控制著 getnameinfo() 的行為,取值為:

NI_DGRAM:默認(rèn)情況下,getnameinfo() 返回 TCP 服務(wù)對應(yīng)的名字,NI_DGRAM 標(biāo)記強制返回 UDP 服務(wù)的名字

NI_NAMEREQD:默認(rèn)情況下,如果無法解析主機名,那么在 host 中返回一個數(shù)值地址字符串,如果指定了 NI_NAMEREQD,就會返回一個錯誤 EAI_NONAME

NI_NOFQDN:在默認(rèn)情況下會返回主機的完全限定域名,指定 NI_NOFQDN 標(biāo)記會導(dǎo)致當(dāng)主機位于局域網(wǎng)中時只返回名字的第一部分(即主機名)

NI_NUMERICHOST:強制在 host 中返回一個數(shù)值地址字符串,這個標(biāo)記在需要避免可能耗時較長的 DNS 服務(wù)器調(diào)用時是比較有用的

NI_NUMERICSERV:強制在 service 中返回一個十進制端口號字符串,這個標(biāo)記在知道端口號不對應(yīng)于服務(wù)器名時,如它是一個由內(nèi)核分配給 socket 的臨時端口號,以及需要避免不必要的搜索 /etc/service 的低效性時是比較有用的

流式 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;
}

過時的主機和服務(wù)轉(zhuǎn)換 API

inet_aton() 和 inet_ntoa()

#include 
#include 
#include 

int inet_aton(const char *str, struct in_addr *addr);

inet_aton() 將 str 指向的點分十進制字符串轉(zhuǎn)換成一個網(wǎng)絡(luò)字節(jié)序的 IPv4 地址,轉(zhuǎn)換得到的地址將會返回 addr 指向的結(jié)構(gòu)

成功時返回 1,str 無效時返回 0

str 字符串的數(shù)值部分無需是十進制的,它可以是八進制的,也可以是十六進制的

#include 
#include 
#include 

char *inet_ntoa(struct in_addr in);

inet_ntoa() 返回一個指向包含用點分十進制標(biāo)記法標(biāo)記的地址的字符串指針

inet_ntoa() 返回的字符串是靜態(tài)分配的,因此會被后續(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 給出的主機名,并返回一個指向靜態(tài)分配的包含了主機名相關(guān)信息的 hostent 結(jié)構(gòu)的指針

gethostbyaddr() 執(zhí)行 gethostbyname() 的逆操作,給定一個二進制 IP 地址,它會返回一個包含與配置了該地址的主機相關(guān)的信息的 hostent 結(jié)構(gòu)

發(fā)生錯誤時,或者無法解析一個名字時,gethostbyname() 和 gethostbyaddr() 都會返回 NULL,并設(shè)置全局變量 h_errno。這個變量類似于 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 返回主機的官方名字,是一個以 null 結(jié)尾的字符串

h_aliases 指向一個指針數(shù)組,數(shù)組中的指針指向以 null 結(jié)尾的包含了主機名的別名的字符串

h_addr_list 是一個指針數(shù)組,數(shù)組中的指針指向這個主機的 IP 地址結(jié)構(gòu),這個列表由 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ù)名或者其中一個別名與 name 匹配以及協(xié)議與 proto 匹配的記錄,proto 可以是 TCP 或者 UDP,或者設(shè)置為 NULL

如果找到了一個匹配的記錄,那么 getservbyname() 會返回一個指向靜態(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() 的逆操作,它返回一個 servent 記錄,該記錄包含了 /etc/services 文件中端口號與 port 匹配的記錄相關(guān)的信息

UNIX 與 Internet domain socket 比較

編寫只使用 Internet domain socket 的應(yīng)用程序即可以運行在同一主機上,也可以運行在網(wǎng)絡(luò)中的不同主機上。

UNIX domain socket 只能用于同一系統(tǒng)上的應(yīng)用程序間通信,使用 UNIX domain socket 的幾個原因:

在一些實現(xiàn)上,UNIX domain socket 速度要比 Internet domain socket 快

可以使用目錄權(quán)限來控制對 UNIX domain socket 的訪問,這樣只有運行于指定的用戶或組 ID 下的應(yīng)用程序才能夠連接到一個監(jiān)聽流 socket 或向一個數(shù)據(jù)報 socket 發(fā)送一個數(shù)據(jù)報。





審核編輯:劉清

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

    關(guān)注

    6

    文章

    1905

    瀏覽量

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

    關(guān)注

    4

    文章

    588

    瀏覽量

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

    關(guān)注

    0

    文章

    214

    瀏覽量

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

    關(guān)注

    0

    文章

    146

    瀏覽量

    4192

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

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

收藏 人收藏

    評論

    相關(guān)推薦

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

    第一章本章將簡要介紹一下什么是Linux,C語言的特點,程序開發(fā)的預(yù)備知識,Linux下C語言開發(fā)的環(huán)境,程序設(shè)計的特點和原則以及編碼風(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è)計簡介
    發(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ù)計今年7月底完成Data Domain的收購程序后,EMC便將以Data Domain
    發(fā)表于 07-28 07:46 ?872次閱讀

    Internet簡介

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

    什么是Domain Name

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

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

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

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

    Linux教程之Linux命令、編程器、Shell編程、實例大全pdf免費下載

    的實用程序。全書分上、中、下3篇,共20章,內(nèi)容涵蓋了Linux簡介、Red Hat Linux基礎(chǔ)知識、系統(tǒng)管理與設(shè)置、用戶和用戶組管理、磁盤管理、文件和目錄管理、備份與壓縮、網(wǎng)絡(luò)管理、正則表達式、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>、實例大全pdf免費下載

    IRQ domain支持幾種映射方式

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

    Linux應(yīng)用編程的基本概念

    Linux應(yīng)用編程涉及到在Linux環(huán)境下開發(fā)和運行應(yīng)用程序的一系列概念。以下是一些涵蓋Linux應(yīng)用編程的基本概念。
    的頭像 發(fā)表于 10-24 17:19 ?46次閱讀