Linux下的socket編程實踐(二)socket編程基本API簡介

來源:互聯網
上載者:User

Linux下的socket編程實踐(二)socket編程基本API簡介

Socket是什麼

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的檔案,一些socket函數就是對其進行的操作(讀/寫IO、開啟、關閉)。 說白了Socket是應用程式層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

Socket可以看成是使用者進程與核心網路通訊協定棧的介面(編程介面, 如所示), 其不僅可以用於本機處理序間通訊,可以用於網路上不同主機的處理序間通訊, 甚至還可以用於異構系統之間的通訊。

 

如TCP/IP協議棧已經屬於核心的一部分了,被實現好了,路由器工作於網路層(Router),Application是需要我們去實現的。Socket可以看作是使用者進程和核心網路通訊協定棧的編程介面,可以把socket看作處理序間通訊的一種方式,和管道不同,他是全雙工系統的,可用於本機和不同主機之間的處理序間通訊,異構通訊也是可以的。(例如手機和電腦,分別是ARM和X86架構)

Pv4套介面地址結構

IPv4套介面地址結構通常也稱為“網際通訊端地址結構”,它以“sockaddr_in”命名,定義在標頭檔中

 

struct sockaddr_in  {      uint8_t  sin_len;      sa_family_t  sin_family;      in_port_t   sin_port;   //2位元組      struct in_addr  sin_addr;   //4位元組      char sin_zero[8];   //8位元組  };  

 

成員說明:

sin_len:整個sockaddr_in結構體的長度,在4.3BSD-Reno版本之前的第一個成員是sin_family.

sin_family:指定該地址家族,對於IPv4來說必須設為AF_INET(Socket不僅可以用於TCP/IP還可以用於UNIX域協議)

sin_port:連接埠

sin_addr:IPv4的地址;

sin_zero:暫不使用,一般將其設定為0

通用地址結構

用來指定與通訊端關聯的地址(可以支援其他協議).

struct sockaddr  {      uint8_t  sin_len;      sa_family_t  sin_family;      char sa_data[14];   //14位元組     };  

說明:

sin_len:整個sockaddr結構體的長度

sin_family:指定該地址家族

sa_data:由sin_family決定它的形式。

注意:使用的時候通常把IPv4的地址結構強制轉換成通用地址結構,就像上面的sockaddr_in 轉換為sockaddr

 

網路位元組序

 

大端位元組序和小端位元組序 的出現是為了異構系統之間的使用

1.大端位元組序(Big Endian)

最高有效位(MSB:Most Significant Bit)儲存於最低記憶體位址處,最低有效位(LSB:Lowest Significant Bit)儲存於最高記憶體位址處。

2.小端位元組序(Little Endian)

最高有效位(MSB:Most Significant Bit)儲存於最高記憶體位址處,最低有效位(LSB:Lowest Significant Bit)儲存於最低記憶體位址處。

3.主機位元組序

不同的主機有不同的位元組序,如x86為小端位元組序,Motorola 6800為大端位元組序,ARM位元組序是可配置的。

4.網路位元組序

網路位元組序規定為大端位元組序


 

判斷自己主機的位元組序是哪一種?

//測試當前系統是否為小端模式  int main()  {      int data = 0x12345678;  //int = 4位元組(32位)                              //每4個二進位位代表1位十六進位位,                              //則8位十六進位位代表4*8=32位二進位位      char *p = (char *)&data;      printf("%x, %x, %x, %x\n",p[0],p[1],p[2],p[3]);        //0x78屬於低位,如果其放在了p[0](低地址)處,則說明是小端模式      if (p[0] == 0x78)      {          cout << "當前系統為小端模式" << endl;    //x86平台為小端模式      }      else if (p[0] == 0x12)      {          cout << "當前系統為大端模式" << endl;    //IBM為大端模式      }  }  
如果是小端模式,那麼原串輸出是 78 56 34 12

位元組序轉換函式(常用於連接埠轉換)

 

uint32_t htonl(uint32_t hostlong);  uint16_t htons(uint16_t hostshort);  uint32_t ntohl(uint32_t netlong);  uint16_t ntohs(uint16_t netshort);  /**說明: h代表(local)host;n代表network; s代表short;l代表long; */  
//測試轉換結果  int main()  {      int localeData = 0x12345678;      char *p = (char *)&localeData;      printf("Begin: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);      //將本地位元組轉換成網路位元組      int inetData = htonl(localeData);      p = (char *)&inetData;      printf("After: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);        if (p[0] == 0x12)          cout << "網路系統為大端模式" << endl;      else          cout << "網路系統為小端模式" << endl;      printf("host:%x, inet:%x\n", localeData, inetData);  }  
地址轉換函式(用於IP地址轉換)

 

 

#include   #include   int inet_aton(const char *cp, struct in_addr *inp);  in_addr_t inet_addr(const char *cp);      char *inet_ntoa(struct in_addr in);  //in_addr定義如下:  typedef uint32_t in_addr_t;  struct in_addr  {      in_addr_t s_addr;  };  
//實踐  int main()  {      //將點分十進位轉換成十進位數      cout << inet_addr("192.168.139.137") << endl;        //將十進位數轉換成點分十進位形式      struct in_addr address;      address.s_addr = inet_addr("192.168.139.137");      cout << inet_ntoa(address) << endl;        memset(&address,0,sizeof(address));      inet_aton("127.0.0.1", &address);      cout << address.s_addr << endl;      cout << inet_ntoa(address) << endl;      return 0;  }  

 

通訊端類型

1)流式通訊端(SOCK_STREAM)

提供連線導向的、可靠的Data Transmission Service,資料無差錯,無重複的發送,且按發送順序接收, 對應TCP協議。

2)資料報式通訊端(SOCK_DGRAM)

