Linux网络编程实践二(文件上传和下载)

题目:

        在Linux环境下,编程实现文件的上传和下载,即客户端可以发送文件给服务器,服务器将文件写到服务器端文件系统中;客户端请求下载文件时,服务器读取文件内容,发送给客户端,客户端接受内容并写入本地文件。要求:

  1. 源代码格式化良好并适当注释;
  2. 除上述核心功能外,尽量完善程序,比如使其使用方便等;
  3. 提交报告,报告中包括程序源代码和测试效果截图。

测试效果图:

        如下图,我创建了两个文件夹,方便区分服务器端和客户端。

        分别打开两个文件夹:

        接下来我要在客户端将test.txt文件上传到服务端,然后在客户端将服务端的文件test.c下载下来

分别在两个文件夹里面打开终端,编译并运行服务端和客户端的C语言程序:

        在客户端尝试给服务端发送消息

        可以看到,服务端成功的接受到了消息。为了方便,进行消息传递的时候,服务端和客户端的显示我都设成一样的了,当然,上传文件和下载文件的时候不一样。

        然后在服务端上传文件test.txt和下载文件test.c:


然后查看并对比上传/下载的文件:

        可以看到,Server文件夹中多了test.txt文件,Client文件夹中多了test.c文件,并且文件内容是完全一样的,说明文件的上传和下载成功实现。

        同时,代码还具有防卡死的功能,比如说,一端因为文件不存在或其他原因打开文件失败,会将消息传回,防止另一端一直在死循环接收消息而卡死。

        也是因为防卡死,在上传/下载文件结束之后,客户端可以和服务端继续进行消息交互。也因此,客户端可以连续多次的上传或下载文件,并在上传/下载文件之后还可以和服务端进行消息交互。

        客户端上传文件的命令是:上传文件xxxx,下载文件的命令是:下载文件xxxx。

        我的设计是必须要有上传文件(或下载文件)这四个字(可以多,但不能少),才可以进行文件传输,然后我写了个函数get_filename(char buffer[])来将命令中的文件名分割出来,然后进行文件传输。但是文件名不能含有中文,否则读取文件会失败。

        如下图所示,我写的get_filename()函数只会将英文字母的文件名分割出来。

        测试效果展示到这里。

程序源代码:

服务端代码server.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8888	/*侦听端口地址*/
#define BACKLOG 2	/*侦听队列长度*/
#define Maxline 1024	/*最大读取文件行数*/

//------全局变量------
char file_name[105];	/*设置一个全局变量用来保存文件名*/

//------函数声明------
void get_filename(char buffer[]);	/*从buffer字符串中提取出文件名*/
void upload_file(char buffer[], int s);		/*服务端上传文件函数*/
void download_file(char buffer[], int s);	/*服务端接收文件函数*/
void process_conn_server(int s);/*服务端处理连接*/

int main(int argc, char *argv[])
{
	int ss,sc;	/*ss为服务器的socket描述符,sc为客户端的socket描述符*/
	struct sockaddr_in server_addr;		/*服务器地址结构*/
	struct sockaddr_in client_addr;		/*客户端地址结构*/
	int err;	/*返回值*/
	pid_t pid;	/*分叉的进行ID*/

	/*建立一个流式套接字*/
	ss = socket(AF_INET, SOCK_STREAM, 0);
	if(ss < 0){	/*出错*/
		printf("socket error\n");
		return -1;	
	}
	
	/*设置服务器地址*/
	bzero(&server_addr, sizeof(server_addr));	/*清零*/
	server_addr.sin_family = AF_INET;	/*协议族*/
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	/*本地地址*/
	server_addr.sin_port = htons(PORT);	/*服务器端口*/
	
	/*绑定地址结构到套接字描述符*/
	err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(err < 0){/*出错*/
		printf("bind error\n");
		return -1;	
	}
	
	/*设置侦听*/
	err = listen(ss, BACKLOG);
	if(err < 0){	/*出错*/
		printf("listen error\n");
		return -1;	
	}
	
		/*主循环过程*/
	while(1){
		socklen_t addrlen = sizeof(struct sockaddr);
		
		sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen); 
		/*接收客户端连接*/
		if(sc < 0){	/*出错*/
			continue;	/*结束本次循环*/
		}	
		
		/*建立一个新的进程处理到来的连接*/
		pid = fork();		/*分叉进程*/
		if( pid == 0 ){		/*子进程中*/
			process_conn_server(sc);	/*处理连接*/
			close(ss);	/*在子进程中关闭服务器的侦听*/
		}else{
			close(sc);	/*在父进程中关闭客户端的连接*/
		}
	}
}

