tcp发送文件、多路复用

文章详细介绍了如何使用TCP协议正确地发送大文件,强调了分块读取和发送的重要性,并提供了示例代码。此外,还阐述了多路复用的概念,特别是select函数的使用,包括其在监测文件描述符状态变化和设置超时时间的应用。
摘要由CSDN通过智能技术生成

tcp发送文件

 1.如果文件比较大   几十M  甚至几百M
              错误思路:  read(整个文件) --》没有那么的buf
                                 send() --》无法一口气发送那么多的数据
              正确思路:计算文件大小,分段读,分段发
    2.recv的第四个参数
              recv(newsock,buf,10000,MSG_WAITALL);
                           MSG_WAITALL --》确保recv函数按照10000字节完整接收数据,如果网络中数据不够10000字节,recv会一直阻塞,直到网络中凑够10000字节

#include "myhead.h"
/*
	tcp发送大文件
	
*/


int main()
{
	char sbuf[100];
	int tcpsock;
	int ret;
	int fd;
	int i;
	pthread_t id;
	//定义ipv4地址结构体变量
	struct sockaddr_in bindaddr;
	bzero(&bindaddr,sizeof(bindaddr));
	bindaddr.sin_family=AF_INET;
	bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50");  //男朋友的ip地址
	bindaddr.sin_port=htons(10000);  //男朋友的端口号
	
	//定义ipv4地址结构体变量存放女朋友(服务器)的ip和端口号
	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_addr.s_addr=inet_addr("192.168.1.50");  //女朋友的ip地址
	serveraddr.sin_port=htons(20000);  //女朋友的端口号
	
	//创建套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建套接字失败!\n");
		return -1;
	}
	
	//取消端口号绑定限制
	int on=1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	
	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定ip和端口号失败!\n");
		return -1;
	}
	
	//连接服务器
	ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	if(ret==-1)
	{
		perror("连接服务器失败!\n");
		return -1;
	}
	
	
	//发送大文件给服务器
	fd=open("/mnt/hgfs/code/2021-10-08/基于stm32的智能门锁项目演示.mp4",O_RDWR);
	if(fd==-1)
	{
		perror("打开文件失败!\n");
		return -1;
	}
	
	//求出文件大小
	struct stat mystat;
	stat("/mnt/hgfs/code/2021-10-08/基于stm32的智能门锁项目演示.mp4",&mystat);
	size_t filesize=mystat.st_size;
	
	printf("文件大小:%lu\n",filesize);
	
	//先把文件大小发送给对方
	send(tcpsock,&filesize,sizeof(filesize),0);
	
	//循环读取,循环发送
	char buf[10000]; 
	//计算循环次数以及发送字节数
	int m=filesize/10000;  //循环次数
	int n=filesize%10000;  //余数
	
	for(i=0; i<m; i++)
	{
		//读取文件内容
		ret=read(fd,buf,10000);
		//把读取的内容发送给对方
		send(tcpsock,buf,ret,0); 
	}
	
	//读取余数,发送剩余的字节数
	ret=read(fd,buf,n);
	send(tcpsock,buf,ret,0);
		
	
	close(fd);
	close(tcpsock);
	return 0;
}

#include "myhead.h"
/*
	服务器的代码
	
*/


