一, 项目需求
二, 源码
三, 项目总结
1. 项目需求
服务器端等待链接请求:
fengjunhui@ubuntu:~/homework/ftpserver$ ./server 192.168.1.200 9999 (指定你本机Ubuntu的ip和>5000的端口号)
server sockfd :3
注意:编译生成可执行文件之后./server 和 ./client的执行在不同的目录
客户端登录
fengjunhui@ubuntu:~/homework/ftpserver/feng$ ./client 192.168.1.200 9999 (指定你本机Ubuntu的ip和>5000的端口号)
**************************
***请输入 help 查看选项***
**************************
input your choice: >>>
input your choice: >>> help
*****************************************************
********输入/功能*************************************
********list :查看服务器所在目录的所有文件************
********get filename 下载服务器目录的文件************
********put filename: 上传文件到服务器****************
********quit :关闭客户端 *****************************
******************************************************
功能1----------------查看服务器端的文件列表信息
input your choice: >>> list
***Makefile
***server
***server.c
.......
服务器目录已经接收完毕
服务器应答
目录清单已经成功发送
功能2 --------------从服务器段下载文件到客户端
input your choice: >>> get server.c
下载完毕
ls 客户端所在目录可以看到server.c的文件
服务器提示:
文件传送完成
功能3 ------------向服务器端上传文件
input your choice: >>> put hello.c(自己定义一个文件,输出hello world就行)
上传完毕
服务器提示:
接收文件成功
client client.c client.o Makefile server server.c server.o hello.c
功能4--------------客户端退出,服务器继续等待链接
input your choice: >>> quit
服务器端打印客户端退出
2. 源码
(1)net.h文件
#ifndef _NET_H_ //防止头文件被重复包含
#define _NET_H_
#include<stdio.h>
#include<stdlib.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#include<stdbool.h>
#include<signal.h>
//定义新的数据类型, 简化
typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;
#define ERR_HANDLER(msg) do{perror(msg);exit(EXIT_FAILURE);}while(0)
#define BACKLOG 5
#define CONFIG_FILE ".info"
#define DEBUG(msg) printf("======%s=====\n", msg); //定义调试的宏, 很方便
//配置addr, port
struct info {
char addr[16];
char port[12];
};
//完成对套接字的设置, 通过标志位执行服务端和客户端不同的代码
int SockInit(char *addr, char *port, bool flags);
//该函数完成读入配置文件的内容给sockinit函数
void ConfigInit(struct info *config);
//对read和write函数封装
int Read(int fd, void *buf, size_t size);
int Write(int fd, void *buf, size_t size);
#endif
(2)file_transfer.h文件
#ifndef _NET_H_ //防止头文件被重复包含
#define _NET_H_
#include<stdio.h>
#include<stdlib.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#include<stdbool.h>
#include<signal.h>
//定义新的数据类型, 简化
typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;
#define ERR_HANDLER(msg) do{perror(msg);exit(EXIT_FAILURE);}while(0)
#define BACKLOG 5
#define CONFIG_FILE ".info"
#define DEBUG(msg) printf("======%s=====\n", msg); //定义调试的宏, 很方便
//配置addr, port
struct info {
char addr[16];
char port[12];
};
//完成对套接字的设置, 通过标志位执行服务端和客户端不同的代码
int SockInit(char *addr, char *port, bool flags);
//该函数完成读入配置文件的内容给sockinit函数
void ConfigInit(struct info *config);
//对read和write函数封装
int Read(int fd, void *buf, size_t size);
int Write(int fd, void *buf, size_t size);
#endif
linux@linux:~/homework/lv_11/file_transfer$ cat file_transfer.h
#ifndef _FILI_TRANSFER_H_
#define _FILI_TRANSFER_H_
#include "net.h"
#include<errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
//定义枚举类型, 给消息结构体里面的type, 服务端用此判断发生的事件
enum {
TYPE_OVER, TYPE_OK, TYPE_GET, TYPE_PUT, TYPE_LIST, TYPE_SYNC
};
//定义客户端和服务端交互的消息结构体
typedef struct file_transfer {
uint16_t type; //类型判断
char file_name[BUFSIZ]; //文件名
int file_namelen; //文件名的长度
int file_size; //文件大小
char file_boyd[BUFSIZ];
}FTS;
//公用函数
int Send(int fd, void *buf, size_t size);
int Recv(int fd, void *buf, size_t size);
//客户端函数
int do_fileget(int sockfd, char *argv, struct file_transfer *fts);
int do_fileput(int sockfd, char *argv, struct file_transfer *fts);
int do_filelist(int sockfd, char *argv, struct file_transfer *fts);
int do_filesync(int sockfd, char *argv, struct file_transfer *fts);
//服务端函数
int do_filegethandler(int acceptfd, struct file_transfer *fts);
int do_fileputhandler(int acceptfd, struct file_transfer *fts);
int do_filelisthandler(int acceptfd, struct file_transfer *fts);
int do_filesynchandler(int acceptfd, struct file_transfer *fts);
#endif
(3)server.c文件
#include "file_transfer.h"
int do_client(int acceptfd, struct file_transfer *fts);
int main(int argc, char *argv[])
{
int sockfd, acceptfd; //准备两个变量接收sockfd
pid_t pid; //服务端并法
struct info config;
struct file_transfer fts;
Addr_in peeraddr;
socklen_t peeraddr_size = sizeof(Addr_in);
ConfigInit(&config); //初始化配置addr, port
sockfd = SockInit(config.addr, config.port, true); //准备套接字
while(1) {
//接收客户端的连接请求
if((acceptfd = accept(sockfd, (Addr *)&peeraddr, &peeraddr_size)) < 0) {
ERR_HANDLER("accept");
}
//打印客户端的addr port
printf("[%s %d]:connect success.\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid = fork(); //实现并发
if(pid < 0) {
perror("fork");
return -1;
} else if(pid > 0) {
close(acceptfd); //父进程负责接收客户端的连接请求
} else {
do_client(acceptfd, &fts); //子进程处理客户端的需求
close(sockfd);
}
}
close(sockfd);
return 0;
}
//服务器处理客户端的需求
int do_client(int acceptfd, struct file_transfer *fts) {
//循环接收客户端的连接请求
while(Recv(acceptfd, fts, sizeof(FTS)) > 0) {
switch(fts->type) { //以此判断客户端的需求
case TYPE_GET:
do_filegethandler(acceptfd, fts);
break;
case TYPE_PUT:
do_fileputhandler(acceptfd, fts);
break;
case TYPE_LIST:
do_filelisthandler(acceptfd, fts);
break;
case TYPE_SYNC:
do_filesynchandler(acceptfd, fts);
default:
printf("Client data Invanid.\n");
exit(EXIT_FAILURE); //客户端错误导致退出
}
}
return 0;
}
(4)client.c文件
#include "file_transfer.h"
void UserAgument(int argc, char *argv[]); //用户输入参数的规范检查
int main(int argc, char *argv[])
{
int sockfd;
struct info config; //声明配置结构体
struct file_transfer fts;
//check argument
UserAgument(argc, argv);
ConfigInit(&config);
//准备套接字
sockfd = SockInit(config.addr, config.port, false); //可以执行客户和服务端对sock的需求
switch(argv[1][1]) {
case 'g':
do_fileget(sockfd, argv[2], &fts);
break;
case 'p':
do_fileput(sockfd, argv[2], &fts);
break;
case 'l':
do_filelist(sockfd, argv[2], &fts);
break;
case 's':
do_filesync(sockfd, argv[2], &fts);
break;
default:
printf("Invanid option.\n");
}
return 0;
}
//用户参数检查
void UserAgument(int argc, char *argv[]) {
if(argc < 3) {
printf("%s [-g|p|l|s][filename]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
(5)file_transfer.c文件
#include "file_transfer.h"
/***************************************************/
/*公用函数*/
/***************************************************/
int Send(int fd, void *buf, size_t size) {
int ret;
do{
ret = send(fd, buf, size, 0);
}while(ret < 0 && errno == EINTR);
if(ret < 0) {
printf("send failed.\n");
return -1;
}
return 0;
}
int Recv(int fd, void *buf, size_t size) {
int ret;
do{
ret = recv(fd, buf, size, 0);
}while(ret < 0 && errno == EINTR);
if(ret < 0) {
printf("recv failed.\n");
return -1;
}
return ret;
}
/***************************************************/
/*客户端函数*/
/***************************************************/
int do_fileget(int sockfd, char *argv, struct file_transfer *fts) {
int fd;
fts->type = TYPE_GET;
strcpy(fts->file_boyd, argv);
fts->file_namelen = strlen(argv);
Send(sockfd, fts, sizeof(FTS));
if((fd = open(argv, O_CREAT|O_WRONLY, 0664)) < 0) {
perror("open failed.\n");
return -1;
}
Recv(sockfd, fts, sizeof(FTS));
write(fd, fts->file_boyd, fts->file_size);
close(fd);
return 0;
}
int do_fileput(int sockfd, char argv[0], struct file_transfer *fts) {
int fd;
struct stat st;
fts->type = TYPE_PUT;
strcpy(fts->file_boyd, argv);
fts->file_namelen = strlen(argv);
Send(sockfd, fts, sizeof(FTS));
if((fd = open(argv, O_RDONLY)) < 0) {
perror("open");
return -1;
}
if((stat(argv, &st)) < 0) {
perror("stat");
return -1;
}
Recv(sockfd, fts, sizeof(FTS));
if(fts->type == TYPE_OK) {
fts->file_size = st.st_size;
read(fd, fts->file_boyd, st.st_size);
Send(sockfd, fts, sizeof(FTS));
}
close(fd);
return 0;
}
int do_filelist(int sockfd, char *argv, struct file_transfer *fts) {
fts->type = TYPE_LIST;
Send(sockfd, fts, sizeof(FTS));
while(Recv(sockfd, fts, sizeof(FTS)) > 0) {
if(fts->type == TYPE_OVER)
break;
if(fts->type == TYPE_OK) {
printf("%s\n", fts->file_boyd);
} else {
printf("accept failed.\n");
}
}
return 0;
}
int do_filesync(int sockfd, char *argv, struct file_transfer *fts) {
struct dirent *sd;
DIR *dp;
int fd;
struct stat st;
if((dp = opendir(".")) == NULL) {
printf("opendir failed.\n");
return -1;
}
fts->type = TYPE_SYNC;
while((sd = readdir(dp)) != NULL) {
strcpy(fts->file_name, sd->d_name);
//调试
if((fd = open(sd->d_name, O_RDONLY)) < 0) {
printf("open failed.\n");
return -1;
}
if((stat(sd->d_name, &st)) < 0) {
printf("stat failed .\n");
return -1;
}
if(sd->d_type & DT_REG && sd->d_name[0] == '.') {
fts->file_size = st.st_size;
do_fileput(sockfd,fts->file_name, fts);
}
}
close(fd);
return 0;
}
/***************************************************/
/*服务端函数*/
/***************************************************/
int do_filegethandler(int acceptfd, struct file_transfer *fts) {
struct stat file_attr;
int fd;
printf("%s\n", fts->file_boyd);
if((stat(fts->file_boyd, &file_attr)) < 0) {
perror("stat");
return -1;
}
fts->file_size = file_attr.st_size;
if((fd = open(fts->file_boyd, O_RDONLY)) < 0) {
perror("open");
return -1;
}
read(fd, fts->file_boyd, file_attr.st_size);
Send(acceptfd, fts, sizeof(FTS));
usleep(1000);
fts->type = TYPE_OVER;
Send(acceptfd, fts, sizeof(FTS));
close(fd);
return 0;
}
int do_fileputhandler(int acceptfd, struct file_transfer *fts) {
int fd;
printf("%s\n", fts->file_boyd);
if((fd = open(fts->file_boyd, O_CREAT|O_WRONLY, 0664)) < 0) {
perror("open");
return -1;
}
fts->type = TYPE_OK;
Send(acceptfd, fts, sizeof(FTS));
Recv(acceptfd, fts, sizeof(FTS));
write(fd, fts->file_boyd, fts->file_size);
close(fd);
return 0;
}
int do_filelisthandler(int acceptfd, struct file_transfer *fts) {
DIR *dp;
struct dirent *sd;
if((dp = opendir(".")) == NULL) {
printf("opendir failed.\n");
return -1;
}
fts->type = TYPE_OK;
while((sd = readdir(dp)) != NULL) {
strcpy(fts->file_boyd, sd->d_name);
Send(acceptfd, fts, sizeof(FTS));
}
fts->type = TYPE_OVER;
Send(acceptfd, fts, sizeof(FTS));
return 0;
}
int do_filesynchandler(int acceptfd, struct file_transfer *fts) {
int fd;
//debug
printf("%s\n", fts->file_name);
printf("%s\n", fts->file_boyd);
if((fd = open(fts->file_name, O_CREAT|O_WRONLY), 0220) < 0) {
printf("open failed .\n");
return -1;
}
write(fd, fts->file_boyd, fts->file_size);
close(fd);
return 0;
}
(6)Init.c文件
#include "net.h"
void ConfigInit(struct info *config) {
FILE *fp;
int len;
if((fp = fopen(CONFIG_FILE, "r")) == NULL) {
printf("fopen failed.\n");
return ;
}
//first
if((fgets(config->addr, 16, fp)) == NULL) {
printf("fgets addr failed.\n");
return ;
}
len = strlen(config->addr);
if(config->addr[len] == '\n')
config->addr[len] = '\0';
//second
if((fgets(config->port, 12, fp)) == NULL) {
printf("fgets port failed.\n");
return ;
}
len = strlen(config->port);
if(config->port[len] == '\n')
config->port[len] = '\0';
return ;
}
int SockInit(char *addr, char *port, bool flags) {
int sockfd;
Addr_in serveraddr;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
ERR_HANDLER("socket");
bzero(&serveraddr, sizeof(Addr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(port));
if(!(inet_aton(addr, &serveraddr.sin_addr)));
//server
if(flags) {
//设置套接字属性
int attr_on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &attr_on, sizeof(int ));
if((bind(sockfd, (Addr *)&serveraddr, sizeof(Addr_in))) < 0)
ERR_HANDLER("bind");
if(listen(sockfd, BACKLOG) < 0)
ERR_HANDLER("listen");
}
//client
else {
if(connect(sockfd, (Addr *)&serveraddr, sizeof(Addr_in)) < 0)
ERR_HANDLER("connect");
}
return sockfd;
}
3. 项目总结
(1)对于c语言知识的很多禁区有了全新的认识, 在项目中看似简单的内容, 往往是导致项目过程中影响开发效率的最大因素, 不可以轻视很多小知识, 这样会使你在开发过程中debug到爆炸,从而心态爆炸。
(2)编写makefil, 因为整个程序的过程中需要大量的调试, 不断的编译, 手动编译你会疯的
(3)makefile如下
CC=gcc
CFLAGS=-g -Wall
all:server client
server:server.c file_transfer.c Init.c
gcc -g server.c file_transfer.c Init.c -o server -Wall
client:client.c file_transfer.c Init.c
gcc -g client.c file_transfer.c Init.c -o client -Wall
clean:
rm server client
cp:
cp client .info /home/linux/homework/lv_11/file_transfer/test/