您的位置:首页 > 理论基础 > 计算机网络

Linux网络编程学习之---简单局域网FTP文件传输服务器

2015-04-06 19:09 525 查看
这两天在学习Linux网络编程,老师留了个小作业,就是做一个基于TCP协议的文件服务器。具体要求如下:

编写TCP文件服务器和客户端。

客户端功能:

支持如下指令:

help:显示客户端所有命令和说明。

list:先是服务器端可下载文件列表。

get filename:下载文件。

put filenme:上传文件。

quit:退出。

服务器端功能(单进程):

解析客户端指令并提供相应服务。

以下是代码:

ftp_server.c

/*
* ftp_server.c
*
* Copyright 2015 wnavy <whjwnavy@163.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"

#define INET_NUM 8888//端口号
#define IP_ADDRESS "192.168.1.111"//IP地址

#ifndef BUFF_LEN
#define BUFF_LEN 1028
#endif

#ifndef CMD_HELP
#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误
#endif

int main(void)
{
int sockfd;//网络套接字
//初始化网络连接
sockfd = network_init(NET_SERVER, IP_ADDRESS, INET_NUM);
if(sockfd == -1)
{
perror("Network init error!");
exit(EXIT_FAILURE);
}
printf("LISTEN-ing...\n");

char RCV_BUFF[BUFF_LEN];//接收缓存
char SEND_BUFF[BUFF_LEN];//发送缓存
int cmd_result;//命令解析结果
int connectfd;//建立连接后用于通信的套接字文件
int readlen;//读取到的字节数
while(1)
{
if((connectfd = accept(sockfd, NULL, NULL)) == -1)//出错
{
perror("Connect error!\n");
break;
}
printf("Connect success!\n");
while(1)
{
//接收命令
readlen = read(connectfd, RCV_BUFF, sizeof(RCV_BUFF));
if(readlen <0)//接收出错
{
perror("Read error!\n");
break;
}
else
{
if(readlen == 0)//客户端关闭文件描述符后就会断开连接
{
printf("Welcome to use again!\nQUIT...\n");
break;
}
else
{
//printf("RECV:%s\n",RCV_BUFF);
cmd_result = ftp_cmd_analyse(RCV_BUFF);//解析命令

switch(cmd_result)
{
case CMD_ERROR:
printf("CMD_ERROR!\n");
break;
case CMD_LIST:
printf("List file:%s\n",RCV_BUFF+5);
if(ftp_putlist(connectfd, RCV_BUFF+5) == -1)
{
printf("List files error!\n");
}
else
{
printf("List files success!\n");
}
break;
case CMD_GET://客户端从服务器下载文件
printf("Put files:%s\n", RCV_BUFF+4);
if(ftp_putfile(connectfd, RCV_BUFF+4) == -1)
{
printf("Put files error!\n");
}
else
{
printf("Put files success!\n");
}
break;
case CMD_PUT://客户端上传文件到服务器
printf("Get files:%s\n", RCV_BUFF+4);
if(ftp_getfile(connectfd, RCV_BUFF+4) == -1)
{

printf("Get files error!\n");
}
else
{
printf("Get files success!\n");
}
break;
default:
break;
}
}
}
}
close(connectfd);//客户端退出,断开连接
}
close(sockfd);
return 0;
}


ftp_client.c

/*
* ftp_client.c
*
* Copyright 2015 wnavy <whjwnavy@163.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"

#define INET_NUM 8888//端口号
#define IP_ADDRESS "192.168.1.111"//服务器IP

#ifndef BUFF_LEN
#define BUFF_LEN 1028
#endif

#ifndef CMD_HELP
#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误
#endif

int main(void)
{
int sockfd;
//初始化网络连接
sockfd = network_init(NET_CLIENT, IP_ADDRESS, INET_NUM);
if(sockfd == -1)
{
perror("Network init error!");
exit(EXIT_FAILURE);
}
printf("Connect success!\n");

//向服务器发送数据
char SEND_BUFF[BUFF_LEN];
//char RCV_BUFF[BUFF_LEN];
int cmd_result;//命令解析结果
while(1)
{
fgets(SEND_BUFF, sizeof(SEND_BUFF), stdin);//从键盘输入命令
SEND_BUFF[strlen(SEND_BUFF)-1] = '\0';//去掉最后输入的回车符
cmd_result = ftp_cmd_analyse(SEND_BUFF);//解析命令
switch(cmd_result)
{
case CMD_ERROR:
printf("ERROR!\n");
break;
////////////////////////////////////////////////////////////
case CMD_HELP:
ftp_print_help();
break;
////////////////////////////////////////////////////////////
case CMD_LIST:
printf("List file:%s\n",SEND_BUFF+5);
//列出服务器端可下载的所有文件
if(ftp_getlist(sockfd, SEND_BUFF+5) == -1)
{
printf("List file error!\n");
}
else
{
//printf("List file success!\n");
}
break;
////////////////////////////////////////////////////////////
case CMD_QUIT://跳出循环,关闭文件描述符
printf("Welcome to use again!\nQUIT!\n");
close(sockfd);//客户端关闭文件描述符后就会自动断开连接
exit(EXIT_SUCCESS);
break;
////////////////////////////////////////////////////////////
case CMD_GET://下载文件
printf("Download file:%s\n",SEND_BUFF+4);
//向服务器发送命令
if(write(sockfd, SEND_BUFF, BUFF_LEN) == -1)
{
perror("Send cmd error!");
break;
}
if(ftp_getfile(sockfd, SEND_BUFF+4) == -1)//下载文件
{
printf("Download error!\n");
}
else
{
printf("Download success!\n");
}
break;
////////////////////////////////////////////////////////////
case CMD_PUT:
printf("Upload file:%s\n",SEND_BUFF+4);
//向服务器发送命令
if(write(sockfd, SEND_BUFF, BUFF_LEN) == -1)
{
perror("Send cmd error!");
break;
}
if(ftp_putfile(sockfd, SEND_BUFF+4) == -1)//上传文件
{
printf("Upload error!\n");
}
else
{
printf("Upload success!\n");
}
break;
////////////////////////////////////////////////////////////
default:
break;
}
}
close(sockfd);//客户端关闭文件描述符后就会自动断开连接
return 0;
}


network.h

/*
* network.h
*
* Copyright 2015 wnavy <whjwnavy@163.com>
*
*/
#ifndef __NETWORK_H_
#define __NETWORK_H_

