|
上一个项目写了个POS机下面的广告程序,其中需要先写一个POS机下的FTP客户端程序,这个程序很让我郁闷。首先,对方公司提供了一个FTP操作的中间层,所以与其说写,不如说是组合代码;其次,让我不爽的是,对方中间层的API有误,他们还叫我跟着他们瞎折腾测试这中间层的功能。总之就是很不爽,因为你明明知道他们是在瞎折腾,但是你又不得不配合他们,不配合他们,他们就有理由说我不合作,项目拖延是我的错,我很不爽。
这一次我自己在Linux下写了一个简单的FTP客户端,支持的操作只有那些最常用的,以后如果有必要,这个程序可以移植到POS机下面。这个程序已经通过简单的一些测试,目前实现的操作有:ls, dir, pwd, cd, ascii, binary, passive, get, put, delete, system, mkdir, rmdir, quit, bye. 另外,我用的了steven那本《UNIX网络编程卷1》的动态库,因为里面包含了一些API的封装,我无须理会这些API的返回码,所以程序需要包含unp.h这个头文件和链接libunp.so这个动态库。
这个FTP客户端程序主要分两个模块,一个是ftp_socket.c,负责socket方面的操作,另外一个是ftp.c,负责FTP的操作实现。有参考了网上开源的项目中PORT和PASV部分的处理,其他其实都挺简单的。核心代码不到900行,其中有一些地方没考虑得很全面,一些处理得不够优雅,以后慢慢再修改,在git上已经给程序打了一个tag。直接贴代码了:
typedef.h : 声明和定义了一些类型和操作
代码
#ifndef TYPEDEF_H
#define TYPEEF_H
typedef enum _Ret
{
FTP_RET_OK,
FTP_RET_OOM,
FTP_RET_STOP,
FTP_RET_INVALID_PARAMS,
FTP_RET_FAIL
} FTP_Ret;
#define DECLS_BEGIN
#define DECLS_END
#define and &&
#define or ||
#define return_if_fail(p) if (!(p)) \
{printf("%s:%d Warning: "#p" failed.\n",\
__func__, __LINE__); return; }
#define return_val_if_fail(p, ret) if (!(p)) \
{printf("%s:%d Warning: "#p" failed.\n", \
__func__, __LINE__); return (ret); }
#define SAFE_FREE(p) if (p != NULL) {free(p); p = NULL;}
#endif
ftp_socket.
#include "typedef.h"
#ifndef FTP_SOCKET_H
#define FTP_SOCKET_H
DECLS_BEGIN
#define INVALID_SOCKET (~0)
#define FD_READ_BIT 0
#define FD_READ (1 transMode = FTP_MODE_PASV;
memset(ftpObj->replyString, 0x00, sizeof(ftpObj->replyString));
return FTP_RET_OK;
}
static FTP_Ret ftp_get_reply(FTP_Obj *ftpObj)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(ftpObj->commandChannel != INVALID_SOCKET, FTP_RET_FAIL);
char recvBuf[MAX_RECV_SIZE] = {0x00};
ftpObj->replyCode = -1;
memset(ftpObj->replyString, 0x00, sizeof(ftpObj->replyString));
while (ftp_socket_select(ftpObj->commandChannel, FD_READ, ftpObj->secTimeOut) > 0)
{
ftp_set_nonblock(ftpObj->commandChannel);
if (ftp_recv_command(ftpObj->commandChannel, recvBuf, MAX_RECV_SIZE) replyString, recvBuf, strlen(recvBuf));
return FTP_RET_OK;
}
}
return FTP_RET_FAIL;
}
static FTP_Ret ftp_send_and_recv_reply(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
ftp_send_command(ftpObj->commandChannel, command);
memset(ftpObj->replyString, 0x00, sizeof(ftpObj->replyString));
return ftp_get_reply(ftpObj);
}
static FTP_Obj *ftp_connect_server(const char *ipAddress, const int port)
{
return_val_if_fail(ipAddress != NULL, NULL);
FTP_Obj *ftpObj = (FTP_Obj *)malloc(sizeof(FTP_Obj));
return_val_if_fail(ftpObj != NULL, NULL);
ftp_init_obj(ftpObj);
ftpObj->commandChannel = ftp_socket_create();
if (ftpObj->commandChannel == INVALID_SOCKET)
{
fprintf(stderr, "creating socket failed.\n");
goto error;
}
else
{
int ret = ftp_socket_connect(ftpObj->commandChannel, ipAddress, port);
if (ret != 0)
{
fprintf(stderr, "connect to %s failed.\n", ipAddress);
goto error;
}
else
{
fprintf(stdout, "connected to %s.\n", ipAddress);
if (ftp_get_reply(ftpObj) == FTP_RET_OK)
{
return ftpObj;
}
else
{
goto error;
}
}
}
error:
SAFE_FREE(ftpObj);
return NULL;
}
static void ftp_login_enter_name(char *userName, const int maxSize, const char *ipAddress)
{
char enterNamePrompt[MAX_PROMPT_SIZE] = {0x00};
sprintf(enterNamePrompt, "Name (%s): ", ipAddress);
printf("%s", enterNamePrompt);
Fgets(userName, maxSize, stdin);
userName[strlen(userName) - 1] = '\0';
}
static void ftp_login_enter_password(char *userPassword, const int maxSize)
{
char enterPwPrompt[MAX_PROMPT_SIZE] = {0x00};
sprintf(enterPwPrompt, "Password: ");
printf("%s", enterPwPrompt);
Fgets(userPassword, maxSize, stdin);
userPassword[strlen(userPassword) - 1] = '\0';
}
static FTP_Ret ftp_login(FTP_Obj *ftpObj, const char *ipAddress)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(ipAddress != NULL, FTP_RET_INVALID_PARAMS);
char command[COMMAND_SIZE];
char userName[20] = {0x00};
char userPassword[20] = {0x00};
ftp_login_enter_name(userName, 20, ipAddress);
memset(command, 0x00, sizeof(command));
sprintf(command, "USER %s", userName);
if (ftp_send_and_recv_reply(ftpObj, command) == FTP_RET_OK)
{
if (strncmp(ftpObj->replyString, "331", 3) != 0)
{
return FTP_RET_FAIL;
}
ftp_login_enter_password(userPassword, 20);
memset(command, 0x00, sizeof(command));
sprintf(command, "PASS %s", userPassword);
if (ftp_send_and_recv_reply(ftpObj, command) == FTP_RET_OK)
{
if (strncmp(ftpObj->replyString, "230", 3) != 0)
{
return FTP_RET_FAIL;
}
}
}
else
{
return FTP_RET_FAIL;
}
return FTP_RET_OK;
}
static FTP_Ret ftp_syst(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, "SYST");
}
static FTP_Ret ftp_set_transfer_type(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static FTP_Ret ftp_set_transfer_mode(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static bool ftp_pasv(FTP_Obj *ftpObj)
{
return_val_if_fail(ftpObj != NULL, false);
char pasv[32] = {0x00};
char hostAddr[16] = {0x00};
unsigned int addr_1, addr_2, addr_3, addr_4, port_1, port_2;
ftpObj->dataChannel = ftp_socket_create();
if (ftp_send_and_recv_reply(ftpObj, "PASV") != FTP_RET_OK)
{
return false;
}
strcpy(pasv, strchr(ftpObj->replyString, '(') );
sscanf(pasv, "(%d, %d, %d, %d, %d, %d)",
&addr_1, &addr_2, &addr_3, &addr_4, &port_1, &port_2);
sprintf(hostAddr, "%d.%d.%d.%d", addr_1, addr_2, addr_3, addr_4);
if (ftp_socket_connect(ftpObj->dataChannel, hostAddr, (port_1 commandChannel, (struct sockaddr *)&dataAddr, &len) != 0)
{
return false;
}
ftpObj->dataChannel = ftp_socket_create();
if (ftpObj->dataChannel == INVALID_SOCKET)
{
return false;
}
if (bind(ftpObj->dataChannel, (struct sockaddr *)&dataAddr, len) != 0)
{
return false;
}
if (getsockname(ftpObj->dataChannel, (struct sockaddr *)&dataAddr, &len) != 0)
{
return false;
}
if (ftp_socket_listen(ftpObj->dataChannel, 1) != 0)
{
return false;
}
addr = (char *)&dataAddr.sin_addr;
port = (char *)&dataAddr.sin_port;
sprintf(command, "PORT %d, %d, %d, %d, %d, %d",
UC(addr[0]), UC(addr[1]), UC(addr[2]), UC(addr[3]),
UC(port[0]), UC(port[1]) );
if (ftp_send_and_recv_reply(ftpObj, command) != FTP_RET_OK)
{
return false;
}
return true;
}
static bool ftp_init_data_channel(FTP_Obj *ftpObj)
{
return_val_if_fail(ftpObj != NULL, false);
if (ftpObj->dataChannel != INVALID_SOCKET)
{
ftp_socket_close(ftpObj->dataChannel);
}
if (ftpObj->transMode == FTP_MODE_PASV)
{
return ftp_pasv(ftpObj);
}
else
{
return ftp_port(ftpObj);
}
return false;
}
static bool ftp_build_data_channel(FTP_Obj *ftpObj)
{
return_val_if_fail(ftpObj != NULL, false);
if (ftpObj->transMode == FTP_MODE_PASV)
{
return true;
}
if (ftp_socket_select(ftpObj->dataChannel, FD_ACCEPT, ftpObj->secTimeOut) dataChannel);
if (socketHandle == INVALID_SOCKET)
{
return false;
}
else
{
ftp_socket_close(ftpObj->dataChannel);
ftpObj->dataChannel = socketHandle;
ftp_set_nonblock(ftpObj->dataChannel); // set nonblock
return true;
}
return false;
}
#define MAX_RECV_DATA_SIZE 1024 * 100
static char* ftp_recv_data_channnel(FTP_Obj *ftpObj, const char *command)
{
if (ftp_init_data_channel(ftpObj) != true)
{
ftp_socket_close(ftpObj->dataChannel);
return NULL;
}
if (ftp_send_and_recv_reply(ftpObj, command) != FTP_RET_OK)
{
return NULL;
}
if (ftp_build_data_channel(ftpObj) != true)
{
return NULL;
}
char *recvBuf = (char *)malloc(sizeof(char) * MAX_RECV_DATA_SIZE);
if (recvBuf == NULL)
{
return NULL;
}
memset(recvBuf, 0x00, MAX_RECV_DATA_SIZE);
if (ftp_recv_command(ftpObj->dataChannel, recvBuf, MAX_RECV_DATA_SIZE) < 0)
{
return NULL;
}
return recvBuf;
}
static FTP_Ret ftp_list(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
char *recvBuf = ftp_recv_data_channnel(ftpObj, command);
if (recvBuf != NULL)
{
printf("%s", recvBuf);
SAFE_FREE(recvBuf);
ftp_socket_close(ftpObj->dataChannel);
if (ftp_get_reply(ftpObj) == FTP_RET_OK)
{
return FTP_RET_OK;
}
}
return FTP_RET_FAIL;
}
static long ftp_get_file_size(const char *replyString)
{
return_val_if_fail(replyString != NULL, -1);
char sizeBuf[10] = {0x00};
char *ptr = strchr(replyString, '(');
int index;
for (index = 0, ptr += 1; index < 10; ++index, ++ptr)
{
if (*ptr != ' ')
{
sizeBuf[index] = *ptr;
}
else
{
break;
}
}
return atol(sizeBuf);
}
static bool ftp_get_file_name(const char *replyString, char *fileName)
{
return_val_if_fail(replyString != NULL, -1);
char *ptr = strstr(replyString, "for");
if (ptr != NULL)
{
int index;
// (ptr += 4) : ignore the "for " substring
for (index = 0, ptr += 4; *ptr != '\0'; ++ptr, ++index)
{
if (*ptr != ' ')
{
fileName[index] = *ptr;
}
else
{
return true;
}
}
}
return false;
}
static FTP_Ret ftp_get_file(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
char *recvBuf = ftp_recv_data_channnel(ftpObj, command);
if (recvBuf != NULL)
{
char fileName[256] = {0x00};
long fileSize = ftp_get_file_size(ftpObj->replyString);
if (ftp_get_file_name(ftpObj->replyString, fileName) == true)
{
int fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP);
if (fileSize != 0)
{
long writeSize = write(fd, recvBuf, fileSize);
if (writeSize != fileSize)
{
return FTP_RET_FAIL;
}
}
close(fd);
}
SAFE_FREE(recvBuf);
ftp_socket_close(ftpObj->dataChannel);
if (ftp_get_reply(ftpObj) == FTP_RET_OK)
{
return FTP_RET_OK;
}
}
return FTP_RET_FAIL;
}
static bool ftp_put_file_name(const char *command, char *fileName)
{
char *ptr = strchr(command, ' ');
if (ptr != NULL)
{
int index = 0;
for (ptr += 1; *ptr != '\0'; ++index, ++ptr)
{
fileName[index] = *ptr;
}
return true;
}
return false;
}
static long ftp_put_file_size(const char *fileName)
{
struct stat buf;
if (stat(fileName, &buf) == -1)
{
return -1;
}
return buf.st_size;
}
static FTP_Ret ftp_put_file(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
if (ftpObj->commandChannel == INVALID_SOCKET)
{
return FTP_RET_FAIL;
}
if (ftp_init_data_channel(ftpObj) != true)
{
ftp_socket_close(ftpObj->dataChannel);
return FTP_RET_FAIL;
}
if (ftp_send_and_recv_reply(ftpObj, command) != FTP_RET_OK)
{
return FTP_RET_FAIL;
}
if (ftp_build_data_channel(ftpObj) != true)
{
return FTP_RET_FAIL;
}
char fileName[256] = {0x00};
if (ftp_put_file_name(command, fileName) == true)
{
long size = ftp_put_file_size(fileName);
printf("----->put: %s: %ld\n", fileName, size);
int fd = open(fileName, O_RDONLY);
if (fd != -1)
{
char *recvBuf = (char *)malloc(sizeof(char) * MAX_RECV_DATA_SIZE);
if (recvBuf != NULL)
{
ssize_t readSize = read(fd, recvBuf, size);
if (readSize dataChannel, recvBuf, readSize) != -1)
{
close(fd);
ftp_socket_close(ftpObj->dataChannel);
}
return ftp_get_reply(ftpObj);
}
}
}
return FTP_RET_FAIL;
}
static FTP_Ret ftp_delete_file(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static FTP_Ret ftp_make_directory(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static FTP_Ret ftp_remove_directory(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static FTP_Ret ftp_change_directory(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, command);
}
static FTP_Ret ftp_pwd(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
return ftp_send_and_recv_reply(ftpObj, "PWD");
}
static FTP_Ret ftp_close(FTP_Obj *ftpObj, const char *command)
{
return_val_if_fail(ftpObj != NULL, FTP_RET_INVALID_PARAMS);
return_val_if_fail(command != NULL, FTP_RET_INVALID_PARAMS);
if (ftpObj->commandChannel != INVALID_SOCKET)
{
FTP_Ret ret = ftp_send_and_recv_reply(ftpObj, command);
if (ret == FTP_RET_OK)
{
ftp_socket_close(ftpObj->commandChannel);
}
else
{
return FTP_RET_FAIL;
}
}
if (ftpObj->dataChannel != INVALID_SOCKET)
{
ftp_socket_close(ftpObj->dataChannel);
}
SAFE_FREE(ftpObj);
return FTP_RET_OK;
}
static bool ftp_command_filter(const char *command, char *filter)
{
int index = 0;
for ( ; *command != '\0'; ++index, ++command)
{
if (*command != ' ')
{
filter[index] = *command;
}
else
{
return true;
}
}
return false;
}
static bool ftp_replay_command(char *sendCommand, const char *commandName)
{
char temp[COMMAND_SIZE] = {0x00};
char *ptr = strchr(sendCommand, ' ');
if (ptr == NULL)
{
strcpy(sendCommand, commandName);
return true;
}
strcpy(temp, commandName);
strcat(temp, ptr);
if (strlen(temp) = 0)
{
ftp_replay_command(enterCommand, replace_command[index]);
printf("-------->%s\n", enterCommand);
ftp_command_func[index](ftpObj, enterCommand);
}
if (strcmp(enterCommand, "QUIT") == 0)
{
break;
}
else if (strcmp(enterCommand, "") == 0)
{
continue;
}
}
}
return FTP_RET_OK;
}
main.c:
main.c
#include "ftp.h"
#include "unp.h"
#define DEBUG 0
#define PORT 21
static void ftp_test(const char *ipAddress)
{
FTP_entry(ipAddress, PORT);
return;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: ./ftp_client \n");
return -1;
}
ftp_test(argv[1]);
return 0;
}
最后给了Makefile:
Makefile
CC=gcc
CFILES=main.c ftp.c ftp_socket.c
OBJ+=$(CFILES:.c=.o)
EXE=ftp_client
all: $(EXE)
$(EXE) : $(OBJ) $(LIBFILES)
$(CC) -Wall $(CFILES:.c=.o) -o $@ -lunp -lcurses
clean:
rm -f *.o *.gdb $(EXE)
.SUFFIXES: .cpp .c .o
.c.o:
$(CC) -Wall -o $*.o -c $(CCFLAGS) $*.c
END |
|