//------函数实现------
void process_conn_server(int s) {/*服务端处理连接*/
	ssize_t size = 0;
	char buffer[1024];	/*数据的缓冲区*/
	char message[1024];	/*用来传递消息*/

	while (1) {/*循环处理过程*/
		size = read(s, buffer, 1024);	/*从套接字中读取数据放到缓冲区buffer中*/
		if (size == 0) {/*没有数据*/
			return;
		}
		write(1, buffer, size);/*写到标准输出*/
		if (strstr(buffer, "上传文件") != NULL) {	/*收到客户端上传文件的请求*/
			strcpy(message, "服务器端开始接收文件...");

			write(s, message, strlen(message) + 1);	/*发给客户端*/
			write(1, message, strlen(message) + 1);	/*写到标准输出*/

			printf("\n接收中...\n");
			download_file(buffer, s);	/*调用函数进行文件接收*/
			sleep(1);	/*用1s时间来进行缓冲*/
		}
		else if (strstr(buffer, "下载文件") != NULL) {	/*收到客户端下载文件的请求*/
			printf("开始上传文件...\n");

			size = read(s, message, 1024);	/*从客户端读取数据*/
			write(1, message, size);		/*写到标准输出*/

			printf("\n上传中....\n");
			upload_file(buffer, s);		/*调用函数进行文件上传*/
			sleep(1);	/*用1s时间来进行缓冲*/
		}
		else {   /*不上传、下载文件则进行消息交互*/

			/*构建响应字符,为接收到客户端字节的数量*/
			sprintf(buffer, "%ld bytes altogether\n", size);
			char message[] = "收到你的消息啦!你的消息长度是:";
			write(s, message, strlen(message) + 1);	/*发给客户端*/
			write(s, buffer, strlen(buffer) + 1);	/*发给客户端*/

			write(1, message, strlen(message) + 1);	/*写到标准输出*/
			write(1, buffer, strlen(buffer) + 1);	/*写到标准输出*/
		}

	}
}

void download_file(char buffer[], int s) {	/*服务端接收文件函数*/
	get_filename(buffer);	/*获取文件名*/

	FILE* fp = fopen(file_name, "w+");	/*用文件指针打开文件,如果文件不存在会新建一个*/
	if (fp == NULL) {
		printf("打开文件失败!接收失败!\n");
		/*因为此时客户端处于持续发送消息状态,所以把消息传回客户端的话可能会有bug,所以不将消息回传*/
		return;
	}
	size_t size = 0;
	char message[1024] = { 0 };		/*创建一个字符串用来传递消息*/
	while (1) {
		size = read(s, message, 1024);
		if (size < 0) {		/*没有数据*/
			printf("接收失败!\n");
			return;
		}
		if (strstr(message, "打开文件失败") != NULL) {	/*接受到客户端打开文件失败的消息*/
			printf("客户端打开文件失败!接收失败!\n");
			return;
		}

		printf("接收中...\n");
		if (strstr(message, "上传结束") != NULL) break;	/*接收到上传结束的消息就退出死循环*/
		fprintf(fp, "%s", message);	/*将接收到的文件内容保存进文件*/
	}
	printf("接收成功!\n");
	fclose(fp);	/*关闭文件指针*/
	return;
}