int main()
{
	char buf[10000];
	int tcpsock;
	int newsock;
	int fd;
	int i;
	pthread_t id;
	int ret;
	//定义ipv4地址结构体变量
	struct sockaddr_in bindaddr;
	bzero(&bindaddr,sizeof(bindaddr));
	bindaddr.sin_family=AF_INET;
	bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50");  //女朋友的ip地址
	bindaddr.sin_port=htons(20000);  //女朋友的端口号
	
	//定义ipv4地址结构体变量存放连接成功的那个客户端ip和端口号
	struct sockaddr_in clientaddr;
	int addrsize=sizeof(clientaddr);
	
	//创建套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建套接字失败!\n");
		return -1;
	}
	
	//取消端口号绑定限制
	int on=1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	
	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定ip和端口号失败!\n");
		return -1;
	}
	
	//监听
	ret=listen(tcpsock,6);
	if(ret==-1)
	{
		perror("监听失败!\n");
		return -1;
	}
	
	//接收客户端的连接请求     
	newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
	if(newsock==-1)
	{
		perror("接收客户端的连接请求失败!\n");
		return -1;
	}
	
	//新建一个空白的mp4文件
	fd=open("/mnt/hgfs/code/2021-10-09/new.mp4",O_CREAT|O_TRUNC|O_RDWR);
	if(fd==-1)
	{
		perror("新建文件失败!\n");
		return -1;
	}
	
	//接收文件大小
	size_t filesize;
	recv(newsock,&filesize,sizeof(filesize),MSG_WAITALL);
	printf("文件大小:%lu\n",filesize);
	//计算循环次数以及接收字节数
	int m=filesize/10000;  //循环次数
	int n=filesize%10000;  //余数
	//用来接收文件
	for(i=0; i<m; i++)
	{
		ret=recv(newsock,buf,10000,MSG_WAITALL);
		write(fd,buf,ret);
	}
	
	//接收尾数
	ret=recv(newsock,buf,n,MSG_WAITALL);
	write(fd,buf,ret);
	
	close(fd);
	close(tcpsock);
	close(newsock);
	return 0;
}

 多路复用

 1.作用
              多路复用是用来检测IO口文件描述符的状态变化(文件描述符的状态变化本质上反应了数据的流入和流出)
              大白话: 多路复用是个电子警察,生活中的电子警察用来监测违章,多路复用用来监测数据的流入流出

 2.相关的接口函数
         (1) 多路复用的代码思路
                   第一步:定义fd_set类型的变量用来存储你需要监测的文件描述符
                                         fd_set myset;
                                  void FD_CLR(int fd, fd_set *set);     //把文件描述符fd从set集合中删除
                                  int  FD_ISSET(int fd, fd_set *set);    //判断文件描述符fd在不在set集合中,在集合中返回非零   不在集合中返回0
                                  void FD_SET(int fd, fd_set *set);    //把文件描述符fd添加到set集合中
                                  void FD_ZERO(fd_set *set);           //把set集合中所有的文件描述符清空
                   第二步:调用select函数去实现多路复用监测集合中所有的文件描述符状态变化
                   第三步:关键步骤,我如何判断(知道)要监测的文件是否真的发生了状态变化
                                原理(很重要):如果select同时监测了A B C三个文件描述符,某个时刻文件描述符A发生了状态变化,select函数会自动把没有发生变化的文件描述符(B C)从集合中删除,只保留状态发生变化的文件描述符
         (2) 多路复用
             fd_set类型: 叫做文件描述符集合,用来存放所有你想要监测的文件描述符
             #include <sys/select.h>
             int select(int nfds, fd_set *readfds, fd_set * writefds, fd_set *errorfds,struct timeval *timeout);
                         返回值:成功  >0
                                      超时   ==0
                                      失败  -1          
                            参数:nfds --》你要监测的所有的文件描述符中最大的文件描述+1
                                      readfds --》我想监测集合中的文件描述符是否发生了读就绪(是否有数据可读)
                                      writefds --》我想监测集合中的文件描述符是否发生了写就绪(是否有数据可写)
                                      errorfds --》我想监测集合中的文件描述符是否发生了异常错误
                                      timeout --》超时时间
                                                 struct timeval 
                                                 {
                                                         tv_sec;   //秒
                                                         tv_usec;  //微秒
                                                }
          
   3.举例演示select的使用
              例子一:用多路复用监测键盘是否有数据输入,有的话就调用scanf读取键盘的输入
              例子二:演示多路复用超时时间的设置
              例子三:用多路复用实现tcp单向通信  客户端--》服务器
                                     客户端:监测键盘是否有数据可读
                                     服务器:监测新的套接字是否有数据可读
                           用多路复用实现tcp双向通信
  
   4.多路复用的特点
             特点一:select是阻塞,会一直监测文件描述符的状态变化,如果某个文件描述符发生了状态变化,select就解除阻塞
             特点二:当select监测到某个文件描述符发生状态变化以后,会自动剔除没有发生状态变化的文件描述符

 多路复用检测键盘输入

#include "myhead.h"

int main()
{
	char buf[50]={0};
	//定义文件描述符集合
	fd_set myset;
	FD_ZERO(&myset);
	//添加你想要监测的文件描述符--》每个文件描述符代表一个IO口
	FD_SET(0,&myset);  //我要监测键盘
	
	//调用select去监测
	printf("电子警察启动了,监测键盘是否有风吹草动!\n");
	select(0+1,&myset,NULL,NULL,NULL); //我只监测myset中的文件描述符是否有数据可读
	//select(0+1,NULL,&myset,NULL,NULL); //我只监测myset中的文件描述符是否有数据可写
	//select(0+1,NULL,NULL,&myset,NULL); //我只监测myset中的文件描述符是否发生异常
	
	//判断究竟是哪个文件描述符发生了状态改变
	if(FD_ISSET(0,&myset))  //键盘在集合中--》键盘发生了读就绪
	{
		scanf("%s",buf);
		printf("你刚才输入的内容是:%s\n",buf);
	}
	
	
	
	
}

多路复用检测超时

#include "myhead.h"

int main()
{
	char buf[50]={0};
	int ret;
	//定义超时时间
	struct timeval mytime;
	bzero(&mytime,sizeof(mytime));
	mytime.tv_sec=7;  //7秒钟
	
	//定义文件描述符集合
	fd_set myset;
	FD_ZERO(&myset);
	//添加你想要监测的文件描述符--》每个文件描述符代表一个IO口
	FD_SET(0,&myset);  //我要监测键盘
	
	//调用select去监测
	printf("电子警察启动了,监测键盘是否有风吹草动!\n");
	//select(0+1,&myset,NULL,NULL,NULL);   //最后一个参数设置为NULL表示永久等待
	ret=select(0+1,&myset,NULL,NULL,&mytime);  //最后一个参数使用mytime表示我想依照mytime中设定的时间去监测
	if(ret>0)  //成功
	{
		//判断究竟是哪个文件描述符发生了状态改变
		if(FD_ISSET(0,&myset))  //键盘在集合中--》键盘发生了读就绪
		{
			scanf("%s",buf);
			printf("你刚才输入的内容是:%s\n",buf);
		}
	}
	else if(ret==0)  //超时
	{
		printf("兄弟啊,半天都不输入,睡着了,搞什么飞机!\n");
		return -1;
	}
	else  //失败
	{
		perror("select调用失败!\n");
		return -1;
	}		
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqb_newfarmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值