目录
(1)服务端和客户端在两个不同的目录文件夹下, 实现在客户端对客户端目录下的文件上传到服务端, 从服务器获取服务器下的文件列表清单,从服务端下载文件到客户端目录下。
(2)服务器要求可以同时连接多个客户端
(3)要求服务端吗客户端的IP, PORT使用函数实现读取配置文件中的数据填写到相应的位置
(4)要求服务端和客户的socket通信部分使用一个函数实现, 服务端和客户端通过给该函数传参,执行不同的代码
(5)设置服务器的套接字属性为地址快速重用
(6)编写Makefile, 在makefile中实现快捷操作
(1)客户端以这样的形式执行
./client -g filename 从服务器下载文件名为filename的文件
./client -p filename 把客户端文件名为filename的文件上传到服务器
./client -l 从服务器获取到服务器上的文件名
./client -s 云同步客户端的文件到服务器
(2)项目实现的启动流程
在命令行输入make命令, 编译完成后, 输入make install 将配置文件和client执行文件移动到与服务器不同的文件夹下, 启动服务器, ./server, 到客户端目录下, 按照(1)所描述的形式启动客户端
#ifndef _NET_H_
#define _NET_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 5
#define ErrExit(msg) do{fprintf(stderr, "%s %d",__FUNCTION__,__LINE__);\
perror(msg);exit(EXIT_FAILURE);}while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
void Argment(int argc, char *argv[]);
int SocketInit(char *addr, char *port, bool server);
ssize_t Read(int fd, void *buf, size_t len);
/*可变参数void *可以匹配多种类型的数据类型*/
ssize_t Write(int fd, const void *buf, size_t len);
#endif
#ifndef _FILE_TRANSFER_H_
#define _FILE_TRANSFER_H_
#include "net.h"
#include
#include
#include
#include
#include
#define DEBUG printf("==debug information==\n");
#define CONFIG_FILE ".config"
#define SIZE_IP_STR 17
#define SIZE_PORT_STR 6
#define SIZE_PATH_STR 255
#define SIZE_CLOUD_SPACE 1024*1024*1024 //云空间大小 单位:byte
#define SIZE_FT_TYPE 1
#define SIZE_FT_F_SIZE 8
/*自定义协议类型*/
enum {
TYPE_ERR, TYPE_OK, TYPE_GET, TYPE_PUT, TYPE_LIST, TYPE_SYNC,
}; //枚举类型
/*结构体, 数组, ....都属于数据结构*/
/*自定义协议结构体, 文件的数据各个参数信息*/
struct file_transfer {
uint8_t type;
/*GNU C的扩展属性,0长度的数组不占用内存空间
* msg 有效时,其他字段无效
* */
char f_size[8];
uint8_t len;
char f_name[0];
char msg[0];
char f_body[0];
};
/*环境信息*/
struct config{
char ip[SIZE_IP_STR];
char port[SIZE_PORT_STR];
};
/*文件链表*/
typedef struct node{
time_t mtime; //最后一次修改的时间
char name[NAME_MAX]; //文件名
struct node *next;
}node_t;
/**客户端函数**/
/*********************************/
/*初始化环境*/
void FT_InitConfig(struct config *conf);
/*获取文件*/
void FT_GetFile(int sockfd, char *f_name, struct file_transfer *ft);
/*上传文件*/
void FT_PutFile(int sockfd, char *f_name, struct file_transfer *ft);
/*获取文件列表*/
void FT_FileList(int sockfd, struct file_transfer *ft);
/*同步文件信息*/
void FT_Sync(int sockfd, struct file_transfer *ft);
/*********************************/
/**服务端函数**/
/*********************************/
/*处理获取文件的请求*/
void FT_GetFileHandler(int sockfd, struct file_transfer *ft);
/*处理上传文件的:请求*/
void FT_PutFileHandler(int sockfd, struct file_transfer *ft);
/*处理获取文件列表的请求*/
void FT_FileListHandler(int sockfd, struct file_transfer *ft);
/*同步文件信息*/
void FT_SyncHandler(int sockfd, struct file_transfer *ft);
#endif
#include "net.h"
int SocketInit(char *str_ip, char *str_port, bool server){
int fd;
Addr_in addr;
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)
ErrExit("socket");
/*设置通信结构体*/
bzero(&addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(str_port) );
if (inet_aton(str_ip, &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
if(server){ //判断是否为服务端
/*地址快速重用*/
int b_reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int) );
/*绑定地址*/
if( bind(fd, (Addr *)&addr, sizeof(addr) ) )
ErrExit("bind");
/*设定为监听模式*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
} else /*如果是客户端就发起连接请求*/
if( connect(fd, (Addr *)&addr, sizeof(addr) ) )
ErrExit("connect");
return fd;
}
/*把read和wirte函数的出错处理封装起来*/
ssize_t Read(int fd, void *buf, size_t len) {
ssize_t ret;
do {
ret = read(fd, buf, len);
} while(ret < 0 && errno == EINTR);
if(ret < 0)
ErrExit("read");
return ret;
}
ssize_t Write(int fd, const void *buf, size_t len) {
ssize_t ret;
do {
ret = write(fd, buf, len);
} while(ret < 0 && errno == EINTR);
if(ret < 0)
ErrExit("write");
return ret;
}
#include "file_transfer.h"
/*私有函数*/
/*********************************/
/*创建文件,并写入空内容*/
/*本函数只能在该原文件内使用, 限制了使用范围*/
static int W_zero(char *f_name, long long f_size){
int count, ret;
char buf[BUFSIZ] = {};
int f_fd = open(f_name, O_WRONLY|O_CREAT|O_TRUNC, 0640); //创建文件open多一个参数, 加上文件创建之后的权限
if(f_fd < 0){
perror("open");
return f_fd;
}
/*占领磁盘空间(内核一次最多能写入BUFSIZ)*/
while(f_size > 0){
if(f_size > BUFSIZ) count = BUFSIZ;
else count = f_size;
ret = Write(f_fd, buf, count);
if(ret < 0)
return ret;
f_size -= ret;
}
close(f_fd);
return 0;
}
/*接收文件*/
void W_body(int sockfd,char *f_name, long long f_size){
char buf[BUFSIZ] = {};
int f_fd, ret, count;
if( (f_fd = open(f_name, O_WRONLY) ) < 0)
ErrExit("open");
while(f_size > 0){
if(f_size > BUFSIZ) count = BUFSIZ;
else count = f_size;
ret = Read(sockfd, buf, count);
Write(f_fd, buf, ret);
f_size -= ret;
}
close(f_fd);
}
/*发送文件*/
void R_body(int sockfd,char *f_name, long long f_size){
char buf[BUFSIZ] = {};
int f_fd, count, ret;
/*打开文件*/
if( (f_fd = open(f_name, O_RDONLY) ) < 0)
return;
while(f_size > 0){
if(f_size > BUFSIZ) count = BUFSIZ;
else count = f_size;
ret = Read(f_fd, buf, count);
Write(sockfd, buf, ret);
f_size -= ret;
}
close(f_fd);
}
/**客户端函数**/
/*********************************/
/*初始化环境*/
void FT_InitConfig(struct config *conf){
size_t len;
FILE *fp = fopen(CONFIG_FILE, "r"); //使用标准IO打开配置文件
if(fp == NULL)
ErrExit("fopen");
/*设置IP地址*/
fgets(conf->ip, SIZE_IP_STR, fp); //fgets自动加上换行符
len = strlen(conf->ip);
if(conf->ip[len-1] == '\n')
conf->ip[len-1] = '\0';
/*设置端口号*/
fgets(conf->port, SIZE_IP_STR, fp);
len = strlen(conf->port);
if(conf->port[len-1] == '\n')
conf->port[len-1] = '\0';
#ifdef DEBUG
/*一些预定义的符号*/
printf("[%s:%d] conf->ip=%s\n", __FUNCTION__, __LINE__,
conf->ip);
printf("[%s:%d] conf->port=%s\n", __FUNCTION__, __LINE__,
conf->port);
#endif
fclose(fp);
}
/*获取文件*/
void FT_GetFile(int sockfd, char *f_name, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
long long f_size;
ft->type = TYPE_GET;
/*发送请求信息*/
Write(sockfd, ft, SIZE_FT_TYPE);
ft->len = strlen(f_name);
Write(sockfd, &ft->len, 1); //发送文件名长度
Write(sockfd, f_name, ft->len); //发送文件名
/*接收请求结果*/
Read(sockfd, ft, SIZE_FT_TYPE);
if(ft->type == TYPE_OK){
Read(sockfd, &f_size, SIZE_FT_F_SIZE); //接收文件大小
/*创建文件,并写入空内容*/
W_zero(f_name, f_size); //把大小给这个函数,站好磁盘空间
/*写入文件内容*/
W_body(sockfd, f_name, f_size);
}else{
/*如果接收文件失败,打印错误信息*/
Read(sockfd, &ft->len, 1);
Read(sockfd, ft->msg, ft->len);
ft->msg[ft->len] = '\0';
printf("get file [%s] failed [%s]\n", f_name, ft->msg);
}
}
/*上传文件*/
void FT_PutFile(int sockfd, char *f_name, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
/*打开文件*/
int f_fd = open(f_name, O_RDONLY);
if(f_fd < 0)
ErrExit("open");
/*获取文件属性*/
struct stat st;
if( fstat(f_fd, &st) ) //得到文件的相关信息放到结构体
ErrExit("stat");
/*检查文件类型:只能上传普通文件*/
if(!(st.st_mode & S_IFREG)) //mode域下面的某一个标志位
return;
/*获取文件大小*/
long long f_size = (long long)st.st_size;
/*设置自定义协议*/
ft->type = TYPE_PUT; //消息类型
Write(sockfd, ft, SIZE_FT_TYPE); //发送消息类型
Write(sockfd, &f_size, SIZE_FT_F_SIZE); //发送文件大小
ft->len = strlen(f_name);
Write(sockfd, &ft->len, 1);
Write(sockfd, f_name, ft->len); //发送文件名字
/*等待确认*/
if( !Read(sockfd, ft, SIZE_FT_TYPE) )
return;
if(ft->type == TYPE_OK){ //发送文件
R_body(sockfd, f_name, f_size);
}else{ //如果确认失败,打印错误信息
Read(sockfd, &ft->len, 1);
Read(sockfd, ft->msg, ft->len);
ft->msg[ft->len] = '\0';
printf("get file [%s] failed [%s]\n", f_name, ft->msg);
}
/*关闭文件*/
close(f_fd);
}
/*获取文件列表*/
void FT_FileList(int sockfd, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__); //哪一个函数, 哪一行
time_t mtime;
ft->type = TYPE_LIST;
Write(sockfd, &ft->type, SIZE_FT_TYPE);
if( Read(sockfd, ft, SIZE_FT_TYPE) == 0)
return;
if(ft->type == TYPE_OK){
while(1){
Read(sockfd, &ft->len, 1);
Read(sockfd, ft->f_name, ft->len);
if(ft->len == 0)
break;
ft->f_name[ft->len] = '\0';
printf("%-32s", ft->f_name);
Read(sockfd, &mtime, sizeof(time_t) );
printf("%s", ctime(&mtime) );
}
}else{
printf("get list failed [%s]", ft->msg);
}
}
/*同步文件信息*/
void FT_Sync(int sockfd, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
DIR *dir;
struct dirent *p;
/*打开目录*/
if( (dir = opendir(".") ) == NULL)
ErrExit("opendir");
/*读取目录*/
while( (p = readdir(dir) ) != NULL ){
if(p->d_type == DT_REG && p->d_name[0] != '.'){
printf("输入任意键,继续上传文件%s\n", p->d_name);
FT_PutFile(sockfd, p->d_name, ft);
}
}
closedir(dir);
}
/*********************************/
/**服务端函数**/
/*********************************/
/*处理获取文件的请求*/
void FT_GetFileHandler(int sockfd, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
Read(sockfd, &ft->len, 1);
Read(sockfd, ft->f_name, ft->len);
ft->f_name[ft->len] = '\0';
/*获取文件属性*/
struct stat st;
if( stat(ft->f_name, &st) )
ErrExit("stat");
/*检查文件类型:只能下载普通文件*/
char *errmsg = "只可以下载普通文件\n";
if(!(st.st_mode & S_IFREG) || ft->f_name[0] == '.'){
ft->type = TYPE_ERR;
Write(sockfd, &ft->type, SIZE_FT_TYPE);
ft->len = strlen(errmsg);
Write(sockfd, &ft->len, 1);
Write(sockfd, errmsg, ft->len);
return;
}
ft->type = TYPE_OK;
Write(sockfd, &ft->type, SIZE_FT_TYPE);
long long f_size = (long long)st.st_size;
Write(sockfd, &f_size, SIZE_FT_F_SIZE);
/*发送文件内容*/
R_body(sockfd, ft->f_name, f_size);
}
/*处理上传文件的请求*/
void FT_PutFileHandler(int sockfd, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
long long f_size;
/*创建文件*/
Read(sockfd, &f_size, SIZE_FT_F_SIZE);
Read(sockfd, &ft->len, 1);
Read(sockfd, ft->f_name, ft->len);
ft->f_name[ft->len] = '\0';
if(ft->f_name[0] == '.'){
ft->type = TYPE_ERR;
sprintf(ft->msg, "不可以上传隐藏文件\n");
Write(sockfd, ft, SIZE_FT_TYPE+strlen(ft->msg)+1);
return;
}
int f_fd = open(ft->f_name, O_WRONLY|O_CREAT|O_TRUNC, 0640);
if(f_fd < 0) {
ft->type = TYPE_ERR;
Write(sockfd, &ft->type, SIZE_FT_TYPE);
sprintf(ft->msg, "[创建打开失败][open:%s]\n", strerror(errno) );
ft->len = strlen(ft->msg);
Write(sockfd, &ft->len, 1);
Write(sockfd, ft->msg, ft->len);
printf("[send msg]%s\n", ft->msg);
return;
}
/*占领磁盘空间(内核一次最多能写入BUFSIZ)*/
if (W_zero(ft->f_name, f_size) < 0){
ft->type = TYPE_ERR;
Write(sockfd, &ft->type, SIZE_FT_TYPE);
sprintf(ft->msg, "[磁盘空间不足][write:%s]\n", strerror(errno) );
ft->len = strlen(ft->msg);
Write(sockfd, &ft->len, 1);
Write(sockfd, ft->msg, ft->len);
printf("[send msg]%s\n", ft->msg);
return;
}
/*发送确认信息*/
ft->type = TYPE_OK;
if( Write(sockfd, ft, 1) < 0)
return;
/*写入文件内容*/
W_body(sockfd, ft->f_name, f_size);
printf("[文件接收成功]文件名: %-32s文件大小:%lld\n", ft->f_name, f_size);
}
/*处理获取文件列表的请求*/
void FT_FileListHandler(int sockfd, struct file_transfer *ft){
printf("[%s:%d]\n", __FUNCTION__, __LINE__);
DIR *dir;
struct dirent *p;
struct stat st; //用于存储文件的相关属性的结构体
/*打开目录*/
if( (dir = opendir(".") ) == NULL){
ft->type = TYPE_ERR;
sprintf(ft->msg, "[目录打开失败][opendir:%s]\n", strerror(errno) );
Write(sockfd, ft, SIZE_FT_TYPE+strlen(ft->msg)+1);
return;
}
/*读取目录*/
ft->type = TYPE_OK;
Write(sockfd, ft, SIZE_FT_TYPE);
while( (p = readdir(dir) ) != NULL ){
if(p->d_type == DT_REG && p->d_name[0] != '.'){
ft->len = strlen(p->d_name);
Write(sockfd, &ft->len, 1);
Write(sockfd, p->d_name, ft->len);
stat(p->d_name, &st);
Write(sockfd, &st.st_mtime, sizeof(time_t) );
}
}
ft->len = 0;
Write(sockfd, &ft->len, 1);
closedir(dir);
}
/*同步文件信息*/
void FT_SyncHandler(int sockfd, struct file_transfer *ft){
}
#include "file_transfer.h"
#define MAXEVENTS 1024
void Client_Handle(int newfd, struct file_transfer *ft);
int main(int argc, char *argv[])
{
char buf[BUFSIZ] = {};
int fd, newfd;
int epfd;
int nfds = 0; //用来描述准备好IO操作的数量
struct config conf;
struct file_transfer *ft = (struct file_transfer *)buf; //把buf的空间给结构体, 就不用再申请内存空间了
struct epoll_event temp, events[MAXEVENTS];
Addr_in peeraddr;
socklen_t peeraddr_size = sizeof(Addr_in);
/*根据config初始化环境*/
FT_InitConfig(&conf); //读出配置文件的内容给配置结构体
/*准备套接字*/
fd = SocketInit(conf.ip, conf.port, true);
/*循环处理客户端请求*/
//EPOLL实现多路复用
if((epfd = epoll_create(1)) < 0) {
perror("epoll_create");
return -1;
}
temp.events = EPOLLIN;
temp.data.fd = fd;
if((epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &temp)) < 0) {
perror("epoll_ctl");
return -1;
}
int i;
while(1){
if((nfds = epoll_wait(epfd, events, MAXEVENTS, -1)) < 0) {
perror("epoll_wait");
return -1;
}
for(i = 0; i < nfds; i++) {
if(events[i].data.fd == fd) {
/*接收来自客户端的连接*/
newfd = accept(fd, (Addr *)&peeraddr, &peeraddr_size);
printf("[%s %d] Connect succes .\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
temp.data.fd = newfd;
temp.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &temp);
} else {
Client_Handle(newfd, ft);
close(newfd);
}
}
}
close(fd);
return 0;
}
/*有一个结构体来封装文件的相关信息*/
void Client_Handle(int newfd, struct file_transfer *ft){
char *err_str = "Invalid option!";
/*接收客户端数据*/
while(Read(newfd, ft, SIZE_FT_TYPE) > 0){
switch(ft->type){
case TYPE_GET://处理获取文件的请求
FT_GetFileHandler(newfd, ft);
break;
case TYPE_PUT://处理上传文件的请求
FT_PutFileHandler(newfd, ft);
break;
case TYPE_LIST://处理获取文件列表的请求
FT_FileListHandler(newfd, ft);
break;
case TYPE_SYNC://同步文件信息
FT_SyncHandler(newfd, ft);
break;
default:
Write(newfd, err_str, strlen(err_str));
}
}
}
#include "file_transfer.h"
void Usage(int argc, char *argv[]);
int main(int argc, char *argv[])
{
char buf[BUFSIZ] = {};
struct config conf;
struct file_transfer *ft = (struct file_transfer *)buf;
/*参数检查*/
Usage(argc, argv);
/*根据config初始化环境*/
FT_InitConfig(&conf);
/*准备套接字*/
int fd = SocketInit(conf.ip, conf.port, false);
/*执行相应功能*/
switch(argv[1][1]) {
case 'g': //获取文件
FT_GetFile(fd, argv[2], ft);
break;
case 'p': //上传文件
FT_PutFile(fd, argv[2], ft);
break;
case 'l'://获取文件列表
FT_FileList(fd, ft);
break;
case 's'://同步文件信息
FT_Sync(fd, ft);
break;
default:
fprintf(stderr, "Invalid option!\n");
}
/*关闭文件描述符*/
close(fd);
return 0;
}
void Usage(int argc, char *argv[]){
if(argc < 2){
fprintf(stderr, "%s[type]\n", argv[0]);
fprintf(stderr, "\t[type] -g: get file.\n");
fprintf(stderr, "\t[type] -p: put file.\n");
fprintf(stderr, "\t[type] -l: upload file list\n");
fprintf(stderr, "\t[type] -s: File synchronization.\n");
exit(EXIT_FAILURE);
}
if((argv[1][1] == TYPE_GET || argv[1][1] == TYPE_PUT) && argc < 3){
fprintf(stderr, "\t[type] -g: get file.\n");
fprintf(stderr, "\t[type] -p: put file.\n");
exit(EXIT_FAILURE);
}
}
all:client server
CFLAGS=-g -Wall
client:client.c net.c file_transfer.c
server:server.c net.c file_transfer.c
install:
cp client client-mod/
cp server server-mod/
#cp server /mnt/hgfs/download-share/
clean:
rm client server
0(IPV4地址)
8888(端口号)
1. 编写代码之前切记先分析好需求, 画好程序的思维导图, process on值得推荐。
2.不要怕分析和确定代码思路浪费时间, 因为我发现一顿瞎写和重复修改才是最浪费时间的。
3. 先把整个项目的代码框架搭建起来, 在一个一个的去实现每一个功能模块。
4. 完成每一个模块, 都先测试是否成功。
5. 在不确定代码在哪个地方出现问题无从下手调试的时候可以打印调试信息。
6. 调试语句可以这些写
//msg是字符串, 填写的时候别忘记加上双引号
#define DEBUG(msg) printf("======%s======\n", msg)
这样, msg那你可以写1, 2, 3、、、、这样就知道代码执行到哪一个地方出问题, 也很清晰, 分享给大家。