#define NET_SERVER  11
#define NET_CLIENT  22

#define BUFF_LEN 1028//接收发送缓冲区大小

#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误

#define E_NOFILE    "ERROR:No such file or directory!\n"
#define E_DODNLOAD  "ERROR:Download error!\n"
#define E_UPLOAD    "ERROR:Upload error!\n"
#define GET_LIST_END "SUCCESS:GET LIST SUCCESS!"

int     network_init(int net_type, const char* IP_ADDRESS, \
short INET_NUM);

void    ftp_print_help(void);
int     ftp_cmd_analyse(const char* cmd);
int     ftp_getlist(int getsockfd, const char* LIST_NAME);
int     ftp_putlist(int putsockfd, const char* LIST_NAME);
int     ftp_getfile(int getsockfd, const char* GET_FILENAME);
int     ftp_putfile(int putsockfd, const char* PUT_FILENAME);

#endif


network.c

/*
* network.c
*
* Copyright 2015 wnavy <whjwnavy@163.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"
#include <stddef.h>
#include <dirent.h>

#define BACKLOG 10//最大连接数

/***********************************************************************
* 函数作用:网络初始化
* 函数参数:
*      net_type:网络类型
*          NET_SERVER:服务器
*          NET_CLIENT:客户端
*      NET_IP:IP地址
*      NET_NUM:端口号
* 返回值:
*      已经创建好的网络的套接字
* 说明:
*      无
**********************************************************************/
int network_init(int net_type, const char* NET_IP, short NET_NUM)
{
int sockfd;
//创建监听套接字
if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Create socket error!");
//exit(EXIT_FAILURE);
return -1;
}

