Linux下网络编程(2)文件发送
【摘要】 这篇文章完成了Linux下文件发送的思路分析、完成文件的接收、发送、讲解了select函数用法。主要是对Linux下socket编程的深入学习理解。
这篇文章完成了Linux下文件发送的思路分析、完成文件的接收、发送、讲解了select函数用法。主要是对Linux下socket编程的深入学习理解。
任务1: 网络文件传输
作业
1. 重新编写代码,实现客户端与服务器之间基本通信。
2. 实现文件传输:
比如: 服务器给客户端发送文件 (下载)
客户端给服务器发送文件 (上传)
(1) 在本地计算机上测试
(2) 与局域网内其他计算机之间进行测试。 考虑网络、丢包问题。
扩展练习:
1. 实现目录传输(只写一级目录)。----封装一个函数(发送一个文件)
2. 实现目录内文件的同步 (公交车站台、地铁里广告机(视频播放机))
思路1: (适合本机测试代码,了解文件发送思路)
(服务器给客户端发送文件)
TCP服务器端:
1. 创建套接字
2. 绑定端口号(创建服务器)
3. 设置监听的数量
4. 等待客户端连接
5. 打开将要发送的文件(open)
6. 读取指定字节的文件数据(1024)
7. 将读取的数据发送给客户端 (循环执行6和7两个步骤,直到文件发送完毕)
8. 关闭套接字
TCP客户端:
1. 创建套接字
2. 连接指定的TCP服务器
3. 创建一个文件
4. 接收TCP服务器发送过来的数据
5. 将数据写入到文件 (循环读取,直到数据读取完毕,读取到一次数据不足1024字节表示对方发送完毕)
6. 关闭文件、关闭套接字
思路2: (考虑数据传输的应答)
TCP服务器端:
1. 创建套接字
2. 绑定端口号(创建服务器)
3. 设置监听的数量
4. 等待客户端连接
5. 打开将要发送的文件(open)
6. 读取指定字节的文件数据(1024)
7. 将读取的数据发送给客户端
8. 等待客户端的应答。比如: 客户端收到数据之后向服务器发送一个”OK”
9. 收到应答就循环执行6、7、8三个步骤,直到文件发送完毕
10. 关闭套接字
TCP客户端:
1. 创建套接字
2. 连接指定的TCP服务器
3. 创建一个文件
4. 接收TCP服务器发送过来的数据
5. 向TCP服务器发送应答。 比如: “OK”
6. 将数据写入到文件 (循环读取服务器发送的数据,直到数据读取完毕,读取到一次数据不足1024字节表示对方发送完毕)
7. 关闭文件、关闭套接字
思路3: (考虑文件发送的完整性)
每次使用TCP协议发送数据时,可以对数据进行封包。
比如: 封包就是定义一个结构体或者一个指定数据序列。
结构体里有以下成员:
1. 帧头(区分一包新的数据) (0xAA,0xAB,0xAC,0xAD) 2. 当前数据包的数据字节大小 3. 整个文件的大小 4. 当前文件的名称 5. 当前数据包的序号。 (1,2,3,4,5,6,7,8,9,10……) 6. 数据包的源数据(数组) 比如: 数组大小1024字节。 (1,2,3,4,5) 7. 数据包源数据的校验和 。 |
Linux下Socket网络编程默认的接收缓冲区大小: 8192个字节。
1,2,3,4,5,6,7,8,9,0
(1)文件发送
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
/*
TCP服务器创建
*/
int main(int argc,char **argv)
{
char file_buff[1024];
int r_cnt; //读取的字节数量
FILE *tx_file=NULL; //文件指针
int tcp_server_fd; //服务器套接字描述符
int tcp_client_fd; //客户端套接字描述符
struct sockaddr_in tcp_server;
struct sockaddr_in tcp_client;
socklen_t tcp_client_addrlen=0;
int tcp_server_port; //服务器的端口号
//判断传入的参数是否合理
if(argc!=3)
{
printf("参数格式:./tcp_server <端口号> <tx_file_name>\n");
return -1;
}
tcp_server_port=atoi(argv[1]); //将字符串转为整数
/*1. 创建网络套接字*/
tcp_server_fd=socket(AF_INET,SOCK_STREAM,0);
if(tcp_server_fd<0)
{
printf("TCP服务器端套接字创建失败!\n");
return -1;
}
/*2. 绑定端口号,创建服务器*/
tcp_server.sin_family=AF_INET; //IPV4协议类型
tcp_server.sin_port=htons(tcp_server_port);//端口号赋值,将本地字节序转为网络字节序
tcp_server.sin_addr.s_addr=INADDR_ANY; //将本地IP地址赋值给结构体成员
if(bind(tcp_server_fd,(const struct sockaddr*)&tcp_server,sizeof(struct sockaddr))<0)
{
printf("TCP服务器端口绑定失败!\n");
return -1;
}
/*3. 设置监听的客户端数量*/
listen(tcp_server_fd,10);
/*4. 等待客户端连接*/
tcp_client_addrlen=sizeof(struct sockaddr);
tcp_client_fd=accept(tcp_server_fd,(struct sockaddr *)&tcp_client,&tcp_client_addrlen);
if(tcp_client_fd<0)
{
printf("TCP服务器:等待客户端连接失败!\n");
return -1;
}
//打印连接的客户端地址信息
printf("已经连接的客户端信息: %s:%d\n",inet_ntoa(tcp_client.sin_addr),ntohs(tcp_client.sin_port));
/*5. 打开文件*/
tx_file=fopen(argv[2],"rb");
if(tx_file==NULL)
{
printf("文件打开失败!\n");
goto file_error;
}
/*6. 文件发送*/
while(1)
{
r_cnt=fread(file_buff,1,1024,tx_file); //从文件里读取指定长度的字节数量
write(tcp_client_fd,file_buff,r_cnt); //将数据发送给客户端
if(r_cnt!=1024)
{
fclose(tx_file);
break;
}
}
file_error:
/*6. 关闭连接*/
close(tcp_client_fd);
}
(2)文件接收
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
/*
TCP客户端创建
*/
int main(int argc,char **argv)
{
FILE *rx_file=NULL;
int tcp_client_fd; //客户端套接字描述符
int Server_Port; //服务器端口号
struct sockaddr_in tcp_server; //存放服务器的IP地址信息
char rx_buff[1024];
int rx_len;
if(argc!=4)
{
printf("TCP客户端形参格式:./tcp_client <服务器IP地址> <服务器端口号> <rx_file_name>\n");
return -1;
}
Server_Port=atoi(argv[2]); //将字符串的端口号转为整型
/*1. 创建网络套接字*/
tcp_client_fd=socket(AF_INET,SOCK_STREAM,0);
if(tcp_client_fd<0)
{
printf("TCP服务器端套接字创建失败!\n");
return -1;
}
/*2. 连接到指定的服务器*/
tcp_server.sin_family=AF_INET; //IPV4协议类型
tcp_server.sin_port=htons(Server_Port);//端口号赋值,将本地字节序转为网络字节序
tcp_server.sin_addr.s_addr=inet_addr(argv[1]); //IP地址赋值
if(connect(tcp_client_fd,(const struct sockaddr*)&tcp_server,sizeof(const struct sockaddr))<0)
{
printf("TCP客户端: 连接服务器失败!\n");
return -1;
}
/*3. 创建文件*/
rx_file=fopen(argv[3],"wb");
if(rx_file==NULL)
{
printf("%s文件创建失败!\n",argv[3]);
return -1;
}
/*4. 连续接收数据*/
while(1)
{
rx_len=read(tcp_client_fd,rx_buff,1024);
if(rx_len>0)
{
fwrite(rx_buff,1,rx_len,rx_file); //写数据到文件
printf("rx=%d\n",rx_len);
if(rx_len!=1024)
{
fclose(rx_file); //文件关闭文件
break;
}
}
}
/*4. 关闭连接*/
close(tcp_client_fd);
}
任务2: select 多路复用IO
Select支持多个文件描述符的事件轮询。 (1024个) poll、epoll 轮询机制。
每个文件描述符一般有3个事件: 读事件、写事件、错误(异常)事件
应用场景 比如: TCP服务器需要同时处理多个客户端的连接。
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//监控文件描述符
int select
(
int nfds, //填所有文件描述符最大值+1
fd_set *readfds, //读事件
fd_set *writefds, //写事件
fd_set *exceptfds, //其他事件
struct timeval *timeout //阻塞的时间。正常要达到的效果都是无限阻塞。
);
//返回值: 0表示没有任何事件发生,>0表示发生了事件(文件名描述符的数量)。
<0表示出现错误
//文件描述符操作函数
void FD_CLR(int fd, fd_set *set); //从文件操作集合里清除指定文件描述符
int FD_ISSET(int fd, fd_set *set); //判断指定文件描述符是否产生了指定的事件
void FD_SET(int fd, fd_set *set); //添加指定的文件描述符到文件操作集合
void FD_ZERO(fd_set *set); //清空整个文件操作集合
//时间结构
struct timeval
{
long tv_sec; /*10*/
long tv_usec; /*0*/
};
如何判断对方已经断开连接?
当select函数返回值为1,并且read函数的返回值为0 就表示对方已经断开连接。
slect用法示例。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
unsigned char rx_buff[1024];
unsigned int rx_cnt;
unsigned char log_info[1024];
unsigned int log_cnt=0;
/*
TCP服务器创建
*/
int main(int argc,char **argv)
{
int tcp_server_fd; //服务器套接字描述符
int tcp_client_fd; //客户端套接字描述符
struct sockaddr_in tcp_server;
struct sockaddr_in tcp_client;
socklen_t tcp_client_addrlen=0;
int tcp_server_port; //服务器的端口号
//判断传入的参数是否合理
if(argc!=2)
{
printf("参数格式:./tcp_server <端口号>\n");
return -1;
}
tcp_server_port=atoi(argv[1]); //将字符串转为整数
/*1. 创建网络套接字*/
tcp_server_fd=socket(AF_INET,SOCK_STREAM,0);
if(tcp_server_fd<0)
{
printf("TCP服务器端套接字创建失败!\n");
return -1;
}
/*2. 绑定端口号,创建服务器*/
tcp_server.sin_family=AF_INET; //IPV4协议类型
tcp_server.sin_port=htons(tcp_server_port);//端口号赋值,将本地字节序转为网络字节序
tcp_server.sin_addr.s_addr=INADDR_ANY; //将本地IP地址赋值给结构体成员
if(bind(tcp_server_fd,(const struct sockaddr*)&tcp_server,sizeof(struct sockaddr))<0)
{
printf("TCP服务器端口绑定失败!\n");
return -1;
}
/*3. 设置监听的客户端数量*/
listen(tcp_server_fd,10);
/*4. 等待客户端连接*/
tcp_client_addrlen=sizeof(struct sockaddr);
tcp_client_fd=accept(tcp_server_fd,(struct sockaddr *)&tcp_client,&tcp_client_addrlen);
if(tcp_client_fd<0)
{
printf("TCP服务器:等待客户端连接失败!\n");
return -1;
}
//打印连接的客户端地址信息
printf("已经连接的客户端信息: %s:%d\n",inet_ntoa(tcp_client.sin_addr),ntohs(tcp_client.sin_port));
/*5. 数据通信*/
fd_set readfds; //读事件的文件操作集合
int select_state; //接收返回值
while(1)
{
/*5.1 清空文件操作集合*/
FD_ZERO(&readfds);
/*5.2 添加要监控的文件描述符*/
FD_SET(tcp_client_fd,&readfds);
/*5.3 监控文件描述符*/
select_state=select(tcp_client_fd+1,&readfds,NULL,NULL,NULL);
if(select_state>0)//表示有事件产生
{
/*5.4 测试指定的文件描述符是否产生了读事件*/
if(FD_ISSET(tcp_client_fd,&readfds))
{
/*5.5 读取数据*/
rx_cnt=read(tcp_client_fd,rx_buff,1024);
if(rx_cnt==0)
{
printf("对方已经断开连接!\n");
break;
}
sprintf(log_info,"server rx data[%d]",log_cnt++);
write(tcp_client_fd,log_info,strlen(log_info)); //回发数据
write(tcp_client_fd,rx_buff,rx_cnt); //将收到的数据返回
}
}
else if(select_state<0) //表示产生了错误
{
printf("select函数产生异常!\n");
break;
}
}
/*6. 关闭连接*/
close(tcp_client_fd);
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)