《unix網絡編程》筆記

inet_pton和inetntop函數。字母p和n表明presentation和numeric。地址的表達presentation格式一般是ASCIL串,數值(numeric)格式則是存在於套接字地址結構中的二進制值。html

inet_pton和inet_ntop函數是比較新的函數,它們可以處理ipv4和ipv6的地址轉換。c#

 

1. inet_pton緩存

 

int inet_pton(int af, const char *src, void *dst);
將src所指的網絡地址字符串(如"192.168.0.1")轉換成網絡使用的二進制數(unsigned long),存放在dst所指的in_addr結構中。使用基本與inet_aton一致,不一樣的是多了一個參數af(地址族:AF_INET或AF_INET6,分別對應ipv4和ipv6,對應的地址結構爲sockaddr_in和sockaddr_in6)。

 

使用:服務器

 

sockaddr_in server_addr;
inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr);

 

 

2. inet_ntop網絡

 

const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
將src所指的網絡二進制數轉換成網絡地址字符串(如"192.168.0.1"),存放在dst所指字符串和返回值中。相比inet_pton多了個參數cnt,定義緩存區dst的大小,防止溢出。若是緩存區過小沒法存儲地址的值,則返回一個空指針,並將errno置爲ENOSPC。

使用:socket

sockaddr_in server_addr;
char IPdotdec[20]; //存放點分十進制IP地址
inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr);
printf(inet_ntop(AF_INET, (void *)&server_addr.sin_addr, IPdotdec, 16));  // 輸出"192.168.0.1"
printf("%s", IPdotdec);  // 輸出"192.168.0.1"

 

 readn、writen和readline函數
     字節流套接字上的read和write函數所表現的行爲不一樣於一般的文件I/O.字節流套接字上調用read或write輸入或輸出的字節數可能比請求的數量少,然而這不是出錯狀態.這個現象的緣由在於內核中用於套接字的緩衝區可能已經達到了極限.此時所需的是調用者再次調用read個write函數,以輸入或輸出剩餘的字節. 咱們提供的如下三個函數是每當咱們讀或寫一個字節流套接字時要使用的函數.
//從一個描述符讀取n個字節
ssize_t readn(int fd, void* vptr, size_t n)
{
 size_t  nleft = n;  //記錄還剩下多少字節數沒讀取
 ssize_t nread;      //記錄已經讀取的字節數
 char*  ptr = vptr;  //指向要讀取數據的指針
 while(nleft > 0)    //還有數據要讀取
 {
  if(nread = read(fd,ptr,nleft) < 0)
   if(erron == EINTR)//系統被一個捕獲的信號中斷
    nread = 0;       //再次讀取
   else
    return -1;       //返回
  else if(nread == 0)//沒有出錯可是也沒有讀取到數據
   break;            //再次讀取
  nleft -= nread;    //計算剩下未讀取的字節數
  ptr  += nread;     //移動指針到以讀取數據的下一個位置
 }
 return (n-nleft);   //返回讀取的字節數
}
/**************************************************************************************************/
//從一個描述符讀文本行,一次一個字節
ssize_t readline(int fd, void* vptr, size_t maxlen)//一個字節一個字節地讀取
{
 ssize_t  rc;        //每次讀取的字符
 ssize_t  n;         //讀取的次數也即讀取字符串的長度
 char     c;         //
 char* ptr = vptr;   //指向要讀取的數據的指針
 for(n = 1;n < maxlen; n++)
 {
  again:
  if((rc = read(fd,&c,1)) == 1)
  {
   *ptr++ = c;       //移動指針
   if(c == '\n')     //換行符
    break;           //跳出循環
   else if(rc == 0)  //結束
    *ptr = 0;        //字符串以0結尾
   return (n-1);     //返回讀取的字節數 末尾的0不算
  }
  else
  {
   if(erron == EINTR)
    goto again;      //從新讀取
   return (-1)
  }
 }
 *ptr=0;
 return n;
}
/**************************************************************************************************/
//往一個描述符寫n個字節
ssize_t writen(ind fd, const void* vptr, size_t n)
{ 
 size_t nleft = n;        //還須要寫入的字節數 
 ssize_t nwritten;        //每次寫入的字節數 
 const char* ptr = vptr;  //指向要寫入的數據的指針 
 while(nleft > 0) 
 { 
  if((nwritten = write(fd,ptr,nleft)) <= 0) 
  { 
   if(nwritten < 0 && erron == EINTR) 
    nwritten = 0; 
   else return -1; 
  }
   nleft -= nwritten;     //計算還須要寫入的字節數 
 ptr += nwritten;         //移動數據指針 
  } 
  return n;
 }

