基於數據報(UDP)編程的接口總結

Udp編程接口

socket():創建套接字
  • 1.使用socket()函數創建一個文件描述符
int socket(AF_INET,SOCK_DGRAM,0);
//參數一:表示改文件描述符使用的是IPV4
//參數二:表示該文件描述符使用的是UDP協議
//參數三:一般不使用
  • 參數domain 用來說明網路程序所在的主機採用的是那種通信協議簇,這些協議族再頭文件<sys/socket.h>中定義

  • AF_INET //表示IPv4網絡協議

  • AF_INET6 //IPv6網絡協議

  • 參數type 用來指明創建的套接字類型,

  • SOCK_STREAM:流式套接字,面向連接可靠的通信類型

  • SOCK_DGRAM:數據報套接字,非面向連接和不可靠的通信類型

  • SOCK_RAW:原始套接字,用來直接訪問IP協議

  • 參數protocol 指定套接字使用的協議,一般採用默認值0,表示讓系統根據地址格式和套接字類型,自動選擇一個合適的協議

  • 返回值:

  • 調用成功就創建了一個新的套接字並返回它的描述符,在之後對該套接字的操作中都要藉助這個文件描述符,

  • 否則返回-1(<0) 表示套接字出錯.應用程序可調用WSAGetLastError() 獲取相應的錯誤代碼

bind ():將套接字綁定到指定的網絡地址

使用這個函數前需要將服務器的ip和端口號賦值到結構體sockaddr_in中

使用bind()函數前的準備工作

  • sockaddr 結構:針對各種通信域的套接字,存儲它們的地址信息
struct sockaddr{
    u_short sa_family;   /*16位協議家族*/
    char sa_data[14]    /*14字節協議地址*/
};
  • sockaddr_in結構:專門針對Internet通信域,存儲套接字相關的網絡地址信息(IP地址、傳輸層端口號等信息)
struct sockadd_in{
  short int sin_family;   //地址家族
  unsigned short int sin_port; //端口號
  struct in_addr sin_addr;  //IP地址
  unsigned char sin_zero[8];  //全爲0    
};
  • in_addr 結構:專門用來存儲IP地址
struct in_addr{
    unsigned long s_addr;
};
#include<netinet/in.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
  • 參數sockfd:是未經綁定的套接字文件描述符,是由socket()函數返回的,要將它綁定到指定的網絡地址上

  • 參數addr : 是一個指向sockaddr結構變量的指針,所指結構體中保存着特定的網絡地址,就是要把套接字sockfd綁定到這個地址上.

  • 參數 addrlen :是結構sockaddr結構的長度,等於sizeof(struct sockadd

  • 返回值

  • 返回0:表示已經正確的實現了綁定

  • 如果返回SOCKET_ERROR表示有錯。應用程序可調WSAGetLastError()獲取相應的錯誤代碼

  • 最後在函數調用的時候,將這個結構強制轉換成sockaddr類型
本機字節序和網絡字節序
  • 本機字節序在不同的計算機中,存放多字節值的順序是不一樣的,有的是從低到高,有的是從高到低.計算機中的多字節數據的存儲順序稱爲本機字節順序.

  • 網絡字節序:在網絡協議中,對多字節數據的存儲,有它自己的規定。多字節數據在網絡協議報頭中的存儲順序,稱爲網絡字節序.在套接字中必須使用網絡字節序

在這裏插入圖片描述

所以把IP地址和端口號裝入套接字時,我們需要將本機字節序抓換成網絡字節序,在本機輸出時,我們需要將它們從網絡字節序轉換成本機字節序

  • 套接字編程接口專門爲解決這個問題設置了4個函數

  • 1.htons():短整數本機順序轉換爲網絡順序,用於端口號

  • 2.htonl():長整數本機順序轉換成網絡順序,用於IP地址

  • 3.ntohs():短整數網絡順序轉換爲本機順序,用於端口號

  • 4.ntohl():長整數網絡順序轉換爲本機順序,用於IP地址

這4個函數將被轉換的數值作爲函數的參數,函數返回值作爲轉換後的結果

  • 點分十進制的IP地址的轉換

在Internet中,IP地址常常是用點分十進制的表示方法,但在套接字中,IP地址是無符號的長整型數,套接字編程接口設置了兩個函數,專門用來兩種形式IP地址的轉換

  • 1.inet_addr函數
unsigned long inet_addr(const char* cp);

/*
入口參cp:點分十進制形式的IP地址
返回值:網絡字節順序的IP地址,是無符號的長整數
*/
  • 2.inet_ntoa函數
char* inet_ntoa(struct in_addr in)

/*
入口參數in: 包含長整數IP地址的in_addr結構變量
返回值:指向點分十進制IP地址的字符串的指針
*/
recvfrom()接收一個數據報並保存源地址,從數據報套接字接收數據
調用格式
int recvfrom(SOCKET s,char *buf,int len,int falgs,
struct sockaddr* from, int* fromlen);
  • 參數s:接收端的數據報套接字描述符,包含接收方的網絡地址,從這個套接字接收數據報

  • 參數buf:字符串指針,指向用戶進程的接收緩衝區,用來接收從套接字收到的數據報

  • 參數len:用戶接收緩衝區的長度,制定了所能接收的最大字節數

  • 參數flags:接收的方式,一般置爲0.

  • 參數from: 指向sockaddr 結構的指針,實際上是一個出口參數,當本次調用成功執行後,在這個結構中返回了發送方的網絡地址,包括對方的IP地址和端口號

  • 參數fromlen:整數型指針,也是一個出口型參數,本調用結束時,返回存在from中的網絡地址長度

  • 返回值: 如果正確接收,則返回實際收到的字節數,如果出錯,返回SOCKET_ERROR,應用程序可通過WSAGetLastError()獲取相應的錯誤代碼

函數功能
  • 本函數從s套接口 的接收緩衝區隊列中,取出第一個數據報,把它放到用戶進程 的緩衝區buf中,但最多不超過用戶緩衝區的大小。如果數據報大於用戶緩衝區長度,那麼用戶緩衝區中只有數據報的前面部分,後面的數據都會丟失,並且recvfrom()函數WSAEMSGSIZE錯誤
sendto():按照指定目的地向數據報套接字發送數據
調用格式
int sendto(SOCKET s,char* buf,int len,int flags,struct sockaddr* to,
int tolen)
  • 參數s:發送方的數據報套接字 描述符,包含發送方的網絡地址,數據報通過這個套接字向對方發送

  • 參數buf:指向用戶進程發送緩衝區的字符串指針,該緩衝區包含將要發送的數據,

  • 參數len:用戶發送緩衝區中要發送的數據的長度,是可以發送的最大字節數

  • 參數flags: 指定函數的執行方式,一般置爲0.

  • 參數to:指向sockaddr 結構的指針,指定接收數據報的目的套接字的完整的網絡地址

  • 參數tolen: to的地址長度,等於sizeof(struct sockaddr)

  • 返回值:如果發送成功,則返回實際發送的字節數,注意這個數字可能小於len中所規定的長度,如果出錯返回錯誤碼

函數功能

本函數專用於數據報套接字,用來向發送端的本地套接字發送一個數據報.套接字會將數據下交給傳輸層的UDP,由它向對方發送.

  • 容易看出,這個調用需要決定通信的兩個端點,需要一個全相關的五元組信息,既協議(UDP)、源IP地址、源IP地址、源端口號、目的IP地址和目的端口.通信一端由發送方套接字s指定,通信的另一端由to結構決定
實例代碼
udp_server.cc
//先實現UDP版本的服務器

#include<stdio.h>
#include<sys/socket.h>  //socket所用的頭文件
#include<netinet/in.h>  //socketaddr_in 結構體的頭文件
#include<arpa/inet.h>   //inet.addr的頭文件
#include<cstring>
#include<unistd.h>

int main(){
  //1.先創建一個socket
  // AF_INET是一個宏 ,表示使用 ipv4 協議
  //SOCK_DGRAM   表示使用 UDP 協議
  int sock=socket(AF_INET,SOCK_DGRAM,0);
  if(sock<0){
    perror("socket");
    return 1;
  }
  //2.把當前的socket  綁定上一個ip + 端口號
  sockaddr_in addr;   //是一個結構體
  addr.sin_family=AF_INET;
  addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //0.0.0.0將本地所有的ip都包含進來 
  //把點分十進制轉換成32位整數
  //設置ip地址
  //ip 地址也是一個整數,也需要轉成網絡字節序,只不過
  //int_addr 函數自動幫我們轉了
  addr.sin_port=htons(8080);
  //端口號必須得先轉成網絡字節序
  //9090 TCP端口,這個端口不是系統默認的端口,
  //屬於自定義的端口
  int ret=bind(sock,(sockaddr*)&addr,sizeof(addr));
  if(ret<0){
    perror("bind");
    return 1;
  }

  printf("server start ok!\n");
  //3.處理服務器收到的請求
  while(true){
    //1.讀取客戶端的請求
    //面向數據報的函數
    sockaddr_in peer;//獲取客戶端的ip
    socklen_t len=sizeof(peer);//表示一個整數
    char buf[1024]={0};
    ssize_t n= recvfrom(sock,buf,sizeof(buf)-1,0,(sockaddr*)&peer,&len);
  
    printf("客戶端發來消息:%s\n",buf);
    printf("請輸入回覆內容:");
   if(n<0){
     perror("recvfrom");
     continue;//不要因爲一次請求的失利就結束整個程序
   }
   buf[n]='\0';
   //double check
    //2.根據請求計算響應時間
    //[略]此處寫的是一個回顯服務器(echo server)
    //3.把響應寫回客戶端
    char buf2[1024]="0";
    scanf("%s",buf2);
    n=sendto(sock,buf2,strlen(buf2),0,(sockaddr*)&peer,len);
    if(n<0){
      perror("sendto");
      continue;
    }
    printf("[%s:%d] buf: %s\n",inet_ntoa(peer.sin_addr),
      ntohs(peer.sin_port), buf2);
  }
  close(sock);
  return 0;
}
ucp_client.cc
//實現UDP版本的客戶端

#include<stdio.h>
#include<sys/socket.h>  //socket所用的頭文件
#include<netinet/in.h>  //socketaddr_in 結構體的頭文件
#include<arpa/inet.h>   //inet.addr的頭文件
#include<cstring>
#include<unistd.h>


//./client [ip]
int main(int argc,char *argv[]){

  //1.先創建一個socket
  int sock=socket(AF_INET,SOCK_DGRAM,0);
  if(sock<0){
    perror("socket");
    return 1;
  }
  //2.客戶端一般不需要bind
  //bind  意味着和某個具體的端口號關聯在一起
  //如果沒有bind,操作系統會隨機分配一個端口號
  //如果是服務器程序不去bind ,就會導致每次啓動服務器的端口發生變化
  //客戶端就沒法連接了
  //如果客戶端bind的話,可能會出現問題
  //通常情況下,一個端口號只能被一個進程bind
  //如果客戶端bind一個端口號其他客戶端也可能bind同一個端口號
  //就會出錯
  //客戶端最好還是讓操作系統隨機分配更科學
  
  //2.準備好服務器的sockaddr_in 結構
  
  sockaddr_in  server_addr;
  server_addr.sin_family=AF_INET;
  server_addr.sin_addr.s_addr=inet_addr(argv[1]);
  server_addr.sin_port=htons(8080);
   
  //3.客戶端直接發送數據即可
  while(1){
    char buf[1024]={0};
    printf("請輸入一段內容:");
    fflush(stdout);
    scanf("%s",buf);
    sendto(sock,buf,strlen(buf),0,
        (sockaddr*)&server_addr,sizeof(server_addr));
    //從服務器接收一下返回結果
    char buf_output[1024]={0};
    //後兩個參數填NULL 表示不關注對端的地址
    recvfrom(sock,buf_output,sizeof(buf_output)-1,0,
          NULL,NULL);
    printf("客戶端迴應:%s\n",buf_output);
  }
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章