1.1 TFTP の概要、通信プロセス
1.1.1 TFTPの概要
TFTP: Trivial Text Transfer Protocol;
元々はディスクレス システムの起動に使用され、小さなファイルを転送するために設計されました
特徴:
UDPプロトコルに基づいて実現
ユーザー正当性認証なし
データ転送モード:
オクテット: バイナリ モード
netascii: テキスト モード
メール: サポートされなくなりました
1.1.2 TFTP通信処理
TFTP通信の概要
- サーバーはポート 69 でクライアントのリクエストを待ちます。
- サーバーがリクエストを承認すると、一時ポートを使用してクライアントと通信します。
- 各パケットの番号が変わります(1から始まります)
- すべてのデータ パケットは ACK によって確認される必要がある タイムアウトが発生した場合は、最後のパケット (データまたは ACK) を再送信する必要がある
- データの長さは512Byteで送信されます
- データが 512 バイト未満の場合は転送の終了を意味します
1.1.3 TFTPプロトコルの解析
注:
上記の 0 は「\0」を表します。
異なるエラー コードは異なるエラー メッセージに対応します。
エラー コード:
0: 未定義、エラー メッセージを参照
1: ファイルが見つかりません
2: アクセス違反
3: ディスクがいっぱいまたは割り当てを超えています
4: 不正な TFTP 操作
5: 不明な転送 ID
6: ファイルはすでに
存在します7:そのようなユーザーはいません
8: サポートされていないオプションが要求されました
1.1.4 演習 - TFTP クライアント
演習要件:
TFTP プロトコルを使用してサーバーからローカルにファイルをダウンロードする
実装のアイデア:
1. 要求メッセージを作成し、サーバー (ポート 69) に送信します;
2. サーバーの応答を待ちます;
3. サーバーの応答を分析します;
4. 受信したデータ パケットが指定されたパケットより小さくなるまでデータを受信しますデータ長
サーバー側はあらかじめ用意されたソフトウェアによって利用されます
サーバーを設定する
クライアントがコードを書く
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <sys/types.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
void do_download(int sockfd, struct sockaddr_in serveraddr){
char filename[128] = "";
printf("请输入要下载的文件名:");
scanf("%s",filename);
//给服务器发送信息,告知服务器执行下载操作
unsigned char text[1024] = "";
int text_len;
socklen_t addrlen = sizeof(struct sockaddr_in);
int fd;
int flags = 0;
int num = 0;
ssize_t bytes;
//构建给服务器发送的tftp指令并发送给服务器,例如01test.txtoctet0
text_len = sprintf(text,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
if(sendto(sockfd,text,text_len,0,(struct sockaddr *)&serveraddr,addrlen) < 0){
perror("fail to sendto");
exit(1);
}
while(1){
//接收服务器发送过来的数据并处理
if((bytes = recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&serveraddr,*addrlen)) == -1){
perror("fail to recvfrom");
exit(1);
}
//判断操作码执行相应的处理
if(text[1] == 5){
printf("error: %s\n",text + 4); //打印差错信息
return;
}
else if(text[1] == 3){
if(flags == 0){
//创建文件,只创建一次
if((fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0){
perror("fail to open");
exit(1);
}
flags = 1;
}
//对比编号和接收的数据大小并将文件内容写入文件
if((num + 1 == ntohs(*(unsigned short *)(text + 2))) && (bytes == 516)){
num = ntohs(*(unsigned short *)(text + 2));
if(write(fd,text+4,bytes - 4) < 0){
perror("fail to write");
exit(1);
}
//当文件写入完毕后,给服务器发送ACK
text[1] = 4;
if(sendto(sockfd, text, 4, 0, (struct sockaddr *)&serveraddr,addrlen) < 0 ){
perror("fail to sendto");
exit(1);
}
//当最后一个数据接收完毕后,写入文件后退出函数
else if(num + 1 == ntohs(*(unsigned short *)(text + 2)) && (bytes < 516)){
if(write(fd,text + 4, bytes - 4) < 0){
perror("fail to write");
exit(1);
}
text[1] = 4;
if(sendto(sockfd,text,4,0,(struct sockaddr *)&serveraddr, addrlen)< 0){
perror("fail to sendto");
exit(1);
}
printf("文件下载完毕");
}
}
}
}
int main(int argc, char **argv)
{
if(argc < 2){
fprintf(stderr,"Usage: %s <server_ip>\n",argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
//创建套接字
if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
perror("fail to socket");
exit(1);
}
//填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(agrv[1]); //tftp服务器的ip地址,192.168.3.78
serveraddr.sin_port = htons(69); //tftp服务器的端口默认是69
do_download(sockfd,serveraddr); //下载操作
return 0;
}
1.2 UDP - ブロードキャスト
1.2.1 放送の概念
ブロードキャスト: ホストが、そのホストが存在するサブネット内のすべてのホストにデータを送信する方法。たとえば、
192.168.3.103 のホストがブロードキャスト情報を送信すると、192.168.3.1 ~ 192.168.3.254 のすべてのホストがデータを受信できます。
ブロードキャストは、TCP ではなく、UDP または raw IP でのみ実装できます。
ブロードキャストの目的:
単一サーバーが複数のクライアント ホストと通信するときのパケット フローを削減します。
次のプロトコルはすべてブロードキャストを使用します。
1. アドレス解決プロトコル (ARP)
2. 動的ホスト構成プロトコル (DHCP)
3. ネットワーク タイム プロトコル (NTP) )
ブロードキャストの特徴:
1. 同じサブネット内のすべてのホストがデータを処理する必要がある
2. UDP データ パケットはプロトコル スタックを上り、UDP レイヤに到達します。
3. オーディオ、ビデオ、その他の高速アプリケーションを実行すると、大きなマイナスが生じます
4。 LAN内での使用に限定
ブロードキャスト アドレス
{ネットワーク ID、ホスト ID}
ネットワーク ID は、サブネット マスクの 1 でカバーされる連続ビットを表します。
ホスト ID は、サブネット マスクの 0 でカバーされる連続ビットを表します
定向广播地址:主机ID全1
1、例:对192.168.220.0/24,其定向广播地址为192.168.220.255
2、通常路由器不转发该广播
受限广播地址:255.255.255.255
路由器从不转发该广播
1.2.2 广播和单播的对比
单播:
广播:
1.2.3 广播流程
发送者:
第一步:创建套接字socket()
第二步:设置为允许发送广播权限setsocket()
第三步:向广播地址发送数据sendto()
接收者:
第一步:创建套接字socket()
第二步:将套接字与广播的信息结构体绑定bind()
第三步:接收数据recvfrom()
套接字选项
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
参数:
sockfd: 文件描述符
level: 协议层次
SOL_SOCKET 套接字层次
IPPROTO_TCP tcp层次
IPPROTO_IP IP层次
optname: 选项的名称
SO_BROADCAST 允许发送广播数据包
SO_RCVBUF 接收缓冲区大小
SO_SNDBUF 发送缓冲区大小
optval: 设置的选项的值
int类型的值,存储的是bool的数据(1和0)
0 不允许
1 允许
option_len: option_value的长度
返回值
成功执行返回0,否则返回-1
1.2.4 广播示例
发送者
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]){
if(argc < 3){
fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
exit(1)
}
int sockfd;
struct sockaddr_in broadcataddr; //服务器网络信息结构体
socklen_t addrlen = sizeof(broadcataddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET,SOCK_DGRAM,0))< 0){
perror("fail to socket");
exit(1);
}
//第二步:设置为允许发送广播权限
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on)) < 0){
perror("fail to setsockopt");
exit(1);
}
//第三步:填充广播信息结构体
broadcataddr.sin_family = AF_INET;
broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.255 或者255.255.255.255
broadcataddr.sin_port = htons(atoi(argv[2]));
//第四步:进行通信
char buf[128] = "";
while(1){
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
if(sendto(sockfd,buf,0,(struct sockaddr *)&broadcataddr,addrlen)){
perror("fail to sendto");
exit(1);
}
}
return 0;
}
接收
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]){
if(argc < 3){
fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
exit(1)
}
int sockfd;
struct sockaddr_in broadcataddr; //服务器网络信息结构体
socklen_t addrlen = sizeof(broadcataddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET,SOCK_DGRAM,0))< 0){
perror("fail to socket");
exit(1);
}
//第二步:填充广播信息结构体
broadcataddr.sin_family = AF_INET;
broadcataddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.255 或者255.255.255.255
broadcataddr.sin_port = htons(atoi(argv[2]));
第三步:将套接字与广播信息结构体绑定
if(bind(sockfd,(struct sockaddr *)&broadcataddr,addrlen) < 0){
perror("fail to bind");
exit(1);
}
//第四步:进行通信
char text[32] = "";
struct sockaddr_in sendaddr;
while(1){
if(recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&sendaddr,&addrlen)){
perror("fail to recvfrom");
exit(1);
}
printf("[%s - %d]: %s\n",inet_ntoa(sendaddr.sin_addr),ntohs(sendaddr.sin_port),text);
}
return 0;
}
1.3 UDP—多播
1.3.1 多播概述
多播:数据的收发仅仅在同一分组中进行,所以多播又称之为组播。
多播的特点:
1、多播地址标示一组接口
2、多播可以用于广域网使用
3、在IPV4中,多播是可选的
1.3.2 多播地址
IPV4的D类地址是多播地址
十进制:224.0.0.1 ~ 239.255.255.254
十六进制:E0.00.00.01 EF.FF.FF.FE
多播地址向以太网MAC地址的映射
1.3.3 UDP多播工作过程
比起广播,多播具有可控性,只有加入多播组的接收者才可以接收数据,否则接收不到
1.3.4 多播流程
发送者:
第一步:创建套接字 socket()
第二步:向多播地址发送数据 sendto()
接收者:
第一步:创建套接字socket()
第二步:设置加入多播组setsockopt()
第三步:将套接字与多播信息结构体绑定bind()
第四步:接收数据recvfrom()
1.3.5 多播地址结构体
在IPV4因特网络(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示
struct in_addr
{
in_addr_t s_addr;
}
struct ip_mreq
{
struct in_addr imr_multiaddr; //多播组IP
struct in_addr imr_interface; //将要添加到多播组的IP
}
1.3.6 多播套接口选项
int setsockopt(int sockfd, int level, int optname, const void *optval_value, socklen_t optlen);
功能:设置一个套接字的选项(属性)
参数:
socket: 文件描述符
level: 协议层次
IPPROTO_IP IP层次
option_name: 选项名称
IP_ADD_MEMBERSHIP 加入多播组
IP_DROP_MEMBERSHIP 离开多播组
option_value: 设置的选项的值
struct ip_mreq
{
struct in_addr imr_multiaddr; //多播组IP
struct in_addr imr_interface; //将要添加到多播组的IP
//INADDR_ANY 任意主机地址(自动获取你的主机地址)
}
option_len: option_value的长度
返回值:
成功:0
失败:-1
1.3.7 加入多播组示例
发送者:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]){
if(argc < 3){
fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
exit(1)
}
int sockfd;
struct sockaddr_in groupcastaddr; // 服务器网络信息结构体
socklen_t addrlen = sizeof(groupcastaddr);
// 第一步: 创建套接字
if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
perror("fail to socket");
exit(1);
}
// 第二步: 填充组播信息结构体
groupcastaddr.sin_family = AF_INET;
groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //224.x.x.x - 239.255.255.254
groupcastaddr.sin_port = htons(atoi(argv[2]));
// 第三步: 进行通信
char buf[128] = "";
while(1){
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
if(sendto(sockfd,buf,sizeof(buf),(struct sockaddr *)&groupcastaddr,socklen_t) < 0){
perror("fail to sendto");
exit(1);
}
}
return 0;
}
接收者:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]){
if(argc < 3){
fprintf(stderror,"Useage:%s <ip> <port>\n",argv[0]);
exit(1)
}
int sockfd;
struct sockaddr_in groupcastaddr; // 服务器网络信息结构体
socklen_t addrlen = sizeof(groupcastaddr);
// 第一步: 创建套接字
if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
perror("fail to socket");
exit(1);
}
// 第二步: 设置为加入多播组
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
mreq.imr_interface.s_addr = INADDR_ANY;
if(setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0){
perror("fail to setsockopt");
exit(1);
}
// 第三步: 填充组播信息结构体
groupcastaddr.sin_family = AF_INET;
groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //224.x.x.x - 239.255.255.254
groupcastaddr.sin_port = htons(atoi(argv[2]));
//第四步: 将套接字与广播信息结构体绑定
if(bind(sockfd,(struct sockaddr *)&groupcastaddr, addrlen) < 0){
perror("fail to bind");
exit(1);
}
// 第五步: 进行通信
char text[32] = "";
struct sockaddr_in sendaddr
while(1){
if(recvfrom(sockfd,text,sizeof(text),(struct sockaddr *)&sendaddr,&socklen_t) < 0){
perror("fail to recvfrom");
exit(1);
}
printf("[%s - %d]: %s\n",inet_ntoa(sendaddr.sin_addr),ntohs(sendaddr.sin_port),text);
}
return 0;
}