一、架构:
服务端:
1)socket
2)bind
3)listen
4)accept
5)recv
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:一个用于标识已连接套接口的描述字。
buf:包含待发送数据的缓冲区。
len:缓冲区中数据的长度。
flags:调用执行方式。
客户端:
1)socket
2)connect
3)send
recv
创建一个结构体,用作传输文件的载体:
#ifndef MSG_H
#define MSG_H
typedef enum FTP_CMD FTP_CMD;
enum FTP_CMD{
FTP_CMD_LS=0,
FTP_CMD_GET=1,
FTP_CMD_PUT=2,
FTP_CMD_QUIT=3,
FTP_CMD_CD=4,
FTP_CMD_AUTH=5,
FTP_CMD_HIST=6,
FTP_CMD_ERROR,
};
struct Msg{
FTP_CMD cmd;
char args[32];
char md5[64];
long data_lenth;
char data[5000];
};
struct Auth{
enum FTP_CMD cmd;
char username[32];
char password[32];
};
#endif
客户端接收键盘输入的指令到Msg.args,客户端对Msg.arg进行处理,找到对应的cmd,将cmd传给服务端,服务端执行对应的cmd命令,将执行的结果传到Msg.data反馈给客户端。
例如输入ls指令:
//客户端:
int handle_user_input(struct Msg *msg_send) {
char buf[32];
printf("please input\n");
fgets(buf, 32, stdin);
if (0 == memcmp(buf, "ls", 2))
{
cmd = FTP_CMD_LS;
}
if (cmd == FTP_CMD_ERROR)
{
return -1;
}
msg_send->cmd = cmd;
strcpy(msg_send->args, buf);
return 0;
}
//服务端:
void handle_cmd(struct Msg *in_cmd, struct Msg *out_cmd) {
out_cmd->cmd = in_cmd->cmd;
switch (in_cmd->cmd) {
case FTP_CMD_LS:
fp = popen(in_cmd->args, "r"); // popen创建一个管道,调用fork产生
//一个子进程来执行ls,然后返回ls的结果到管道
if (NULL != fp) {
fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
pclose(fp);
}
break;
default:
break;
}
}
二、要实现的功能
基本功能:
ls:显示服务端的文件
get:下载服务端的文件。
要点:1.分割Msg.args中get和文件名,fopen打开对应文件名的文件。
int split_string(char *in_str,char *out_str)
{
char *first;
char *_n;
first = strstr(in_str," ");
_n = strstr(in_str,"\n");
while(1){
if(NULL == first||*first =='\0'){
return -1;
}
first++;
if(*first !=' '){
break;
}
}
strcpy(out_str,first);
out_str[_n-first]='\0';
return 0;
}
2.计算文件长度,fread读取相应长度的内容到Msg.data。
long get_length(char *filename)
{
FILE *fp = fopen(filename,"r");
if(fp !=NULL){
if(fseek(fp,0,SEEK_END)<0){
log_write("fseek fialed");
return -1;
}
long length =ftell(fp);
fclose(fp);
return length;
}
return -1;
}
put:上传本地文件到服务端
客户端打开本地文件fread读到Msg.data传到服务端,服务端创建文件夹,fwrite将Msg.data写进文件。
quit:输入quit客户端和服务端都退出。
cd:切换目录
调用chdir函数
#unistd.h
int chdir(const char *path); //改变当前目录到path
高级功能:
用户名密码验证:
服务端用一个文件存储用户账号密码,客户端connect前,需输入账号密码,上传到服务端,如果memcmp与服务端的账号密码一致,则connect,反之阻止connect。
#include <string.h>
//函数原型
int memcmp(const void *str1, const void *str2, size_t n));
//参数
str1-- 指向内存块的指针。
str2-- 指向内存块的指针。
n-- 要被比较的字节数。
md5效验:
在文件上传下载前后分别记录文件的文件的md5,如果md5 memcmp()一致,则通过,反之删除文件,上传,下载失败。
void get_md5(char *filename,char *md5sum)
{
char cmd[64];
sprintf(cmd,"md5sum %s",filename);
char md5[64];
FILE *fp=popen(cmd,"r");
if(fp !=NULL){
int ret=fread(md5,1,sizeof(md5),fp);
log_write("fread ret %d,md5 %s\n",ret,md5);
pclose(fp);
}
sscanf(md5,"%s",md5sum);
}
在实现md5效验的时候我犯了一个错误:打开文件fopen后,在没有关闭文件fclose的情况下记录md5,导致md5上传前后不一致,
导致上传失败。所以,记得fclose关闭文件后再记录md5的值。
history:记录输入的指令
在客户端进行指令处理时,将输入的指令内容Msg.args记录到链表中,当用户输入”history“时,将链表内容传到Msg.data,
客户端打印记录。
void linklist_insert(struct linklist **head,char *cmd){
struct linklist *node=(struct linklist *)malloc(sizeof(struct linklist));
strcpy(node->cmd,cmd);
node->pnext=*head;
*head=node;
}
void linklist_get_cmd(struct linklist *head,char *out_buf){
struct linklist *p = head;
char buf[32];
while(p!=NULL){
//在命令后面加\n
sprintf(buf,"%s",p->cmd);
//拷贝命令
strcat(out_buf,buf);
//指向下一个节点
p = p->pnext;
}
}
完整程序:log.c
#include "log.h"
#include <stdio.h>
#include <stdarg.h>
FILE *g_log=NULL;
void log_create(const char *file){
g_log=fopen(file,"a+");
if(NULL==g_log){
printf("fopen log fail");
}
}
void log_destroy(){
fclose(g_log);
g_log=NULL;
}
void log_write(const char *format, ...){
va_list args;
va_start(args,format);
vfprintf(g_log,format,args);
va_end(args);
fflush(g_log);
}
log.h
#ifndef LOG_H
#define LOG_H
void log_create();
void log_destroy();
void log_write(const char *format, ...);
#endif
msg.h
#ifndef MSG_H
#define MSG_H
typedef enum FTP_CMD FTP_CMD;
enum FTP_CMD{
FTP_CMD_LS=0,
FTP_CMD_GET=1,
FTP_CMD_PUT=2,
FTP_CMD_QUIT=3,
FTP_CMD_CD=4,
FTP_CMD_AUTH=5,
FTP_CMD_HIST=6,
FTP_CMD_ERROR,
};
struct Msg{
FTP_CMD cmd;
char args[32];
char md5[64];
long data_lenth;
char data[5000];
};
struct Auth{
enum FTP_CMD cmd;
char username[32];
char password[32];
};
#endif
split.c
#include <stdio.h>
#include <string.h>
#include "split.h"
#include "log.h"
int split_string(char *in_str,char *out_str)
{
char *first;
char *_n;
first = strstr(in_str," ");
_n = strstr(in_str,"\n");
while(1){
if(NULL == first||*first =='\0'){
return -1;
}
first++;
if(*first !=' '){
break;
}
}
strcpy(out_str,first);
out_str[_n-first]='\0';
return 0;
}
long get_length(char *filename)
{
FILE *fp = fopen(filename,"r");
if(fp !=NULL){
if(fseek(fp,0,SEEK_END)<0){
log_write("fseek fialed");
return -1;
}
long length =ftell(fp);
fclose(fp);
return length;
}
return -1;
}
void get_md5(char *filename,char *md5sum)
{
char cmd[64];
sprintf(cmd,"md5sum %s",filename);
char md5[64];
FILE *fp=popen(cmd,"r");
if(fp !=NULL){
int ret=fread(md5,1,sizeof(md5),fp);
log_write("fread ret %d,md5 %s\n",ret,md5);
pclose(fp);
}
sscanf(md5,"%s",md5sum);
}
split.h
#ifndef SPLIT_H
#define SPLIT_H
int split_string(char *in_str,char *out_str);
long get_length(char *filename);
void get_md5(char *filename,char *md5sum);
#endif
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "log.h"
#include "msg.h"
#include "split.h"
#include "linklist.h"
int g_running;
void handle_cmd(struct Msg *in_cmd, struct Msg *out_cmd) {
static struct linklist *head =NULL;
FILE *fp = NULL;
int ret;
char filename[32];
char md5sum[64];
int length;
out_cmd->cmd = in_cmd->cmd;
switch (in_cmd->cmd) {
case FTP_CMD_LS:
linklist_insert(&head,in_cmd->args);
fp = popen(in_cmd->args, "r"); // popen创建一个管道,调用fork产生
//一个子进程来执行ls,然后返回ls的结果到管道
if (NULL != fp) {
ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
printf("%s", out_cmd->data);
log_write("fread ret %d,data %s\n", ret, out_cmd->data);
pclose(fp);
}
break;
case FTP_CMD_GET:
linklist_insert(&head,in_cmd->args);
split_string(in_cmd->args, filename);
length = get_length(filename);
printf("length = %d\n",length);
fp = fopen(filename, "r");
if (fp==NULL) {
perror("fopen\n");
exit(-1);
} else {
printf("fopen success\n");
}
if (NULL != fp) {
ret = fread(out_cmd->data, 1, length, fp);
out_cmd->data_lenth = ret;
log_write("fread ret %d,eof %d\n", ret, feof(fp));
fclose(fp);
}
get_md5(filename,out_cmd->md5);
break;
case FTP_CMD_PUT:
linklist_insert(&head,in_cmd->args);
filename[0]='+';
split_string(in_cmd->args,&filename[1]);
FILE *fp=fopen(filename,"w");
if(fp !=NULL){
int ret =fwrite(in_cmd->data,1,in_cmd->data_lenth,fp);
log_write("fwrite ret %d,filename %s,data_lenth %ld\n",
ret,filename,in_cmd->data_lenth);
fclose(fp);
}
get_md5(filename,md5sum);
if(memcmp(md5sum,in_cmd->md5,32)!=0){
printf("md5 different\nbefore md5:%s\n,after md5:%s\n",in_cmd->md5,md5sum);
remove(filename);
}else{
printf("md5 same\n");
}
break;
case FTP_CMD_QUIT:
linklist_insert(&head,in_cmd->args);
g_running=0;
log_write("g_running=0\n");
break;
case FTP_CMD_CD:
linklist_insert(&head,in_cmd->args);
if(split_string(in_cmd->args,filename)<0){
return;
}
ret = chdir(filename);
log_write("chdir ret:%d",ret);
break;
case FTP_CMD_HIST:
linklist_get_cmd(head,out_cmd->data);
break;
default:
break;
}
}
int main(int argc, char **argv) {
int s_fd;
int c_fd;
int ret;
char readbuf[128] = {0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr, 0, sizeof(struct sockaddr_in));
memset(&c_addr, 0, sizeof(struct sockaddr_in));
struct Msg *msg_send = NULL;
struct Msg *msg_recv = NULL;
msg_send = (struct Msg *)malloc(sizeof(struct Msg));
msg_recv = (struct Msg *)malloc(sizeof(struct Msg));
// 1.socket
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (s_fd == -1) {
perror("socket");
exit(-1);
}
int on = 1;
setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
// 2.bind
s_addr.sin_family = AF_INET;
s_addr.sin_port = 8889;
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
// 3.listen
listen(s_fd, 10);
// 4.accept
int clen = sizeof(struct sockaddr_in);
g_running=1;
while (g_running) {
log_create("server.txt");
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if (c_fd == -1) {
perror("accept");
}
printf("server connected\n");
//读取用户用户密码
struct Auth auth;
ret = recv(c_fd,&auth,sizeof(struct Auth),0);
log_write("%s %s\n",auth.username,auth.password);
//获取本地用户密码
struct Auth server;
FILE *fp=fopen("user","r");
if (fp !=NULL){
fscanf(fp,"%s %s",server.username,server.password);
log_write("server %s %s\n",server.username,server.password);
fclose(fp);
}
//校验用户密码
if(0!=memcmp(auth.username,server.username,strlen(server.username))||
0!=memcmp(auth.password,server.password,strlen(server.password))){
auth.cmd = FTP_CMD_ERROR;
log_write("auth failed\n");
}
ret = send(c_fd,&auth,sizeof(struct Auth),0);
log_write("auth failed\n");
if(FTP_CMD_ERROR==auth.cmd){
return -1;
}
while (g_running) {
ret = recv(c_fd, msg_recv, sizeof(struct Msg), 0);
log_write("recv %d\n", ret);
//处理客户端命令
memset(msg_send, 0, sizeof(struct Msg));
handle_cmd(msg_recv, msg_send);
//发送
ret = send(c_fd, msg_send, sizeof(struct Msg), 0);
log_write("send %d\n", ret);
}
log_destroy();
}
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "log.h"
#include "msg.h"
#include "split.h"
enum FTP_CMD get_cmd(char *buf, struct Msg *msg) {
char *ptr_args;
if (0 == memcmp(buf, "ls", 2)) {
return FTP_CMD_LS;
}
return FTP_CMD_ERROR;
}
int handle_user_input(struct Msg *msg_send) {
char buf[32];
int ret;
long length;
enum FTP_CMD cmd;
printf("please input\n");
// wait
fgets(buf, 32, stdin); // scanf遇到空格会被打断,所以改用fgets;
//判断buf的值是否含有ls,如果有cdm=0
if (0 == memcmp(buf, "ls", 2)) {
cmd = FTP_CMD_LS;
} else if (0 == memcmp(buf, "get", 3)) {
cmd = FTP_CMD_GET;
} else if (0 == memcmp(buf, "put", 3)) {
cmd = FTP_CMD_PUT;
char filename[32];
if (split_string(buf, filename) < 0) {
log_write("filename not find");
return -1;
}
length = get_length(filename);
if(length<0||length>sizeof(msg_send->data)){
log_write("get_length failed,length %ld\n",length);
return -1;
}
log_write("length = %d\n", length);
printf("%ld\n",length);
get_md5(filename,msg_send->md5);
FILE *fp = fopen(filename, "r");
if (NULL != fp) {
msg_send->data_lenth = fread(msg_send->data, 1, length, fp);
log_write("fread %d\n", msg_send->data_lenth);
printf("%ld\n",msg_send->data_lenth);
fclose(fp);
} else {
log_write("filename not find,%s", filename);
return -1;
}
}else if(0 == memcmp(buf,"quit",4)){
cmd=FTP_CMD_QUIT;
}else if(0 == memcmp(buf,"cd",2)){
cmd=FTP_CMD_CD;
}else if(0 == memcmp(buf,"history",7)){
cmd=FTP_CMD_HIST;
}else {
cmd = FTP_CMD_ERROR;
}
if (cmd == FTP_CMD_ERROR) {
return -1;
}
msg_send->cmd = cmd;
strcpy(msg_send->args, buf);
return 0;
}
int main(int argc, char **argv) {
struct Auth auth;
struct Msg *msg_send = NULL;
struct Msg *msg_recv = NULL;
msg_send = (struct Msg *)malloc(sizeof(struct Msg));
msg_recv = (struct Msg *)malloc(sizeof(struct Msg));
log_create("client.txt");
int ret;
int c_fd;
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
char md5sum[64];
// 1.socket
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if (c_fd == -1) {
perror("socket");
exit(-1);
}
// 2.connect
c_addr.sin_family = AF_INET;
c_addr.sin_port = 8889;
c_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in)) ==
-1) {
perror("connect");
exit(-1);
}
printf("username:");
scanf("%s",auth.username);
printf("password:");
scanf("%s",auth.password);
auth.cmd =FTP_CMD_AUTH;
//发送给客户端验证
ret=send(c_fd,&auth,sizeof(struct Auth),0);
log_write("send ret %d\n",ret);
//判断用户名密码是否正确
ret=recv(c_fd,&auth,sizeof(struct Auth),0);
if(FTP_CMD_ERROR == auth.cmd){
printf("username or password error\n");
return -1;
}
printf("welcome!\n");
getchar();
sleep(1);
while (1) {
if(handle_user_input(msg_send)<0){
continue;
}
//发送
ret = send(c_fd, msg_send, sizeof(struct Msg), 0);
log_write("send ret %d\n", ret);
//接收
ret = recv(c_fd, msg_recv, sizeof(struct Msg), 0);
log_write("recv ret %d\n", ret);
if (msg_recv->cmd == FTP_CMD_LS) {
printf("%s\n", msg_recv->data);
printf("----------------------\n");
} else if (msg_recv->cmd == FTP_CMD_GET) {
char filename[32];
filename[0]='_';
printf("1%s\n",msg_send->args);
split_string(msg_send->args,&filename[1]);
FILE *fp = fopen(filename, "w");
if (fp != NULL) {
ret = fwrite(msg_recv->data, 1, msg_recv->data_lenth, fp);
log_write("fwrite ret %d\n",ret);
fclose(fp);
}
get_md5(filename,md5sum);
if(memcmp(md5sum,msg_recv->md5,32)!=0){
log_write("md5 different\nmd5sum:%s\nmsg_recv->md5:%s\n",md5sum,msg_recv->md5);
printf("md5 diffrent,get failed\n");
remove(filename);
}else{
printf("get success\n");
}
printf("----------------------\n");
}else if (msg_recv->cmd ==FTP_CMD_PUT){
printf("put success\n");
printf("-----------------------\n");
}else if(msg_recv->cmd == FTP_CMD_QUIT){
printf("Good bye~~\n");
break;
} else if(msg_recv->cmd ==FTP_CMD_HIST){
printf("%s\n",msg_recv->data);
printf("-----------------------\n");
}
}
log_destroy();
return 0;
}