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