天天看點

C語言網絡程式設計:UDP通信實作

文章目錄

  • ​​UDP的特點:​​
  • ​​UDP的用途​​
  • ​​UDP程式設計模型​​
  • ​​UDP通信代碼實作​​
UDP的特點:
  1. udp 協定是一種無連結的不可靠傳輸協定,且UDP每次發送到分組資料大小都是固定的,它的主要特點如下:
  • 不建立連接配接
  • 沒有應答機制
  • 不會根據網絡狀況的好壞調整分組資料的大小

UDP之是以為不可靠傳輸協定,主要還是因為UDP沒有連結和應答機制,導緻UDP在發送資料的時候根本不清楚通信線路的連接配接情況,所有通過UDP發送資料的時候無法保證資料一定能夠發送給對方。是以UDP時不可靠的傳輸協定

  1. UDP協定每次傳輸資料時,必須指定對方的IP和端口号

    因為udp沒有連接配接的特性,所有UDP協定沒有自動記錄對方ip和端口号的特點,因為每次發送資料事,應用程式必須親自指定對方的ip和端口号,隻有這樣才能将資料發送給對方。

    如果是跨網通信,指定的ip就是對方的路由器公網ip

UDP的用途
  1. 資料量打但是允許資料丢失的傳輸服務(音視訊傳輸)
  2. 開發監控視訊相關得網絡傳輸資料程式時使用
UDP程式設計模型

UDP 程式設計模型非常簡單,通信雙發的實作基本一緻,且沒有伺服器的概念,如下圖

C語言網絡程式設計:UDP通信實作
  1. 建立socket通信

    這裡需要注意socket的内幾個參數設定

    第一個參數仍然為tcp/ip協定族:​​

    ​AF_INET​

    ​​ 第二個參數為傳輸格式:​

    ​SOCK_DGRAM​

  2. 建立綁定,使用​

    ​bind​

    ​函數和自己的ip和端口進行綁定
  3. 使用​

    ​sendto​

    ​​函數向對方發送資料,這裡需要注意的是​

    ​sendto​

    ​​函數需要填寫對方ip和端口号的參數,因為udp通信時需要明确對方裝置的ip和端口,是以發送資料包的時候需要對對方的ip和端口号進行指定

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

    const struct sockaddr *dest_addr, socklen_t addrlen);

  4. 最後接收資訊的時候會接收到從對方的ip和端口發送過來的資訊,因為消息的接收需要知道它的來源,而​

    ​struct sockaddr​

    ​​将會指定消息的來源。

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

    struct sockaddr *src_addr, socklen_t *addrlen);

PS:兩個通信程序在同一主機不能出現端口沖突,是以以下代碼測試是在同一主機上,此時需要設定不同的端口。

UDP通信代碼實作

​UDP_A.c​

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>

#define IP "192.168.102.174"
#define PORT 6000

typedef struct data {
  char name[30];
  unsigned int num;
}Data;

void print_err(char *str, int line, int err_no) {
  printf("%d, %s :%s\n",line,str,strerror(err_no));
  _exit(-1);
}

int cfd = -1;
//接收線程函數
void *receive(void *pth_arg) {
  int ret = 0;
  Data stu_data = {0};
  struct sockaddr_in addr0 = {0};
  int addr0_size = sizeof(addr0);
  //從對端ip和端口号中接收消息,指定addr0用于存放消息
  while(1) {
    bzero(&stu_data, sizeof(stu_data));
    ret = recvfrom(cfd, &stu_data, sizeof(stu_data),0, (struct sockaddr *)&addr0, &addr0_size); 
    if (-1 == ret) {
      print_err("recv failed",__LINE__,errno);
    }
    else if (ret > 0){
      printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);
      //列印對方的消息和端口号
      printf("ip %s,port %d\n",\
      inet_ntoa(addr0.sin_addr),ntohs(addr0.sin_port));
    }
  }
}

