网络编程 总结二

一、TCP

TCP模型

网络编程 总结二_第1张图片

网络编程 总结二_第2张图片

1. TCP搭建相关函数:

 套接字Socket

网络编程 总结二_第3张图片

 1)Socket函数:

网络编程 总结二_第4张图片

2)bind

网络编程 总结二_第5张图片

 3)listen

网络编程 总结二_第6张图片

 4)accept

网络编程 总结二_第7张图片

 5)recv

网络编程 总结二_第8张图片

 注意:

1> TCP中的recv 可以替换成read;         2>TCP中的recv可以替换成recvfrom

6)send

网络编程 总结二_第9张图片

  注意:

1> TCP中的send 可以替换成 write;         2>TCP中的 send 可以替换成 sendto

7)connet

网络编程 总结二_第10张图片

 客户端:

#include
#include
#include
#include
#include
#include
#include
#include

#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
#include
#include
#include
#include
#include
#include
#include

#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模型

网络编程 总结二_第11张图片

 1. UDP相关函数

1)socket

2)bind

3)recvfrom

网络编程 总结二_第12张图片

 4)sendto

网络编程 总结二_第13张图片

 客户端:

#include
#include
#include
#include
#include
#include
#include
#include
#include

#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
#include
#include
#include
#include
#include
#include
#include
#include

#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

网络编程 总结二_第14张图片

 

网络编程 总结二_第15张图片

 【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
#include
#include
#include
#include
#include
#include
#include
#include

#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
#include
#include
#include
#include
#include
#include
#include
#include

#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下载模型

网络编程 总结二_第16张图片

 3)TFTP通信过程总结

1>服务器在69号端口等待客户端的请求

2>服务器若批准请求,则使用临时端口与客户端进行通信

3>每隔数据包的编号都有变化(从1开始)

4>每隔数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包

5>数据长度以512Byte传输,小于512Byte的数据意味着数据传输结束

网络编程 总结二_第17张图片

 4)TFTP协议分析

网络编程 总结二_第18张图片

 代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#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;
}

你可能感兴趣的:(网络,服务器)