提供無串連服務。不提供無錯保證,資料可能丟失或重複,並且接收順序混亂, 對應UDP協議。

3)原始通訊端(SOCK_RAW)

使我們可以跨越傳輸層直接對IP層進行封裝傳輸。(應用程式層直接和IP層)



socket函數

 

#include   #include   int socket(int domain, int type, int protocol);  

 

 

建立一個通訊端用於通訊

參數:

domain:指定通訊協定族(protocol family),常用取值AF_INET(IPv4)

type:指定socket類型, 流式通訊端SOCK_STREAM,資料通訊端SOCK_DGRAM,原始通訊端SOCK_RAW

protocol:協議類型,常用取值0, 使用預設協議

傳回值:

成功: 返回非負整數,通訊端;

失敗: 返回-1

 

bind函數

 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

綁定一個本地地址到通訊端

參數:

sockfd:socket函數返回的通訊端

addr:要綁定的地址

 

//sockaddr_in結構, bind時需要強制轉換成為struct sockaddr*類型  struct sockaddr_in  {      sa_family_t    sin_family; /* address family: AF_INET */      in_port_t      sin_port;   /* port in network byte order */      struct in_addr sin_addr;   /* internet address */  };  /* Internet address. */  struct in_addr  {      uint32_t       s_addr;     /* address in network byte order */  };  

 

 

/**樣本:INADDR_ANY的使用, 綁定本機任意地址**/  int main()  {      int listenfd = socket(AF_INET, SOCK_STREAM, 0);      if (listenfd == -1)          err_exit("socket error");        struct sockaddr_in addr;      addr.sin_family = AF_INET;      addr.sin_port = htons(8001);      //綁定原生任意一個IP地址, 作用同下面兩行語句      addr.sin_addr.s_addr = htonl(INADDR_ANY);      //inet_aton("127.0.0.1", &addr.sin_addr);      //addr.sin_addr.s_addr = inet_addr("127.0.0.1");      if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)          err_exit("bind error");      else          cout << "bind success" << endl;  }  

 

listen函數

 

int listen(int sockfd, int backlog);  

 

listen函數應該用在調用socket和bind函數之後, 並且用在調用accept之前, 用於將一個通訊端從一個主動通訊端轉變成為被動通訊端。

backlog說明:

對於給定的監聽套介面,核心要維護兩個隊列:

1、已由客戶發出併到達伺服器,伺服器正在等待完成相應的TCP三路握手過程(SYN_RCVD狀態)

2、已完成串連的隊列(ESTABLISHED狀態)

但是兩個隊列長度之和不能超過backlog

backlog推薦使用SOMAXCONN(3.13.0-44-generic中該值為128), 使用等待隊列的最大值;

bind之後變成了被動通訊端,接受串連;主動通訊端是用來發起串連,例如使用connect。

accept函數

 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

 

從已完成串連隊列返回第一個串連(the first connection request on the queue of pending connections for the listening

socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state),如果已完成串連隊列為空白,則阻塞。The original socket sockfd is unaffected by this call.

 

參數:

sockfd:伺服器通訊端

addr:將返回對等方的通訊端地址, 不關心的話, 可以設定為NULL

addrlen:返回對等方的通訊端地址長度, 不關心的話可以設定成為NULL, 否則一定要初始化

傳回值: On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On error, -1 is returned, and errno is set appropriately.

connect函數

 

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);   

 

建立一個串連至addr所指定的通訊端

參數:

sockfd:未串連通訊端

addr:要串連的通訊端地址

addrlen:第二個參數addr長度

 

樣本:echo server/client實現

 

//server端代碼  int main()  {      int listenfd = socket(AF_INET, SOCK_STREAM, 0);      if (listenfd == -1)          err_exit("socket error");        struct sockaddr_in addr;      addr.sin_family = AF_INET;      addr.sin_port = htons(8001);      addr.sin_addr.s_addr = htonl(INADDR_ANY);      if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)          err_exit("bind error");      if (listen(listenfd, SOMAXCONN) == -1)          err_exit("listen error");        char buf[512];      int readBytes;      struct sockaddr_in clientAddr;      //謹記: 此處一定要初始化  socklen_t addrLen = sizeof(clientAddr);      while (true)      {          int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);          if (clientfd == -1)              err_exit("accept error");          //列印客戶IP地址與連接埠號碼          cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)               << ", " << ntohs(clientAddr.sin_port) << endl;            memset(buf, 0, sizeof(buf));          while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)          {              cout << buf;              if (write(clientfd, buf, readBytes) == -1)                  err_exit("write socket error");              memset(buf, 0, sizeof(buf));          }          if (readBytes == 0)          {              cerr << "client connect closed..." << endl;              close(clientfd);          }          else if (readBytes == -1)              err_exit("read socket error");      }      close(listenfd);  }  

//client端代碼  int main()  {      int sockfd = socket(AF_INET, SOCK_STREAM, 0);      if (sockfd == -1)          err_exit("socket error");        //填寫伺服器連接埠號碼與IP地址      struct sockaddr_in serverAddr;      serverAddr.sin_family = AF_INET;      serverAddr.sin_port = htons(8001);      serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");      if (connect(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)          err_exit("connect error");        char buf[512];      while (fgets(buf, sizeof(buf), stdin) != NULL)      {          if (write(sockfd, buf, strlen(buf)) == -1)              err_exit("write socket error");          memset(buf, 0, sizeof(buf));          int readBytes = read(sockfd, buf, sizeof(buf));          if (readBytes == 0)          {              cerr << "server connect closed... \nexiting..." << endl;              break;          }          else if (readBytes == -1)              err_exit("read socket error");          cout << buf;          memset(buf, 0, sizeof(buf));      }      close(sockfd);  }  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.