void upload_file(char buffer[], int s) {		/*服务端上传文件函数*/
	get_filename(buffer);	/*取出文件名*/

	FILE* fp = fopen(file_name, "r");	/*用文件指针打开文件*/
	if (fp == NULL) {
		char message2[] = "打开文件失败!上传失败!";
		printf("%s\n", message2);
		write(s, message2, strlen(message2) + 1);	/*将打开文件失败的消息传送到客户端,防止客户端卡死*/
		return;
	}
	char message[1024] = { 0 };
	while (fgets(message, Maxline, fp) != NULL) {	/*以行作为单位读取文件*/
		write(s, message, strlen(message) + 1);	/*将读取到的文件内容上传的客户端*/
		sleep(1);	/*每上传一行就暂停1秒,防止客户端来不及接收消息卡死*/
	}
	strcpy(message, "上传结束");
	write(s, message, strlen(message) + 1);	/*将上传结束的消息传递给客户端*/
	printf("\n上传成功\n");
	fclose(fp);	/*关闭文件指针*/
	return;
}

void get_filename(char buffer[]) {	/*从buffer字符串中提取出文件名*/
	memset(file_name, 0, strlen(file_name));	/*因为file_name是全局变量,所以需要先清零*/
	int j = 0;
	for (int i = 0; i < strlen(buffer); i++)
		if ((buffer[i] >= 'a' && buffer[i] <= 'z') || (buffer[i] >= 'A' && buffer[i] <= 'Z') || (buffer[i] == '.'))
			file_name[j++] = buffer[i];
}

客户端代码client.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888	/*侦听端口地址*/
#define Maxline 1024	/*最大读取文件行数*/

//------全局变量------
char file_name[105];	/*设置一个全局变量用来保存文件名*/

//------函数声明------
void get_filename(char buffer[]);	/*从buffer字符串中提取出文件名*/
void upload_file(char buffer[], int s);		/*文件上传函数*/
void download_file(char buffer[], int s);	/*接收文件*/
void process_conn_client(int s);	/*客户端处理过程*/

//------主函数------
int main(int argc, char *argv[])
{
	int s;		/*s为socket描述符*/
	struct sockaddr_in server_addr;		/*服务器地址结构*/
	
	s = socket(AF_INET, SOCK_STREAM, 0); 	/*建立一个流式套接字 */
	if(s < 0){	/*出错*/
		printf("socket error\n");
		return -1;
	}	
	
	/*设置服务器地址*/
	bzero(&server_addr, sizeof(server_addr));	/*清零*/
	server_addr.sin_family = AF_INET;	/*协议族*/
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	/*本地地址*/
	server_addr.sin_port = htons(PORT);	/*服务器端口*/
	
	/*将用户输入的字符串类型的IP地址转为整型*/
	inet_pton(AF_INET, argv[1], &server_addr.sin_addr);	
	/*连接服务器*/
	connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
	process_conn_client(s);		/*客户端处理过程*/
	close(s);	/*关闭连接*/
	return 0;
}

//------函数实现------
void process_conn_client(int s){
	ssize_t size = 0;
	char buffer[1024];	/*数据的缓冲区*/
	char message[1024];	/*用来传递消息*/

	while (1) {	/*循环处理过程*/
		/*从标准输入中读取数据放到缓冲区buffer中*/
		size = read(0, buffer, 1024);
		if (size > 0) {	/*读到数据*/
			write(s, buffer, size);		/*发送给服务器*/
			if (strstr(buffer, "上传文件") != NULL) {	/*上传文件*/
				printf("开始上传文件...\n");

				size = read(s, message, 1024);	/*从服务器读取数据*/
				write(1, message, size);		/*写到标准输出*/

				printf("\n上传中....\n");
				upload_file(buffer, s);		/*调用对应函数进行上传*/
				sleep(1);	/*用1s时间来进行缓冲*/
			}
			else if (strstr(buffer, "下载文件") != NULL) {	/*接收文件*/
				strcpy(message, "客户端开始下载文件...");

				write(s, message, strlen(message) + 1);	/*发给服务端*/
				write(1, message, strlen(message) + 1);	/*写到标准输出*/

				printf("\n下载中...\n");
				download_file(buffer, s);	/*调用接收文件的函数*/
				sleep(1);	/*用1s时间来进行缓冲*/
			}
			else {
				size = read(s, buffer, 1024);	/*从服务器读取数据*/
				write(1, buffer, size);		/*写到标准输出*/
			}
		}
	}
}

