Linux下网络编程(3)文件发送

举报
DS小龙哥 发表于 2022/04/28 00:51:38 2022/04/28
【摘要】 这篇文章介绍Linux下线程的创建与使用,完成线程的练习,通过线程完成文件发送代码优化,完成目录下所有文件自动发送,利用文件发送理解socket编程。

这篇文章介绍Linux下线程的创建与使用,完成线程的练习,通过线程完成文件发送代码优化,完成目录下所有文件自动发送,利用文件发送理解socket编程。

扩展练习:

1.​ 实现目录传输(只写一级目录)。 (opendir)

(1)​ 封装一个函数(发送一个文件)

(2)​ 发送数据需要封装结构体(分包发送)

2.​ 实现目录内文件的同步 (公交车站台、地铁里广告机(视频播放机))

(1)​ 使用一个链表存放目录下所有文件信息的状态

(2)​ 每隔5秒钟,就遍历当前目录每个文件的信息,与链表里做比较。


任务1:线程的创建示例

(1)创建线程运行并等待退出

#include <stdio.h>
#include <pthread.h>

//线程函数1
void *pthread_func1(void *arg)
{
	while(1)
	{
		printf("线程函数1正在运行.....\n");
		sleep(2);
	}
}

//线程函数2
void *pthread_func2(void *arg)
{
	while(1)
	{
		printf("线程函数2正在运行.....\n");
		sleep(2);
	}
}

int main(int argc,char **argv)
{
	
	pthread_t thread_id1;
	pthread_t thread_id2;
   /*1. 创建线程1*/
    if(pthread_create(&thread_id1,NULL,pthread_func1,NULL))
	{
		printf("线程1创建失败!\n");
		return -1;
	}
	/*2. 创建线程2*/
    if(pthread_create(&thread_id2,NULL,pthread_func2,NULL))
	{
		printf("线程2创建失败!\n");
		return -1;
	}
	
	/*3. 等待线程结束,释放线程的资源*/
	pthread_join(thread_id1,NULL);
	pthread_join(thread_id2,NULL);
	return 0;
}

//gcc pthread_demo_code.c -lpthread




(2)创建线程并设置分离属性

#include <stdio.h>
#include <pthread.h>

//线程函数1
void *pthread_func1(void *arg)
{
	while(1)
	{
		printf("线程函数1正在运行.....\n");
		sleep(2);
	}
}

//线程函数2
void *pthread_func2(void *arg)
{
	while(1)
	{
		printf("线程函数2正在运行.....\n");
		sleep(2);
	}
}

int main(int argc,char **argv)
{
	pthread_t thread_id1;
	pthread_t thread_id2;
   /*1. 创建线程1*/
    if(pthread_create(&thread_id1,NULL,pthread_func1,NULL))
	{
		printf("线程1创建失败!\n");
		return -1;
	}
	/*2. 创建线程2*/
    if(pthread_create(&thread_id2,NULL,pthread_func2,NULL))
	{
		printf("线程2创建失败!\n");
		return -1;
	}
	
	/*3. 设置分离属性,让线程结束之后自己释放资源*/
	pthread_detach(thread_id1);
	pthread_detach(thread_id2);
	while(1)
	{
		printf("主函数正在运行.....\n");
		sleep(2);
	}
	return 0;
}

(3)创建多个线程同时运行

#include <stdio.h>
#include <pthread.h>

//线程函数
void *pthread_func(void *arg)
{
	int id=*(int*)arg;
	int cnt=0;
	while(1)
	{
		printf("线程函数%d运行次数:%d\n",id,cnt++);
		sleep(2);
	}
}

int main(int argc,char **argv)
{
	pthread_t thread_id;
	int i;
	for(i=0;i<5;i++)
	{
		/*1. 创建线程*/
		if(pthread_create(&thread_id,NULL,pthread_func,(void*)&i))
		{
			printf("线程%d创建失败!\n",i);
			return -1;
		}
		/*2. 设置分离属性,让线程结束之后自己释放资源*/
		pthread_detach(thread_id);
	}
	while(1)
	{
		
	}
	return 0;
}

任务2: TCP服务器多线程方式处理客户端数据

可重入函数? 都是私有局部数据。

不可重入函数? 全局变量 静态变量……


基础练习:

1.​ TCP服务器多线程方式实现数据转发。 类似与QQ群的效果。

2.​ 多线程方式实现文件发送。类似于飞秋。群发文件。

(1)文件发送服务器端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>

/***************************结构体定义区域*****************************/
#define PROT 8888 
#pragma pack(1)  //以下结构体以一个字节对齐
struct filedirinfo
{
	 char name[20];
	 int file_size;
	 int count;
};

/**********************************************************************/


/***************************函数声明区域*******************************/
int wait_client_ack(int client_fd);
int send_file(int client_fd,char *absfilename,char *filename);
/**********************************************************************/


