Linux網絡編程之基於UDP實現可靠的文件傳輸示例

瞭解網絡傳輸協議的人都知道,採用TCP實現文件傳輸很簡單。相對於TCP,由於UDP是面向無連接、不可靠的傳輸協議,所以我們需要考慮丟包和後發先至(包的順序)的問題,所以我們想要實現UDP傳輸文件,則需要解決這兩個問題。方法就是給數據包編號,按照包的順序接收並存儲,接收端接收到數據包後發送確認信息給發送端,發送端接收確認數據以後再繼續發送下一個包,如果接收端收到的數據包的編號不是期望的編號,則要求發送端重新發送。

下面展示的是基於linux下C語言實現的一個示例程序,該程序定義一個包的結構體,其中包含數據和包頭,包頭裏包含有包的編號和數據大小,經過測試後,該程序可以成功傳輸一個視頻文件。

具體實現代碼如下:

server端代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*************************************************************************
  > File Name: server.c
  > Author: SongLee
 ************************************************************************/
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<netdb.h>
#include<stdarg.h>
#include<string.h>
  
#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
  
/* 包頭 */
typedef struct
{
  int id;
  int buf_size;
}PackInfo;
  
/* 接收包 */
struct SendPack
{
  PackInfo head;
  char buf[BUFFER_SIZE];
} data;
  
  
int main()
{
  /* 發送id */
  int send_id = 0;
  
  /* 接收id */
  int receive_id = 0;
  
  /* 創建UDP套接口 */
  struct sockaddr_in server_addr;
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(SERVER_PORT);
  
  /* 創建socket */
  int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if(server_socket_fd == -1)
  {
    perror("Create Socket Failed:");
    exit(1);
  }
  
  /* 綁定套接口 */
  if(-1 == (bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr))))
  {
    perror("Server Bind Failed:");
    exit(1);
  }
  
  /* 數據傳輸 */
  while(1)
  {  
    /* 定義一個地址,用於捕獲客戶端地址 */
    struct sockaddr_in client_addr;
    socklen_t client_addr_length = sizeof(client_addr);
  
    /* 接收數據 */
    char buffer[BUFFER_SIZE];
    bzero(buffer, BUFFER_SIZE);
    if(recvfrom(server_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&client_addr, &client_addr_length) == -1)
    {
      perror("Receive Data Failed:");
      exit(1);
    }
  
    /* 從buffer中拷貝出file_name */
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name,FILE_NAME_MAX_SIZE+1);
    strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
    printf("%s\n", file_name);
  
    /* 打開文件 */
    FILE *fp = fopen(file_name, "r");
    if(NULL == fp)
    {
      printf("File:%s Not Found.\n", file_name);
    }
    else
    {
      int len = 0;
      /* 每讀取一段數據,便將其發給客戶端 */
      while(1)
      {
        PackInfo pack_info;
  
        if(receive_id == send_id)
        {
          ++send_id;
          if((len = fread(data.buf, sizeof(char), BUFFER_SIZE, fp)) > 0)
          {
            data.head.id = send_id; /* 發送id放進包頭,用於標記順序 */
            data.head.buf_size = len; /* 記錄數據長度 */
            if(sendto(server_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0)
            {
              perror("Send File Failed:");
              break;
            }
            /* 接收確認消息 */
            recvfrom(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, &client_addr_length);
            receive_id = pack_info.id; 
          }
          else
          {
            break;
          }
        }
        else
        {
          /* 如果接收的id和發送的id不相同,重新發送 */
          if(sendto(server_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0)
          {
            perror("Send File Failed:");
            break;
          }
          /* 接收確認消息 */
          recvfrom(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, &client_addr_length);
          receive_id = pack_info.id; 
        }
      }
      /* 關閉文件 */
      fclose(fp);
      printf("File:%s Transfer Successful!\n", file_name);
    }
  }
  close(server_socket_fd);
  return 0;
}

client端代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*************************************************************************
  > File Name: client.c
  > Author: SongLee
 ************************************************************************/
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<netdb.h>
#include<stdarg.h>
#include<string.h>
  
#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
  
/* 包頭 */
typedef struct 
{
  int id;
  int buf_size;
}PackInfo;
  
/* 接收包 */
struct RecvPack
{
  PackInfo head;
  char buf[BUFFER_SIZE];
} data;
  
  
int main()
{
  int id = 1;
  
  /* 服務端地址 */
  struct sockaddr_in server_addr;
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  server_addr.sin_port = htons(SERVER_PORT);
  socklen_t server_addr_length = sizeof(server_addr);
  
  /* 創建socket */
  int client_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if(client_socket_fd < 0)
  {
    perror("Create Socket Failed:");
    exit(1);
  }
  
  /* 輸入文件名到緩衝區 */
  char file_name[FILE_NAME_MAX_SIZE+1];
  bzero(file_name, FILE_NAME_MAX_SIZE+1);
  printf("Please Input File Name On Server: ");
  scanf("%s", file_name);
  
  char buffer[BUFFER_SIZE];
  bzero(buffer, BUFFER_SIZE);
  strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
  
  /* 發送文件名 */
  if(sendto(client_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&server_addr,server_addr_length) < 0)
  {
    perror("Send File Name Failed:");
    exit(1);
  }
  
  /* 打開文件,準備寫入 */
  FILE *fp = fopen(file_name, "w");
  if(NULL == fp)
  {
    printf("File:\t%s Can Not Open To Write\n", file_name); 
    exit(1);
  }
  
  /* 從服務器接收數據,並寫入文件 */
  int len = 0;
  while(1)
  {
    PackInfo pack_info;
  
    if((len = recvfrom(client_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&server_addr,&server_addr_length)) > 0)
    {
      if(data.head.id == id)
      {
        pack_info.id = data.head.id;
        pack_info.buf_size = data.head.buf_size;
        ++id;
        /* 發送數據包確認信息 */
        if(sendto(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0)
        {
          printf("Send confirm information failed!");
        }
        /* 寫入文件 */
        if(fwrite(data.buf, sizeof(char), data.head.buf_size, fp) < data.head.buf_size)
        {
          printf("File:\t%s Write Failed\n", file_name);
          break;
        }
      }
      else if(data.head.id < id) /* 如果是重發的包 */
      {
        pack_info.id = data.head.id;
        pack_info.buf_size = data.head.buf_size;
        /* 重發數據包確認信息 */
        if(sendto(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0)
        {
          printf("Send confirm information failed!");
        }
      }
      else
      {
  
      }
    }
    else
    {
      break;
    }
  }
  
  printf("Receive File:\t%s From Server IP Successful!\n", file_name);
  fclose(fp);
  close(client_socket_fd);
  return 0;
}

感興趣的朋友可以動手測試一下該程序,相信會對大家的Linux下C語言網絡編程帶來一定的幫助。

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