值-結果參數
      一個套接字函數傳遞一個套接字地址結構時候,該結構總以引用形式來傳遞,也就是說傳遞的指向該結構的一個指針,該結構的長度也做爲一個參數來傳遞,不過其傳遞方式取決於該結構的傳遞方向:是從進程到內核,仍是從內核到進程.以下圖所示:函數

(1)、從 進程到內核傳遞套接字結構函數:bind、connect和sendto.這些函數的一個參數是指向某個套接字地址結構的指針,另外一個參數是該結構體的整數大小.例如:
[cpp]  view plain copy
 
  1. struct sockaddr_in serv; //定義一個套接字地址結構體變量  
  2. connect (sockfd, (struct sockaddr *) &serv, sizeof(serv));  
(2)、從內核到進程傳遞套接字地址結構的函數:accept、recvfrom、getsockname和getpeername.這4個函數的其中兩個參數指向某個套接字結構體的指針和指向表示該結構體大小的整數變量的指針.例如
[cpp]  view plain copy
 
  1. struct sockaddr_un cli;                          //定義一個套接字地址結構體變量  
  2. socklen_t len = sizeof(cli);                     //該結構體的大小  
  3. getpeername(unixfd,(struct sockaddr *)&cli,&len);//len可能會被改變  
      把套接字地址結構大小這個參數從一個整數改成指向某個整數變量的指針,其緣由在於:當函數被調用時,結構大小是一個值,他告訴內核該結構的大小,這樣內核在寫該結構時不至於越界;當函數返回時,結構大小又是一個結果,它告訴進程內核在該結構中究竟存儲了多少信息.
 

 

fork和exec函數:spa

fork最困難的部分是它調用一次卻返回兩次。在調用進程(稱爲父進程),它返回一次,返回值是新派生進程(稱爲子進程)的進程ID,在子進程中它還返回一次,返回0。可經過返回值來判斷當前進程是子進程仍是父進程。.net

fork在子進程返回0而不是父進程ID,緣由是:子進程只有一個父進程,他總能夠調用getppid來獲得;而父進程有許多子進程,他沒有辦法獲得各子進程ID。若是父進程想跟蹤全部子進程ID,他必須記住fork的返回值。命令行

 

getsockname與getpeername

是返回套接口關聯的本地協議地址和遠程協議地址。

int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);

int getpeername(int sockfd, struct sockaddr * peeraddr, socken_t * addrlen);

返回0表示成功,返回1表示出錯

參數sockfd表示你要獲取的套接口的描述字。

localaddr返回本地協議地址描述結構, peeraddr返回遠程協議地址描述結構,addrlen分別是上述2個結構的長度。

注意,2個函數的最後一個參數是值-結果參數。