int main()
{
  int ret = -1;
  //建立tcp/ip協定族,指定通信方式為無連結不可靠的通信
  cfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == cfd) {
    print_err("socket failed", __LINE__, errno);
  }

  //進行端口号和ip的綁定
  struct sockaddr_in addr;
  addr.sin_family = AF_INET; //設定tcp協定族
  addr.sin_port = htons(PORT); //設定端口号
  addr.sin_addr.s_addr = inet_addr(IP); //設定ip位址
  ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));

  if ( -1 == ret) {
    print_err("bind failed",__LINE__,errno);
  }
  
  //建立線程函數,用于處理資料接收
  pthread_t id;
  ret = pthread_create(&id,NULL,receive,NULL);
  if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);

  struct sockaddr_in addr0;
  addr0.sin_family = AF_INET; //設定tcp協定族
  addr0.sin_port = htons(7000); //設定端口号
  addr0.sin_addr.s_addr = inet_addr(IP); //設定ip位址

  Data std_data = {0};
  //發送消息
  while (1) {
    bzero(&std_data, sizeof(std_data));
    printf("stu name:\n");
    scanf("%s",std_data.name);
    
    
    printf("stu num:\n");
    scanf("%d",&std_data.num);
    std_data.num = htonl(std_data.num);
    
    //發送消息時需要綁定對方的ip和端口号  
    ret = sendto(cfd, (void *)&std_data,sizeof(std_data), 0, (struct sockaddr *)&addr0, sizeof(addr0));
    if ( -1 == ret) {
      print_err("accept failed", __LINE__, errno);
    } 
  }
  return 0;
}      

另一端的實作基本和​

​UDP_A.c​

​一樣,因為是本機測試,隻有在發送的時候設定的端口号不一樣。如果跨網通信,則需要設定發送時指定的ip為 對方路由器的公網ip

​UDP_B.c​

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>

#define IP "192.168.102.174"
#define PORT 7000

typedef struct data {
  char name[30];
  unsigned int num;
}Data;

void print_err(char *str, int line, int err_no) {
  printf("%d, %s :%s\n",line,str,strerror(err_no));
  _exit(-1);
}

int cfd = -1;
void *receive(void *pth_arg) {
  int ret = 0;
  Data stu_data = {0};
  struct sockaddr_in addr0 = {0};
  int addr0_size = sizeof(addr0);
  while(1) {
    bzero(&stu_data, sizeof(stu_data));
    ret = recvfrom(cfd, &stu_data, sizeof(stu_data),0, (struct sockaddr *)&addr0, &addr0_size); 
    if (-1 == ret) {
      print_err("recv failed",__LINE__,errno);
    }
    else if (ret > 0){
      printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);
      printf("ip %s,port %d\n",\
      inet_ntoa(addr0.sin_addr),ntohs(addr0.sin_port));
    }
  }
}

int main()
{
  int ret = -1;
  cfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == cfd) {
    print_err("socket failed", __LINE__, errno);
  }

  struct sockaddr_in addr;
  addr.sin_family = AF_INET; //設定tcp協定族
  addr.sin_port = htons(PORT); //設定端口号
  addr.sin_addr.s_addr = inet_addr(IP); //設定ip位址
  ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));

  if ( -1 == ret) {
    print_err("bind failed",__LINE__,errno);
  }
  
  pthread_t id;
  ret = pthread_create(&id,NULL,receive,NULL);
  if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);

  struct sockaddr_in addr0;
  addr0.sin_family = AF_INET; //設定tcp協定族
  addr0.sin_port = htons(6000); //設定端口号
  addr0.sin_addr.s_addr = inet_addr(IP); //設定ip位址

  Data std_data = {0};
  while (1) {
    bzero(&std_data, sizeof(std_data));
    printf("stu name:\n");
    scanf("%s",std_data.name);
    
    printf("stu num:\n");
    scanf("%d",&std_data.num);
    std_data.num = htonl(std_data.num);

    ret = sendto(cfd, (void *)&std_data,sizeof(std_data), 0, (struct sockaddr *)&addr0, sizeof(addr0));
    if ( -1 == ret) {
      print_err("accept failed", __LINE__, errno);
    } 
  }
  return 0;
}      

編譯運作:

gcc UDP_B.c -o B -pthread
gcc UDP_A.c -o A -pthread