/*服务器端创建步骤:
1. 创建网络套接字
2. 绑定端口
3. 设置监听数量
4. 阻塞等待客户端连接
*/
int main(int argc,char **argv)
{
		if(argc!=2)
		{
			 printf("./app dirname\n");	
			 exit(-1);
		}
	
	 struct sockaddr_in server_addr; //存放服务器的IP地址信息
	 struct sockaddr_in client_addr; //存放客户端的IP地址信息
   int socket_fd;     //存放服务器网络套接字
	 int client_fd;     //存放客户端网络套接字
	 socklen_t addrlen; //存放接收的客户端地址长度
	 
	 /*1. 创建网络套接字  PF_INET:ipv4   、SOCK_STREAM:TCP*/
	 socket_fd=socket(PF_INET,SOCK_STREAM,0);
	 if(socket_fd<0)
	 	{
	 		 printf("服务器套接字创建失败!\n");
	 		 exit(-1);	
	 	}
	 /*2. 绑定端口*/
	 memset(&server_addr,0,sizeof(struct sockaddr_in)); //初始化结构体内存空间
	 server_addr.sin_family=PF_INET;   //ipv4
	 server_addr.sin_port=htons(PROT); //大端转小端
	 server_addr.sin_addr.s_addr=INADDR_ANY;  //IP地址赋值
	 if(bind(socket_fd,(const struct sockaddr *)&server_addr,sizeof(struct sockaddr)))
	 {
	 	  printf("服务器端口绑定失败!\n");
	 	  exit(-1);	
	 }
	 
	 /*3. 设置监听客户端的数量*/
	 if(listen(socket_fd,10))
	 	{
	 	  printf("监听客户端失败!\n");
	 	  exit(-1);		
	 	}
	 addrlen=sizeof(struct sockaddr);
	 /*4. 阻塞等待客户端连接,accept返回客户端的网络套接字*/
	  client_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&addrlen);
	  if(client_fd<0)
	  {
	  	 printf("服务器阻塞出现错误!\n");
	 	   exit(-1);		 
	  }
	 
	  /*1.打开目录 */
	 struct dirent *dir_file; 
	 char *abs_addr,*p; //存放绝对路径
	 int str_len=0;
	 DIR *dir=opendir(argv[1]);
	 struct stat FileStatInfo;
	 
	  if(dir==NULL)
	  	{
	  		 printf("目录打开失败!\n");
	  		 exit(-1);	
	  	}
	  /*循环读取目录*/
	 while(dir_file=readdir(dir))
	 {
	 	  // dir_file->d_name  +  address_dir;
	 	   str_len=strlen(argv[1]); //路径的长度
	 	   str_len+=strlen(dir_file->d_name); //文件名称的长度
	 	   str_len+=1;
	 	   /*申请空间*/
	 	   p=abs_addr=malloc(str_len);
	 	   
	 	   /*拷贝路径*/
	 	   strcpy(abs_addr,argv[1]);
	 	   
	 	   if(strcmp(dir_file->d_name,".")!=0&&strcmp(dir_file->d_name,"..")!=0)
	 	   	{
	 	   	   	/*拼接文件名称*/
		 	   strcat(abs_addr,dir_file->d_name);
		 	   if(stat(abs_addr,&FileStatInfo)!=0)
				 {
					 printf("文件信息获取错误!\n");
					 exit(-1);
				 } 
				 if(S_ISREG(FileStatInfo.st_mode)) //判断是否是普通文件
					{
					   send_file(client_fd,abs_addr,dir_file->d_name); //发送文件
			 	       printf("正在发送:%s 文件!\n",abs_addr);
					}
	 	   	}
	 	    free(p); //释放空间
	 }
	 
	 /*关闭目录*/
	 closedir(dir);
	 return 0;	
}


//发送文件
int send_file(int client_fd,char *absfilename,char *filename)
{
	/*编写发送图片的代码*/
   char buff[1024];        //存放数据缓冲区
   int ack_info;           //存放应答数据
   int count;              //保存读出的字节数
   int fd;                 //文件描述符
   struct filedirinfo fileinfo;//存放TCP头结构
   struct stat t_stat;     //获取文件所有信息的结构体
   
	 //1. 打开图片文件
	 FILE *file=fopen(absfilename,"rb");
	 if(file==NULL)
		{
			 printf("打开文件失败!\n");
			 exit(-1);	
		}
		
	 //把文件指针转化为文件描述符
	 fd=fileno(file);
	 
    /* 获取文件大小 */	
   fstat(fd, &t_stat); //把打开的文件的状态复制到t_stat结构体中
   printf("文件的大小: %d  字节\n",t_stat.st_size);
   
   if(filename==NULL)
   {
   	 printf("%s文件第%d行出现错误。\n",__FILE__ ,__LINE__);
		 return -1; 	 	
   }
	 strncpy(fileinfo.name,filename,20); 
	 fileinfo.file_size=t_stat.st_size; //文件字节大小
	 
	 /*向客户端发送头结构*/
	  write(client_fd,&fileinfo,sizeof(struct filedirinfo));
	  wait_client_ack(client_fd);//等待应答
		while(1)
		{
		     //2. 读出图片内容
		     count=fread(buff,1,1024,file);
		     
		     //3. 向客户端发送数据
		     if(write(client_fd,buff,count)!=count)
		     	{
		     		 printf("%s文件第%d行出现错误。\n",__FILE__ ,__LINE__);
		     	   return -1; //发送错误	
		     	}
		     printf("server tx:%d\n",count);	
		     wait_client_ack(client_fd);//等待应答
		 
		     //5. 判断文件是否到结尾
		     if(feof(file)) 
		     	{
		     		//5. 关闭文件,结束发送
		     		 fclose(file);
		     		 break;
		     	}	
		}
	 return 0;
}