須要這兩個函數的理由以下:

  • 在一個沒有調用bind的TCP客戶上,connect成功返回後,getsockname用於返回由內核賦予該鏈接的本地IP地址和本地端口號

  • 在以端口號爲0調用bind(告知內核去選擇本地臨時端口號)後,getsockname用於返回由內核賦予的本地端口號。

  • 在一個以通配IP地址調用bind的TCP服務器上,與某個客戶的鏈接一旦創建(accept成功返回),getsockname就能夠用於返回由內核賦予該鏈接的本地IP地址。在這樣的調用中,套接字描述符參數必須是已鏈接套接字的描述符,而不是監聽套接字的描述符。

  • 當一個服務器是由調用過accept的某個進程經過調用exec執行程序時,它可以獲取客戶身份的惟一途徑即是調用getpeername。

  • 例以下面的,inetd調用accept(左上方的方框)返回兩個值:已鏈接套接字描述符connfd,這是函數的返回值;客戶的IP地址及端口號,如圖中標有「對端地址」的小方框所示(表明一個網際網套接字地址結構)。inetd隨後調用fork,派生出inetd的一個子進程。這樣父進程的那個套接字地址結構在子進程也可用,那個已鏈接套接字描述符也是如此。然而當子進程調用exec執行真正的服務器程序(譬如說Telent服務器程序)時,子進程的內存映像被替換成新的Telnet服務器的程序文件(也就是說包含對端地址的那個套接字地址結構就此丟棄),不過那個已鏈接套接字描述符跨exec繼續保持開放。Telnet服務器首先調用的函數之一便getpeername
    ,用於獲取客戶的IP地址和端口號。
  •  顯然,最後一個例子中的Telnet服務器必須在啓動以後獲取connfd的值。獲取該值有兩個經常使用方法:

    • 調用exec的進程能夠把這個描述符格式化成一個字符串,再把它做爲一個命令行參數傳遞給新程序。

    • 約定在調用exec以前,老是把某個特定描述符置爲所接受的已鏈接套接字的描述符。

        inetd採用的是第二種方法,它老是把描述符0、一、2置爲所接受的已鏈接套接字的描述符(即將已鏈接套接字描述符dup到描述符0、一、2,而後close原鏈接套接字)。

     

    服務器的代碼:

    #include    "unp.h"  
          
    int
    main(int argc, char ** argv)  
    {  
        int         listenfd,connfd;  
        struct      sockaddr_in servaddr;  
        pid_t       pid;  
        char        temp[20];  
          
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);  
        bzero(&servaddr, sizeof(servaddr));  
        servaddr.sin_family = AF_INET;  
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
        servaddr.sin_port = htons(10010);  
        Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));  
        Listen(listenfd, LISTENQ);  
        for( ; ; )  
        {  
            struct sockaddr_in local;  
            connfd = Accept(listenfd, (SA *)NULL, NULL);  
            if((pid = fork()) == 0)  
            {  
                Close(listenfd);struct sockaddr_in serv, guest;  
                char serv_ip[20];  
                char guest_ip[20];  
                socklen_t serv_len = sizeof(serv);  
                socklen_t guest_len = sizeof(guest);  
                getsockname(connfd, (struct sockaddr *)&serv, &serv_len);  
                getpeername(connfd, (struct sockaddr *)&guest, &guest_len);  
                Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));  
                Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));  
                printf("host %s:%d guest %s:%dn", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));  
                char buf[] = "hello world";  
                Write(connfd, buf, strlen(buf));  
                Close(connfd);  
                exit(0);  
            }  
            Close(connfd);  
        }  
    }

    客戶端的代碼:

    #include "unp.h"  
    #define DEST_IP "127.0.0.1"  
          
    int
    main(int argc, char ** argv)  
    {  
        int         sockfd, n;  
        char        buf[100];  
        char        serv_ip[20], guest_ip[20];  
        struct      sockaddr_in servaddr;  
          
        sockfd = Socket(AF_INET, SOCK_STREAM, 0);  
        bzero(&servaddr, sizeof(struct sockaddr_in));  
        servaddr.sin_family = AF_INET;  
        servaddr.sin_port = htons(10010);  
          
        Inet_pton(AF_INET, DEST_IP, &servaddr.sin_addr);  
        Connect(sockfd, (SA *)&servaddr, sizeof(servaddr));  
          
        struct sockaddr_in serv, guest;  
        socklen_t serv_len = sizeof(serv);  
        socklen_t guest_len = sizeof(guest);  
          
        getsockname(sockfd, (SA *)&guest, &guest_len);  
        getpeername(sockfd, (SA *)&serv, &serv_len);  
          
        Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));  
        Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));  
          
        printf("host  %s:%d, guest  %s:%dn", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));  
          
        n = Read(sockfd, buf, 100);  
        buf[n] = '�';  
        printf("%sn", buf);  
        Close(sockfd);  
        exit(0);  
    }

    TCP

    對於服務器來講,在bind之後就能夠調用getsockname來獲取本地地址和端口,雖然這沒有什麼太多的意義。getpeername只有在連接創建之後才調用,不然不能正確得到對方地址和端口,因此他的參數描述字通常是連接描述字而非監聽套接口描述字。

    對於客戶端來講,在調用socket時候內核還不會分配IP和端口,此時調用getsockname不會得到正確的端口和地址(固然連接沒創建更不可能調用getpeername),固然若是調用了bind 之後可使用getsockname。想要正確的到對方地址(通常客戶端不須要這個功能),則必須在連接創建之後,一樣連接創建之後,此時客戶端地址和端口就已經被指定,此時是調用getsockname的時機。

 

http://blog.chinaunix.net/zt/1016/unixjian_1016285.shtml

http://blog.csdn.net/yirancpp/article/details/8446879

http://tech.ddvip.com/2013-07/1374769636199661.html

相關文章
相關標籤/搜索