struct sockaddr_in sockadd;
memset(&sockadd, 0, sizeof(sockadd));
sockadd.sin_family = AF_INET;
sockadd.sin_port = htons(NET_NUM);
sockadd.sin_addr.s_addr = inet_addr(NET_IP);

if(net_type == NET_CLIENT)
{
//连接服务器
if(connect(sockfd, (struct sockaddr*)(&sockadd), \
sizeof(sockadd)) == -1)
{
perror("Connect error!");
//exit(EXIT_FAILURE);
return -1;
}
}
else
{
if(net_type == NET_SERVER)
{
//绑定IP地址断口号
if(bind(sockfd, (struct sockaddr*)(&sockadd), \
sizeof(sockadd)) == -1)
{
perror("Bind error!");
//exit(EXIT_FAILURE);
return -1;
}
//监听客户端
if(listen(sockfd, BACKLOG) == -1)
{
perror("Listen error!");
//exit(EXIT_FAILURE);
return -1;
}
}
else
{
return -1;
}
}
return sockfd;
}

/***********************************************************************
* 函数作用:命令解析
* 函数参数:
*      cmd:存放命令的字符串
* 返回值:
*      命令解析结果(事先定义好的宏)
* 说明:
*      带参数的命令,参数与命令之间用空格分开
**********************************************************************/
int ftp_cmd_analyse(const char* cmd)
{
if(cmd == NULL)
{
return CMD_ERROR;
}
else
{
if(strcmp(cmd, "help") == 0)//无参数命令
return CMD_HELP;
else
{
if(strncmp(cmd, "list ", 5) == 0)//有参数命令
return CMD_LIST;
else
{
if(strcmp(cmd, "quit") == 0)//无参数命令
return CMD_QUIT;
else
{
if(strncmp(cmd, "get ", 4) == 0)//有参数命令
return CMD_GET;
else
{
if(strncmp(cmd, "put ", 4) == 0)//有参数命令
return CMD_PUT;
else
{
return CMD_ERROR;
}
}
}
}
}
}
}

/***********************************************************************
* 函数作用:打印帮助信息
* 函数参数:
*      无
* 返回值:
*      无
* 说明:
*      无
**********************************************************************/
void ftp_print_help(void)
{
printf("--------------------------------\n");
/*printf("Thankyou  to  use  our system!\n");
printf("Creat by:            WHJWNAVY\n");
printf("Contact us: whjwnavy@163.com\n\n");*/
printf("Command List:\n");
printf("help:           search for help.\n");
printf("list:   list all files on server.\n");
printf("quit:          exit this system.\n");
printf("get filename:     download file.\n");
printf("put filename:       upload file.\n");
}

/***********************************************************************
* 函数作用:从服务器端获取文件列表
* 函数参数:
*      getsockfd:用于接收文件的文件描述符
*      LIST_NAME:路径
* 返回值:
*      成功:非负值(getsockfd)
*      失败:-1
* 说明:
*      文件路径末尾的"/"不能省略!
*      "list /home"这种写法是不对的,必须写成“list /home/”
**********************************************************************/
int ftp_getlist(int getsockfd, const char* LIST_NAME)
{
//接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
char GET_BUFF[BUFF_LEN];
int readsize;
sprintf(GET_BUFF,  "list ");
sprintf(GET_BUFF+5, "%s", LIST_NAME);
if(write(getsockfd, GET_BUFF, BUFF_LEN) == -1)//向服务器发送命令
{
perror("Send cmd error!");
return -1;
}
else
{
while(1)//循环读取
{
readsize = read(getsockfd, GET_BUFF, BUFF_LEN);
if(readsize <= 0)//读错误
{
perror("Get list error!");
return -1;
}
else
{
if(strncmp(GET_BUFF, GET_LIST_END, \
sizeof(GET_LIST_END)) == 0)//判断服务器是否发送完毕
{
printf("\n");
break;//发送完毕,退出
}
else
{
printf("%s", GET_BUFF);//服务器端发送完毕,显示文件
}
}
}
}
return getsockfd;
}

