使用C语言实现tftp协议的客户端和服务端
时间: 2023-11-01 14:07:35 浏览: 244
TFTP协议是一种简单的文件传输协议,它可以用于在计算机之间传输文件。TFTP协议基于UDP协议,因此它不具备TCP协议的可靠传输保证。本文将介绍如何使用C语言实现TFTP协议的客户端和服务端。
## TFTP协议概述
TFTP协议是一种基于UDP协议的文件传输协议,它采用简单的请求/响应模式进行通信。TFTP协议主要有两种模式,分别是Netascii模式和Octet模式。
Netascii模式是一种ASCII码模式,它将传输的文件视为ASCII码字符流进行传输。在Netascii模式下,每个文件的换行符会被转换成Telnet的行结束符CR-LF(回车换行)。
Octet模式是一种二进制模式,它将传输的文件视为二进制数据流进行传输。在Octet模式下,每个文件的换行符会被保留。
TFTP协议定义了五种不同的请求/响应类型,分别是:
- RRQ:读请求
- WRQ:写请求
- DATA:数据包
- ACK:确认包
- ERROR:错误包
## TFTP客户端实现
TFTP客户端主要有两个功能,分别是向服务器请求文件和向服务器发送文件。下面是使用C语言实现TFTP客户端的代码示例。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 69
#define BUF_SIZE 512
void error(char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, n;
struct sockaddr_in servaddr;
char buf[BUF_SIZE];
if (argc < 3) {
fprintf(stderr,"usage: %s <filename> <mode>\n", argv[0]);
exit(1);
}
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
servaddr.sin_port = htons(SERVER_PORT);
// 发送读请求
sprintf(buf, "%c%c%s%c%s%c", 0, 1, argv[1], 0, argv[2], 0);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (n < 0)
error("ERROR sending read request");
// 接收数据
FILE *fp = fopen(argv[1], "wb");
while (1) {
// 接收数据包
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
if (n < 0)
error("ERROR receiving data");
// 解析数据包
unsigned short opcode = ntohs(*(unsigned short *)buf);
unsigned short block = ntohs(*(unsigned short *)(buf + 2));
if (opcode == 3) {
// 写入文件
fwrite(buf + 4, 1, n - 4, fp);
// 发送确认包
sprintf(buf, "%c%c%c%c", 0, 4, buf[2], buf[3]);
n = sendto(sockfd, buf, 4, 0, (struct sockaddr *)&cliaddr, len);
if (n < 0)
error("ERROR sending ACK");
if (n < BUF_SIZE - 4)
break;
} else if (opcode == 5) {
// 接收到错误包
fprintf(stderr, "ERROR: %s\n", buf + 4);
break;
} else {
// 接收到无法识别的数据包
fprintf(stderr, "ERROR: unrecognized packet\n");
break;
}
}
fclose(fp);
close(sockfd);
return 0;
}
```
上面的代码实现了TFTP客户端的读请求功能,它会向服务器发送一个RRQ请求,并接收服务器返回的数据包。如果接收到的数据包是一个数据包,则将数据写入指定的文件中,并发送一个ACK确认包。
## TFTP服务端实现
TFTP服务端主要有两个功能,分别是接收客户端的文件请求和接收客户端发送的文件。下面是使用C语言实现TFTP服务端的代码示例。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 69
#define BUF_SIZE 512
void error(char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, n;
struct sockaddr_in servaddr, cliaddr;
socklen_t len;
char buf[BUF_SIZE];
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(SERVER_PORT);
// 绑定套接字
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
error("ERROR on binding");
while (1) {
// 接收请求
len = sizeof(cliaddr);
n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
if (n < 0)
error("ERROR receiving request");
// 解析请求
unsigned short opcode = ntohs(*(unsigned short *)buf);
if (opcode != 1) {
// 接收到非读请求
sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 4, "Unsupported request", 0);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len);
if (n < 0)
error("ERROR sending error packet");
} else {
// 发送数据
FILE *fp = fopen(buf + 2, "rb");
if (fp == NULL) {
// 文件不存在
sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 1, "File not found", 0);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len);
if (n < 0)
error("ERROR sending error packet");
} else {
// 文件存在,发送数据包
unsigned short block = 1;
while (1) {
// 读取文件
n = fread(buf + 4, 1, BUF_SIZE - 4, fp);
if (n < BUF_SIZE - 4) {
if (feof(fp)) {
// 文件读取完成
sprintf(buf, "%c%c%c%c", 0, 3, block >> 8, block & 0xFF);
n += 4;
break;
} else {
// 文件读取出错
sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 2, "Error reading file", 0);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len);
if (n < 0)
error("ERROR sending error packet");
fclose(fp);
break;
}
} else {
// 发送数据包
sprintf(buf, "%c%c%c%c", 0, 3, block >> 8, block & 0xFF);
n += 4;
block++;
}
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, len);
if (n < 0) {
fclose(fp);
error("ERROR sending data");
}
// 接收ACK确认包
n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
if (n < 0) {
fclose(fp);
error("ERROR receiving ACK");
}
opcode = ntohs(*(unsigned short *)buf);
unsigned short ack_block = ntohs(*(unsigned short *)(buf + 2));
if (opcode != 4 || ack_block != block - 1) {
// 接收到错误的ACK确认包
sprintf(buf, "%c%c%c%c%s%c", 0, 5, 0, 0, "ACK packet error", 0);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&cliaddr, len);
if (n < 0)
error("ERROR sending error packet");
fclose(fp);
break;
}
}
fclose(fp);
}
}
}
close(sockfd);
return 0;
}
```
上面的代码实现了TFTP服务端的读请求功能,它会接收客户端发来的RRQ请求,并将指定的文件内容发送给客户端。如果接收到的数据包是一个ACK确认包,则继续发送下一个数据包。如果接收到的ACK确认包有误,则发送一个错误包告知客户端。
阅读全文