Linux下网络编程(2)文件发送
这篇文章完成了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);
}
- 点赞
 - 收藏
 - 关注作者
 
            
           
评论(0)