/***********************************************************************
* 函数作用:把服务器端文件列表发送到客户端
* 函数参数:
*      putsockfd:用于接收文件的文件描述符
*      LIST_NAME:路径
* 返回值:
*      成功:非负值(putsockfd)
*      失败:-1
* 说明:
*      无
**********************************************************************/
int ftp_putlist(int putsockfd, const char* LIST_NAME)
{
char PUT_BUFF[BUFF_LEN];
int strn, strm;
DIR* dp;
struct dirent *ep;
struct stat st;
char LIST_PATH[256];

dp = opendir(LIST_NAME);//打开目录

if(dp != NULL)//打开目录成功
{
while(ep = readdir(dp))//循环读目录
{
if(ep->d_name[0] != '.')//如果不是隐藏文件或目录
{
//用stat函数读取文件信息,文件名带路径
sprintf(LIST_PATH + sprintf(LIST_PATH, "%s", LIST_NAME), \
"%s", ep->d_name);//为文件名加上路径
if(stat(LIST_PATH, &st) != -1)
{
mode_t filemode = st.st_mode & S_IFMT;
switch (filemode)
{
case S_IFDIR:
strn = sprintf(PUT_BUFF, "DIRE\t");//目录
break;
case S_IFREG:
strn = sprintf(PUT_BUFF, "FILE\t");//文件
break;
case S_IFBLK:
strn = sprintf(PUT_BUFF, "BLCK\t");//块文件
break;
case S_IFCHR:
strn = sprintf(PUT_BUFF, "SPEC\t");//特殊文件
break;
case S_IFIFO:
strn = sprintf(PUT_BUFF, "PIPE\t");//管道文件
break;
default:
break;
}
strm = strn;
strn = sprintf(PUT_BUFF+strm, "%o\t", st.st_mode & \
0x1ff);//权限
strm += strn;
strn = sprintf(PUT_BUFF+strm, "%ld\t", st.st_size);
strm += strn;
strn = sprintf(PUT_BUFF+strm, "%s\n", ep->d_name);
strn = 0;
strm = 0;
//把字符串写回客户端
if(write(putsockfd, PUT_BUFF, BUFF_LEN) == -1)
{
perror("Put list error!");//接收出错,返回
return -1;
}
memset(PUT_BUFF, 0, BUFF_LEN);
}
else
{
perror("Stat error!");
return -1;
}
}
}
if(write(putsockfd, GET_LIST_END, BUFF_LEN) == -1)//发送结束
{
perror("Write endstring error!");
return -1;
}
}
else
{
if(write(putsockfd, GET_LIST_END, BUFF_LEN) == -1)//发送结束
{
perror("Write endstring error!");
return -1;
}
perror("Can't open the directory!");
return -1;
}

closedir(dp);
return putsockfd;
}

