题目:
在Linux环境下,编程实现文件的上传和下载,即客户端可以发送文件给服务器,服务器将文件写到服务器端文件系统中;客户端请求下载文件时,服务器读取文件内容,发送给客户端,客户端接受内容并写入本地文件。要求:
- 源代码格式化良好并适当注释;
- 除上述核心功能外,尽量完善程序,比如使其使用方便等;
- 提交报告,报告中包括程序源代码和测试效果截图。
测试效果图:
如下图,我创建了两个文件夹,方便区分服务器端和客户端。
分别打开两个文件夹:
接下来我要在客户端将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等。
结语
题目是网络编程这门课的一次实验,写的时候还处于模仿阶段(现在也差不多),存在一些不足,有相同题目的可以借鉴一下。