一、TCP
TCP模型
1. TCP搭建相关函数:
套接字Socket
1)Socket函数:
2)bind
3)listen
4)accept
5)recv
注意:
1> TCP中的recv 可以替换成read; 2>TCP中的recv可以替换成recvfrom
6)send
注意:
1> TCP中的send 可以替换成 write; 2>TCP中的 send 可以替换成 sendto
7)connet
客户端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.8.77" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("cfd=%d\n",cfd);
//允许端口快速重用
int reuse=1;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体
//真是的地址信息结构体根据地址族制定 AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;//必须填 AF_INET
sin.sin_port = htons(PORT);//端口号, 1024-49151
sin.sin_addr.s_addr = inet_addr(IP);//本机IP,ifconfig
//连接服务器
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("connet");
return -1;
}
printf("connect sucess __%d__\n",__LINE__);
char buf[128]="";
ssize_t res=0;
while(1)
{
bzero(buf,sizeof(buf));
printf("请输入数据>>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//发送
if(send(cfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
//接收
bzero(buf,sizeof(buf));
res=recv(cfd,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("cfd=%d 服务器下线 __%d__\n", cfd,__LINE__);
break;
}
printf("cfd=%d %s __%d__\n",cfd,buf,__LINE__);
}
close(cfd);
return 0;
}
服务器:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.8.77" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("sfd=%d\n",sfd);
//允许端口快速重用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体
//真是的地址信息结构体根据地址族制定 AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;//必须填 AF_INET
sin.sin_port = htons(PORT);//端口号, 1024-49151
sin.sin_addr.s_addr = inet_addr(IP);//本机IP,ifconfig
//将IP和端口号绑定到套接字上
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
//阻塞函数,从已完成连接的队列头中获取一个客户端信息
//该文件描述符才是与客户端通信的文件描述符
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
char buf[128]="";
ssize_t res=0;
while(1)
{
//接收
res=recv(newfd,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
break;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//发送
strcat(buf,"*_*");
if(send(newfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
}
close(newfd);
close(sfd);
return 0;
}
二、UDP
UDP模型
1. UDP相关函数
1)socket
2)bind
3)recvfrom
4)sendto
客户端:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d",__LINE__);\
perror(msg);\
}while(0)
#define SER_IP "192.168.8.77" //本机IP
#define SER_PORT 6666
#define CLI_IP "192.168.8.77" //本机IP
#define CLI_PORT 8888
int main(int argc, const char *argv[])
{
//创建报式套接字
int cfd = socket(AF_INET, SOCK_DGRAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
//*****************************************************
struct sockaddr_in cin;
cin.sin_family = AF_INET; //必须填AF_INET
cin.sin_port = htons(CLI_PORT); //端口号的网络字节符
cin.sin_addr.s_addr = inet_addr(CLI_IP);
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("client bind success\n");
//********************************************************
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(SER_PORT); //端口号的网络字节符
sin.sin_addr.s_addr = inet_addr(SER_IP);
struct sockaddr_in rcvaddr;//存储数据包是从哪里来的
socklen_t addrlen = sizeof(rcvaddr);
char buf[128]="";
ssize_t res =0;
while(1)
{
bzero(buf,sizeof(buf));
printf("请输入数据>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//发送数据,发送给服务器
if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
printf("发送数据\n");
//接收数据,必须接收数据包的发送方地址信息
res=recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&rcvaddr,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s : %d]: %s\n",inet_ntoa(rcvaddr.sin_addr), ntohs(rcvaddr.sin_port),buf);
}
//关闭文件描述符
close(cfd);
return 0;
}
服务器:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line%d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.8.77" //本机IP
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //端口号的网络字节符
sin.sin_addr.s_addr = inet_addr(IP);
//绑定 必须绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
struct sockaddr_in cin;//存储数据包是从哪里来的
socklen_t addrlen = sizeof(cin);
char buf[128]="";
ssize_t res =0;
while(1)
{
bzero(buf,sizeof(buf));
//接收数据,必须接收数据包的发送方地址信息
res=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s : %d]: %s\n",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),buf);
strcat(buf,"*_*");
if(sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,sizeof(cin))<0)
{
ERR_MSG("sendto");
return -1;
}
printf("发送成功\n");
}
//关闭文件描述符
close(sfd);
return 0;
}
2.UDP中的connect 函数(重点)
1>TCP中的connect函数会造成三次握手,将client 与 server 连接;UDP中的connect 仅仅是将对端的 IP 和端口号记录到内核中。此时UDP只能与记录的对端进行通信。
2>TCP中的connect 函数只能调用一次;UDP中的可以调用多次 connect 函数,刷新内核中对端的IP地址和端口,如果想要清空内核中对端的地址信息,则可以将sin_family 成员修改成AF_UNSPEC
3>当UDP采用connect 方式收发报文后,可以调用 send write 函数 也可以调用sendto函数。
1> sendto ( sd,buf,sizeof ( buf ) , NULL , 0 );
recvfrom 在后面的地址信息结构体填NULL 的时候,可以替换成 recv read
2> recvfrom ( sockfd , buf, len , flags , NULL ,NULL );
UDP调用connect 函数的优点:
1>提升传输效率:
a. 不调用connect :将对端的地址信息填充到内核--> 发送报文--->清空内核信息--->将对端的地址信息填充到内核-->发送报文--->清空内核信息
b. 调用了connect :将对端的地址信息填充到内核--> 发送报文--> 发送报文--> 发送报文--->清空内核信息
2>增加传输的稳定性:
a. 调用connect 函数的UDP通信,可以防止AB进程在数据传输过程中收到C进程消息,造成传输错误
3. UDP多点通信
【1】网络属性
setsockopt / getsockopt
【2】多点通信
1. 单播
1>主机之间一对一的通信模式,交换机以及路由器对数据只进行转发,不复制
2>每次只有两个实体相互通信,发送端和接收端都是唯一确定的
2. 广播
1>主机之间一对多的通信模式,网络对其中的每一台主机发出的信号都进行无条件复制并转发
2>在同一个局域网下的所有主机都可以接收到广播信息
3>禁止广播数据穿过路由器,防止广播数据影响大面积主机
4>广播数据不需要应答,只有UDP才能做广播
5>广播地址:有效网络号+ 全是1 的主机号
1)广播的发送端(类似客户端)
1> socket 创建报式套接字 ; bind 非必须绑定
2>setsockopt 设置允许广播:level: SOL_SOCKET optname: SO_BROADCAST
3> 指定接收端的地址信息结构体
a. IP : 填写广播IP
b. PORT : 与接收端填充的一致即可
4> sendto 发送广播数据
2)广播的接收端(类似服务器)
1> socket 创建报式套接字 ; bind 必须绑定
2>填充接收端自身的地址信息结构体
a. IP : 填写广播IP :有效网络号 + 全是 1 的主机号
调用bind 函数后,会将本机所有可用IP 地址都绑定到套接字上
b. PORT : 与发送端填充的一致即可
3> recvfrom 接收广播数据
3. 组播(多播组)
1)组播的发送端(类似客户端)
1>socket 创建报式套接字 ; bind 非必须绑定
2> 指定接收端的地址信息结构体
a. IP : 填写组播IP (224.0.0.0~239.255.255.255,与接收方填充一致)
b. PORT : 与接收端填充的一致即可
3> sendto 发送组播数据
2)组播的接收端(类似服务器)
1> socket 创建报式套接字 ; bind 必须绑定
2>setsockopt 加入多播组:level : IPPROTO_IP OPTNAME : IP_ADD_MEMBERSHIP
3>填充接收端自身的地址信息结构体
a. IP : 填写组播IP
b. PORT : 与发送端填充的一致即可
4> recvfrom 接收组播数据
多播snd:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d",__LINE__);\
perror(msg);\
}while(0)
#define GRP_IP "243.1.2.3" //本机IP
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建报式套接字
int cfd = socket(AF_INET, SOCK_DGRAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //端口号的网络字节符
sin.sin_addr.s_addr = inet_addr(GRP_IP);
char buf[128]="";
ssize_t res =0;
while(1)
{
bzero(buf,sizeof(buf));
printf("请输入数据>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//发送数据,发送给服务器
if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
printf("发送数据\n");
}
//关闭文件描述符
close(cfd);
return 0;
}
多播rcv:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line%d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.79" //本机IP
#define GRP_IP "224.1.2.3" //组播IP
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
//加入多播组
struct ip_mreqn mq;
mq.imr_multiaddr.s_addr = inet_addr(GRP_IP);//组播IP
mq.imr_address.s_addr = inet_addr(IP);
mq.imr_ifindex=2;
if(setsockopt(sfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mq,sizeof(mq))<0)
{
perror("setsockopt");
return -1;
}
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //端口号的网络字节符
sin.sin_addr.s_addr = inet_addr(GRP_IP);
//绑定 必须绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
struct sockaddr_in cin;//存储数据包是从哪里来的
socklen_t addrlen = sizeof(cin);
char buf[128]="";
ssize_t res =0;
while(1)
{
bzero(buf,sizeof(buf));
//接收数据,必须接收数据包的发送方地址信息
res=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s : %d]: %s\n",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),buf);
}
//关闭文件描述符
close(sfd);
return 0;
}
三、TFTP协议
1)TFTP协议概述:简单文件传输协议适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:应用层协议;基于UDP协议实现
数据传输模式:
octet: 二进制模式(常用)
mail : 已经不在支持
2)TFTP下载模型
3)TFTP通信过程总结
1>服务器在69号端口等待客户端的请求
2>服务器若批准请求,则使用临时端口与客户端进行通信
3>每隔数据包的编号都有变化(从1开始)
4>每隔数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
5>数据长度以512Byte传输,小于512Byte的数据意味着数据传输结束
4)TFTP协议分析
代码:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d",__LINE__);\
perror(msg);\
}while(0)
#define SER_IP "192.168.171.1" //本机IP
#define SER_PORT 69
int do_download(int cfd, struct sockaddr_in sin)
{
//下载请求
char filename[20]="";
char buf[516]={0};
printf("请输入要下载的文件名>>");
scanf("%s",filename);
while(getchar()!=10);
short*p1 = (short*)buf;
*p1 = htons(1);
char*p2 = buf+2;
strcpy(p2,filename);
char*p3 = p2+strlen(p2);
char*p4 = p3+1;
strcpy(p4,"octet");
int size=strlen(p2)+strlen(p4)+4;
//发送下载请求协议
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
printf("发送成功\n");
int fd=-1;
socklen_t addrlen=sizeof(sin);
ssize_t res =0;
unsigned short num=0;
int ret=0;
while(1)
{
bzero(buf,sizeof(buf));
//接收数据,必须接收数据包的发送方地址信息
res=recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
ret=-1;
break;
}
if(buf[1]==3)
{
if(htons(num+1)==*(unsigned short*)(buf+2))
{
//组数据包给服务器
num++;
if(1==ntohs(*(unsigned short*)(buf+2)))
{
fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd<0)
{
ERR_MSG("open");
return -1;
}
}
if(write(fd,buf+4,res-4)<0)
{
ERR_MSG("write");
ret=-1;
break;
}
//回复ACK,由于ACK包和数据包前四个字节只有操作码不一致
//直接修改数据包的操作码
buf[1]=4;
if(sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
ret =-1;
break;
}
if(res-4<512)
{
printf("文件:%s 上传完毕\n",filename);
break;
}
}
}
else if(buf[1]==5)//错误包
{
fprintf(stderr,"DOWNLOAD_EROR: %d: %s\n",
ntohs(*(unsigned short*)(buf+2)),buf+4);
break;
}
}
close(fd);
return ret;
}
int main(int argc, const char *argv[])
{
//创建报式套接字
int cfd = socket(AF_INET, SOCK_DGRAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(SER_PORT); //端口号的网络字节符
sin.sin_addr.s_addr = inet_addr(SER_IP);
char c=0;
while(1)
{
system("clear");
printf("*****************\n");
printf("******1.下载*****\n");
printf("******2.上传*****\n");
printf("******3.退出*****\n");
printf("*****************\n");
c=getchar();
while(getchar()!=10);
switch(c)
{
case'1':
do_download(cfd,sin);
break;
case'2':
break;
case'3':
goto END;
break;
default:
printf("输入错误,请重新输入\n");
}
printf("请输入任意字符清屏>>>");
while(getchar()!=10);
}
END:
close(cfd);
return 0;
}