/***********************************************************************
* 函数作用:接收文件
* 函数参数:
*      getsockfd:用于接收文件的文件描述符
*      GET_FILENAME:带路径的文件名
* 返回值:
*      成功:接收到的文件的文件描述符
*      失败:-1
* 说明:
*      GET数据包格式:
*      数据包总长度为1028个字节
*      数据包头区:前4个字节,存放数据区大小
*      数据内容区:后1024个字节,存放实际的数据
*
*      服务器每次会从文件中读取1024个字节加上包头(4个字节)后发送给客户端。
*      最后一次(读到文件末尾)时实际读取的数据小于1024个字节。可以以此判断
*      是否读取结束。
*
*      使用本函数前BUFF_LEN必须定义,否则无法使用或造成无法预知的结果!!!
**********************************************************************/
int ftp_getfile(int getsockfd, const char* GET_FILENAME)
{
int getfilefd;//存放接收文件的文件描述符
int getfilesize;//实际接收的文件大小
/*
* #ifndef BUFF_LEN
* #define BUFF_LEN 1028
* #endif
*/
//接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
char GET_BUFF[BUFF_LEN];

//打开一个文件描述符用与保存来自发送端的文件
if((getfilefd = open(GET_FILENAME, O_WRONLY|O_CREAT|O_TRUNC, 0666)) \
== -1)
{
perror("Can't open or creat file!");
return -1;
}
else
{
//接收文件
while(getfilesize = read(getsockfd, GET_BUFF, BUFF_LEN) > 0)
{
/*发送端出错会发送一个“ERROR:xxxx”格式的字符串,接收端以此判断
是否出错,如果发送端不发送此错误信息,发送端出错后挂机,接收端也
将卡机*/
if(strncmp(GET_BUFF, "ERROR:", 6) == 0)//接收文件出错
{
printf("%s", GET_BUFF);
return -1;
}
else
{
//取出数据包头中包含的数据区大小
memcpy(&getfilesize, GET_BUFF, 4);

/*GET_BUFF+4是因为数据包前四个字节存放的是数据长度,之后的
1024个字节才存放的实际的数据*/
if(write(getfilefd, GET_BUFF+4, getfilesize) == -1)
{
perror("Download error!");//接收出错,返回
close(getfilefd);//关闭文件
return -1;
}
if(getfilesize < (BUFF_LEN-4))//已经读取到文件末尾
break;//接收结束,退出
}
}
close(getfilefd);//关闭文件
return getfilefd;//接收完成,返回接收到的文件的文件描述符。
/*但是此值仅有数值上的意义,无实际作用。因为该文件在函数返回前已被
关闭要想在函数中通过此文件描述符打开此文件,就不要在此函数结束时关
闭该文件!*/
}
}

/***********************************************************************
* 函数作用:发送文件
* 函数参数:
*      putsockfd:用于发送文件的文件描述符
*      PUT_FILENAME:带路径的文件名
* 返回值:
*      成功:发送的文件的文件描述符
*      失败:-1
* 说明:
*      无
**********************************************************************/
int ftp_putfile(int putsockfd, const char* PUT_FILENAME)
{
int putfilefd;//存放接收文件的文件描述符
int putfilesize;//实际接收的文件大小
/*
* #ifndef BUFF_LEN
* #define BUFF_LEN 1028
* #endif
*/
//接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
char PUT_BUFF[BUFF_LEN];

if((putfilefd = open(PUT_FILENAME, O_RDONLY)) == -1)//打开文件
{
perror("Open error!");
write(putsockfd, E_NOFILE, BUFF_LEN);//把错误信息写回。
/*如果不写回错误信息,发送端会卡死*/
return -1;
}
else
{
//printf("Open %s success!\n",PUT_FILENAME);
/*
* 先从文件中读取1024个字节放入发送缓冲区的第五个字节开始的1024个字
* 节中,后在把实际读取到的字节数放入发送缓冲区的前四个字节中(int型),
* 最后再把这1028个字节发送出去。
*/
while((putfilesize = read(putfilefd, PUT_BUFF+4, (BUFF_LEN-4))) \
>0)
{
memcpy(PUT_BUFF, &putfilesize, 4);
if(write(putsockfd, PUT_BUFF, BUFF_LEN) == -1)
{
perror("Put file error!");
close(putfilefd);
return -1;
}
memset(PUT_BUFF, 0, BUFF_LEN);//清空缓冲区
//printf("\rDownloading...");
}
}
close(putfilefd);
return putfilefd;
}


<完>

第一次发贴,不足之处还望见谅

学习交流联系:

whjwnavy@163.com

805400349@qq.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: