Linux——傳輸層基於UDP協議的套接字編程實例

UDP

1.含義:udp協議是傳輸層的一種協議。(User Datagram Protocol用戶數據報協議)

2.特點:無連接、不可靠、面向數據報。

  無連接:不用向服務端建立連接

  不可靠:數據傳輸的過程是一個不可靠的--數據可能會丟失

  面向數據報:數據報的傳輸方式

3.流程:

4.實現:

(1)首先封裝一個UdpSocket類來實現其基本的功能

//傳輸層基於UDP協議的網絡通信                             
//    1.創建套接字                
//    2.綁定地址信息 
//    3.發送數據
//    4.接收數據                    
//    5.關閉套接字                
#include <iostream>       
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string>
using namespace std;  
                                
#define CHECK_RET(q) if((q) == false){return -1;}
       
class UdpSocket{     
  public:         
    //將_sockfd的初始值給-1,
    UdpSocket():_sockfd(-1){}
                 
    //1.創建套接字
    bool Socket(){
      //socket(int domain, int type, int proto);
      //    domain:地址域的類型--ipv4/ipv6               
      //    type  :套接字類型--數據報/字節流
       //    proto :協議的類型--udp/tcp
       //  返回值:
       //    成功--0;失敗--<-1;
       _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
       if(_sockfd < 0){
         cerr << "socket error\n";
         return false;
       } 
       return true;
     }
 
     //2.綁定地址信息
     bool Bind(string& ip, u_int16_t port){
       sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_addr.s_addr = inet_addr(&ip[0]);
       addr.sin_port = htons(port);
       socklen_t len = sizeof(addr);
       //bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
       //    sockfd    :套接字描述符                                        
       //    addr      :地址信息
       //    addrlen   :地址結構的大小--字節
       //  返回值:
       //    成功--0; 失敗--》-1;
       int ret = bind(_sockfd, (sockaddr*)&addr, len);
      if(ret < 0){
        cerr << "bind error\n";
        return false;
      }
      return true;
    }

    //3.發送數據
    bool Send(string& buf, string& ip, u_int16_t& port){
      //sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
     //     sockfd    :套接字描述符
     //     buf       :發送的字符串的首地址
     //     len       :發送的數據的長度
     //     flags     :標誌位,0--阻塞發送
     //     dest_addr :目的端地址
     //     addrlen   :地址大小
     //   返回值:
     //     成功--返回已發送的字符數目;失敗--》-1
      sockaddr_in addr;                                                                                             
      addr.sin_family = AF_INET;
      addr.sin_addr.s_addr = inet_addr(&ip[0]);
      addr.sin_port = htons(port);
      socklen_t len = sizeof(addr);
      int ret = sendto(_sockfd, &buf[0], buf.size(), 0, (sockaddr*)&addr, len);
      if(ret < 0){
        cerr << "send error\n";
        return false;
      }
      return true;
    }

    //4.接收數據
    bool Recv(string& buf, string& ip, uint16_t& port){
      //recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
      //    sockfd      :套接字描述符
      //    buf         :接收到接收緩衝區
      //    len         :要接收的數據大小
      //    flags       :標誌位,0--阻塞接收
      //    src_addr    :源端地址
      //    addrlen     :地址大小;源端地址非空--就是源端地址的大小;源端地址空--他就是NULL
      //  返回值:
      //    > 0 :接收到的字節數
      //    ==0 :表示連接已經斷開
      //    -1  :接收錯誤
      char tmp[1024] = {0};
      sockaddr_in addr;                                                                                         
      socklen_t len = sizeof(addr);
      int ret = recvfrom(_sockfd, tmp, 1024, 0, (sockaddr*)&addr, &len);
      if(ret < 0 ){
        cerr << "recvfrom error\n";
        return false;
      }else if(ret == 0){
        cout << "peer shutdown\n";
        return false;
      }
      ip = inet_ntoa(addr.sin_addr);
      port = ntohs(addr.sin_port);
      buf.assign(tmp,ret);
      return true;
    }

    //5.關閉套接字
    bool Close(){
      if(_sockfd < 0){
        cerr << "close error\n";
        return false;
      }
      close(_sockfd);
      return true;
    }

    ~UdpSocket(){                                                                    
      Close();
    }
  private:
    int _sockfd;    //套接字描述符--文件描述符(網絡通信的操作句柄)
};

(2)服務端程序

//傳輸層基於UDP協議的服務端程序                                  
//    1.創建套接字
//    2.綁定地址信息
//    3.接收客戶端發送的數據
//    4.發送數據                
//    5.關閉套接字
#include "udp_socket.hpp"
                
int main(int argc, char* argv[]){
  //判斷參數是否足夠
  if(argc != 3){                 
    cout << "./udp_srv 192.168.136.146 9000\n";
    return -1;      
  }            
  string ip = argv[1];                  
  uint16_t port = atoi(argv[2]);         
                
  UdpSocket sock;
  //1.創建套接字
  CHECK_RET(sock.Socket());
  //2.綁定地址信息                      
  CHECK_RET(sock.Bind(ip, port));
  while(1){    
    //3.循環收發數據
    string buf;
    CHECK_RET(sock.Recv(buf, ip, port));
    cout << "client say:" << buf << endl;
    buf.clear();
    cin >> buf;

    //4.發送數據
    CHECK_RET(sock.Send(buf, ip, port)); 
  }
  sock.Close();
  return 0;
}

(3)客戶端程序

//傳輸層基於UDP協議的客戶端                                             
#include "udp_socket.hpp"
                
int main(int argc, char* argv[]){            
  //判斷參數是否足夠
  if(argc != 3){
    cout << "./udp_cli serverip serverport";
    return -1;                  
  }              
  string ip = argv[1];
  uint16_t port = atoi(argv[2]);
  UdpSocket sock;                   
  //1.創建套接字                   
  CHECK_RET(sock.Socket());
  //2.綁定地址信息--可以不用手動綁定
  //CHECK_RET(sock.Bind("192.168.136.148", 8000));
  //進入循環收發數據
  while(1){                             
    string buf; 
    cin >> buf;                                                 
    CHECK_RET(sock.Send(buf, ip, port));
    buf.clear();
    //buf.clear();    //發完以後緩衝區需要清空嗎????????
    CHECK_RET(sock.Recv(buf, ip, port));
    cout << "server say:" << buf << endl;
  }
  sock.Close();
  return 0;
}                     

5.UDP協議的報頭信息

16bitUDP長度:報頭+數據;也就是說udp所能發送的數據長度最大是64k-8(2^16  -  8);

16位的校驗和:int sum = 0;sum +=~data[i];  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章