void download_file(char buffer[], int s) {	/*接收文件*/
	get_filename(buffer);	/*获取文件名*/

	FILE* fp = fopen(file_name, "w+");	/*用文件指针打开文件,如果文件不存在会新建一个*/
	if (fp == NULL) {
		printf("打开文件失败!接收失败!\n");
		/*因为此时服务端处于持续发送消息状态,所以把消息传回服务端的话可能会有bug,所以不将消息回传*/
		return;
	}
	size_t size = 0;
	char message[1024] = { 0 };		/*创建一个字符串用来传递消息*/
	while (1) {
		size = read(s, message, 1024);
		if (size < 0) {		/*没有数据*/
			printf("接收失败!\n");
			return;
		}
		if (strstr(message, "打开文件失败") != NULL) {	/*接受到客户端打开文件失败的消息*/
			printf("服务端打开文件失败!接收失败!\n");
			return;
		}
		if (strstr(message, "收到你的消息啦!") != NULL) {
			/*经运行检测发现有额外输入文件的数据,这里进行隐形处理*/
			continue;
		}

		printf("接收中...\n");
		if (strstr(message, "上传结束") != NULL) break;	/*接收到上传结束的消息就退出死循环*/
		fprintf(fp, "%s", message);	/*将接收到的文件内容保存进文件*/
	}
	printf("接收成功!\n");
	fclose(fp);	/*关闭文件指针*/
	return;
}

void upload_file(char buffer[], int s) {		/*文件上传函数*/
	get_filename(buffer);	/*取出文件名*/

	FILE* fp = fopen(file_name, "r");	/*用文件指针打开文件*/
	if (fp == NULL) {
		char message2[] = "打开文件失败!上传失败!";
		printf("%s\n", message2);
		write(s, message2, strlen(message2) + 1);	/*将打开文件失败的消息传送到服务端,防止服务端卡死*/
		return;
	}
	char message[1024] = { 0 };
	while (fgets(message, Maxline, fp) != NULL) {	/*以行作为单位读取文件*/
		write(s, message, strlen(message) + 1);	/*将读取到的文件内容上传的服务端*/
		sleep(1);	/*每上传一行就暂停1秒,防止服务端来不及接收消息*/
	}
	strcpy(message, "上传结束");
	write(s, message, strlen(message) + 1);	/*将上传结束的消息传递给服务端*/
	printf("\n上传成功\n");
	fclose(fp);	/*关闭文件指针*/
	return;
}

void get_filename(char buffer[]) {	/*从buffer字符串中提取出文件名*/
	memset(file_name, 0, strlen(file_name));	/*因为file_name是全局变量,所以需要先清零*/
	int j = 0;
	for (int i = 0; i < strlen(buffer); i++)
		if ((buffer[i] >= 'a' && buffer[i] <= 'z') || (buffer[i] >= 'A' && buffer[i] <= 'Z') || (buffer[i] == '.'))
			file_name[j++] = buffer[i];
}

存在的一些bug或不足

        1、上传或下载文件后会有一些换行问题。(这个是将数据分太多步发送的缘故)

        2、老师题目说的上传和下载文件并不仅仅指文本文件,而我使用fopen、fgets、fprintf这些函数都是用于文本文件的,应该使用read和write函数进行读写。(使用read/write函数进行文件读写的话,是按照指定的大小进行读取,这样虽然每次读取会暂定一秒,传输文件的速度也会比较快。)

        3、老师在评价我的编程实践三的时候说,使用read和write进行服务端和客户端的通信不太好(),最好用sendto等。

结语

        题目是网络编程这门课的一次实验,写的时候还处于模仿阶段(现在也差不多),存在一些不足,有相同题目的可以借鉴一下。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xiaoyuer2815

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

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

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

打赏作者

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

抵扣说明:

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

余额充值