//等待客户端应答
int wait_client_ack(int client_fd)
{
 int count;
  int ack_info;
  while(1)
   {
   	   //接收客户端的应答
   	   count=read(client_fd,&ack_info,4);
   	   //判断应答
   	   if(count>0&&ack_info==8888)
   	   {
   	   		 return 0;
   	   }else
   	   {
   	   	  //处理错误信息。
   	   }
   }	
}

(2)文件发送客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

/***************************结构体定义区域*****************************/
#define PROT 8888 
#pragma pack(1)  //以下结构体以一个字节对齐
struct filedirinfo
{
	 char name[20];
	 int file_size;
	 int count;
};
/**********************************************************************/



/***************************函数声明区域*******************************/
int send_ack(int client_fd);
int receive_file(int client_fd);
/**********************************************************************/


/*客户端创建步骤:
1. 创建网络套接字
2. 连接服务器
*/


//./app 192.168.1.1
int main(int argc,char **argv)
{
	  if(argc!=2)
	  {
	  	 printf("参数传递方式:./app 192.168.1.1\n");
	  	 exit(-1);	
	  }
	  
		int client_fd;  //存放客户端网络套接字
		struct sockaddr_in server_addr; //存放服务器的IP地址信息	 
   
   /*1. 创建网络套接字  PF_INET:ipv4   、SOCK_STREAM:TCP*/
	 client_fd=socket(PF_INET,SOCK_STREAM,0);
	 if(client_fd<0)
	 	{
	 		 printf("客户端套接字创建失败!\n");
	 		 exit(-1);	
	 	}
	 	
	 /*2. 连接服务器*/
	 memset(&server_addr,0,sizeof(struct sockaddr_in)); //初始化结构体内存空间
	 server_addr.sin_family=PF_INET;   //ipv4
	 server_addr.sin_port=htons(PROT); //大端转小端
	 server_addr.sin_addr.s_addr=inet_addr(argv[1]);  //IP地址赋值
	 
	 if(connect(client_fd,(const struct sockaddr *)&server_addr,sizeof(struct sockaddr)))
	 {
	 		 printf("连接服务器失败!\n");
	 		 exit(-1);
	 }
	 
	 while(1)receive_file(client_fd); //接收文件
	 
	 close(client_fd);        //关闭套接字
	 return 0;	
}

//向服务器发送应答信号
int send_ack(int client_fd)
{
	int ack_info=8888;
  //4.给服务器发送应答数据
  if(write(client_fd,&ack_info,4)<0)
  {
  	 printf("%s文件第%d行出现错误。\n",__FILE__ ,__LINE__);
     return -1;	
  }
	return 0;
}


//接收文件
int receive_file(int client_fd)
{
	/*编写接收BMP图片代码*/
   char buff[1024]; //存放数据缓冲区
   int count;       //保存读出的字节数
   
	 /*接收TCP头指针*/
	 struct filedirinfo info;
	 memset(&info,0,sizeof(struct filedirinfo));
	 	while(1)
		{
			  //读出服务器发送过来的数据
			  count=read(client_fd,(char*)&info,sizeof(struct filedirinfo));
			  //判断是否收到数据
			  if(count==sizeof(struct filedirinfo))
			  {
			  	  if(send_ack(client_fd)!=0)return -1; //给服务器发送应答数据
			  	  break;
			  }	
		}
	if(info.name==NULL||info.file_size<=0)
	{
		  printf("%s文件第%d行出现错误。\n",__FILE__ ,__LINE__);
	   	return -1;
	}
	
	//1. 创建图片文件
	 FILE *file=fopen(info.name,"wb");
	 if(file==NULL)
		{
			 printf("%s 文件创建失败!\n",info.name);
			 exit(-1);	
		}
		
		while(1)
		{
			  //读出服务器发送过来的数据
			  count=read(client_fd,buff,1024);
			  if(count>0)
			  {
			  		printf("rx count:%d\n",count);
			  		//向文件中写数据
			  	  fwrite(buff,count,1,file);
			  	  
			  	  //给服务器发送应答数据
			  	  if(send_ack(client_fd)!=0)return -1; //给服务器发送应答数据
			  	  
			  	  if(count!=1024)
			  	  {
			  	  	 //5. 接收完毕关闭文件
			  	  	 fclose(file);
			  	  	 break;	
			  	  }
			  }	
		}
	 printf("文件总大小:%d\n",info.file_size);
	 return 0;
}



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。