项目介绍
模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。
项目功能介绍:
均有服务器和客户端代码,基于TCP写的。
在同一路径下,将客户端可执行代码复制到其他的路径下,接下来在不同的路径下运行服务器和客户端。
相当于另外一台电脑在访问服务器。
客户端和服务器链接成功后出现以下提示:四个功能
***************list************** //列出服务器所在目录下的文件名(除目录不显示)
***********put filename********** //上传一个文件
***********get filename********** //从服务器所在路径下载文件
**************quit*************** //退出(可只退出客户端,服务器等待下一个客户端链接)
FTP客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
char buf[128] = {};
//客户端函数声明
void putfileC(char *buf, int sockfd); //客户端上传文件到服务器功能函数
void getfileC(char *buf, int sockfd); //客户端从服务器下载文件功能函数
void list(int sockfd);
int main(int argc, char const *argv[])
{
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err:");
return -1;
}
// 2.填充结构体(ipv4)
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 协议族ipv4
addr.sin_port = htons(atoi(argv[2])); // 端口号(网络字节序)
addr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)
// 3.连接
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect err");
return -1;
}
//4.发送
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
{
buf[strlen(buf) - 1] = '\0';
}
if (strcmp(buf, "quit") == 0)
{
break;
}
if (strcmp(buf, "list") == 0)
{
send(sockfd, buf, sizeof(buf), 0);
list(sockfd);
}
if (strncmp(buf, "put ", 4) == 0)
{
send(sockfd, buf, sizeof(buf), 0);
putfileC(buf, sockfd);
printf("put ok\n");
}
if (strncmp(buf, "get ", 4) == 0)
{
send(sockfd, buf, sizeof(buf), 0);
getfileC(buf, sockfd);
printf("get ok\n");
}
}
//5.关闭文件
close(sockfd);
return 0;
}
void list(int sockfd)
{
while (1)
{
recv(sockfd, buf, sizeof(buf), 0);
if (strcmp(buf, "end") == 0)
{
break;
}
else
printf("%s ", buf);
}
printf("\n");
return;
}
void putfileC(char *buf, int sockfd)
{
int fd = open(buf + 4, O_RDONLY);
if (fd < 0)
{
perror("fd client err");
return;
}
ssize_t ret;
while (1)
{
ret = read(fd, buf, sizeof(buf));
if (ret == 0)
{
break;
}
send(sockfd, buf, sizeof(buf), 0);
memset(buf, 0, 128);
}
send(sockfd, "end", sizeof(buf), 0);
close(fd);
return;
}
void getfileC(char *buf, int sockfd)
{
int fd = open(buf + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("fd err");
return;
}
ssize_t ret;
while (1)
{
memset(buf, 0, 128);
ret = read(sockfd, buf, sizeof(buf));
if (strcmp(buf, "end") == 0)
{
break;
}
write(fd, buf, strlen(buf));
}
close(fd);
return;
}
FTP服务器
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
char buf[128] = {};
//服务器函数声明
void menu(); //显示目录
void list(int acceptfd); //列出服务器当前路径的文件名
void putfileS(char *buf, int acceptfd); //服务器接受客户端上传文件功能函数
void getfileS(char *buf, int acceptfd); //服务器下载文件到客户端功能函数
int main(int argc, char const *argv[])
{
//避免少输,出现段错误
if (argc != 2)
{
printf("please input %s ip port\n", argv[0]);
return -1;
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err:");
return -1;
}
// 2.填充结构体(ipv4)
struct sockaddr_in addr, caddr; //客户端连接的时候会自己填充信息,只需给它个空间
addr.sin_family = AF_INET; // 协议族ipv4
addr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
addr.sin_addr.s_addr = INADDR_ANY; // ip地址(网络字节序)
// addr.sin_addr.s_addr = inet_addr("0.0.0.0");
// 3.绑定
int ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("bind err:");
return -1;
}
// 4.监听
if (listen(sockfd, 5) < 0)
{
perror("listen err");
return -1;
}
//5.循环等待连接
socklen_t len = sizeof(caddr);
while (1)
{
//有客户端连接就显示目录
// 5.等待连接
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accpet err:");
return -1;
}
printf("acceptfd = %d\n", acceptfd);
printf("client: ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
// 6.接收
menu();
while (1)
{
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret == -1)
{
perror("recv err:");
return -1;
}
else if (ret == 0)
{
printf("client exit\n");
break;
}
else
{
if (strcmp(buf, "list") == 0)
{
list(acceptfd);
}
if (strncmp(buf, "put ", 4) == 0)
{
putfileS(buf, acceptfd);
printf("put end\n");
}
if (strncmp(buf, "get ", 4) == 0)
{
getfileS(buf, acceptfd);
printf("get end\n");
}
}
}
// 7.关闭
close(acceptfd);
}
close(sockfd);
return 0;
}
void menu() //显示目录
{
printf("***************list**************\n");
printf("***********put filename**********\n");
printf("***********get filename**********\n");
printf("**************quit***************\n");
}
//列出服务器所在目录下的文件名(除目录不显示)
void list(int acceptfd)
{
DIR *dirp;
struct dirent *d;
dirp = opendir("./");
if (NULL == dirp)
{
perror("opendir err");
return;
}
struct stat st;
while ((d = readdir(dirp)) != NULL)
{
if (stat(d->d_name, &st) < 0)
{
perror("stat err");
return;
}
if ((st.st_mode & S_IFMT) == S_IFREG)
{
send(acceptfd, d->d_name, sizeof(d->d_name), 0);
}
}
send(acceptfd, "end", sizeof(buf), 0);
}
//服务器接受客户端上传文件功能函数
void putfileS(char *buf, int acceptfd)
{
int fd = open(buf + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("fd server err");
return;
}
ssize_t ret;
while (1)
{
memset(buf, 0, 128);
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (strcmp(buf, "end") == 0)
{
break;
}
write(fd, buf, strlen(buf));
}
close(fd);
return;
}
//服务器下载文件到客户端功能函数
void getfileS(char *buf, int acceptfd)
{
int fd = open(buf + 4, O_RDONLY);
if (fd < 0)
{
perror("fd err");
return;
}
ssize_t ret;
while (1)
{
ret = read(fd, buf, sizeof(buf));
if (ret == 0)
{
break;
}
send(acceptfd, buf, sizeof(buf), 0);
memset(buf, 0, 128);
}
send(acceptfd, "end", sizeof(buf),0);
close(fd);
return;
}
问题
函数参数时把数组名传进去了,导致计算数组长度时报错,原因就是数组名被当作函数参数时会被降级为指针。
【C语言篇】数组作为函数参数_c语言数组作为函数参数-优快云博客
c++数组传递参数与返回_function cannot return function type-优快云博客
函数使用数组的报错_sizeof' on array function parameter 'a' will retur-优快云博客
粘包问题: 上传或下载都出现过服务器或客户端卡主现象,原因就是粘包问题没有解决,导致判断结束的"end"包与数据包粘在一起,判断条件不能满足跳不出循环一直卡主。目前我能解决的就是加个sleep函数先让发送或者接收数据的一方睡眠个几秒,或者收和发长度一致。