在linux下,开发一个局域网文件共享工具,其实并不局限于局域网,只要是有ip地址和端口号(默认是5168),就能进行文件共享。
功能:
- 客户端向服务端发送文件
./Client IP地址 send 文件名 //Client是编译后的文件名 自行更改
- 客户端从服务器下载文件
./Client IP地址 download 文件名 //注意这里的文件名是服务端的
- 客户端获取服务端的文件目录
./Client IP地址 ls
服务端启动方法
./Server //名字可自行更改
代码:
Client端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#define PORT 5168
#define MSGLEN 1024
int Connect(char *addrname)
{
int sockfd;
struct sockaddr_in sevrAddr;
struct hostent *host;
host = gethostbyname(addrname);
// 声明并初始化一个socket地址结构
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket error");
exit(-1);
}
else
printf("socket ok\n");
sevrAddr.sin_family = AF_INET;
sevrAddr.sin_port = htons(PORT);
sevrAddr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&sevrAddr.sin_zero, 8);
// 向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接
if (connect(sockfd, (struct sockaddr *)&sevrAddr, sizeof(struct sockaddr)) == -1)
{
perror("connect error");
exit(-1);
}
else
printf("connect ok\n");
return sockfd;
}
void file_send(char *addrname, char *option, char *filename)
{
int sockfd;
FILE *fp;
char readBuff[MSGLEN];
char recv_filename[50];
int len;
//获取套接字
sockfd = Connect(addrname);
//发送send命令到服务端
if (send(sockfd, option, sizeof(option), 0) < 0)
{
perror("send option error");
exit(-1);
}
sleep(1);
//发送文件名到服务端
bzero(&recv_filename, sizeof(recv_filename));
strcpy(recv_filename, filename);
if (send(sockfd, recv_filename, sizeof(recv_filename), 0) < 0)
{
perror("send filename error");
exit(-1);
}
sleep(1);
fp = fopen(filename, "rb");
if (fp != NULL)
{
printf("sending file");
bzero(&readBuff, MSGLEN);
while (1)
{
if ((len = fread(readBuff, 1, MSGLEN, fp)) > 0)
{
if (send(sockfd, readBuff, len, 0) < 0)
{
perror("send error");
exit(-1);
}
else
{
printf(".");
bzero(&readBuff, MSGLEN);
}
}
else if (len == 0)
{ //等于0表示文件已到末尾
printf("\nfile send success\n\n\n");
fclose(fp);
break;
}
else
{
perror("read error");
exit(-1);
}
}
}
else
{
printf("open file failed\n");
}
close(sockfd);
}
void file_dowload(char *addrname, char *option, char *filename)
{
int sockfd;
int dfp;
char dowBuff[MSGLEN];
char recv_filename[50];
int dow_len;
char message[50];
sockfd = Connect(addrname);
//发送dowload命令到服务端
if (send(sockfd, option, sizeof(option), 0) < 0)
{
perror("dowload option error");
exit(-1);
}
sleep(1);
//发送想要下载的文件名
bzero(&recv_filename, sizeof(recv_filename));
strcpy(recv_filename, filename);
if (send(sockfd, recv_filename, sizeof(recv_filename), 0) < 0)
{
perror("send filename error");
exit(-1);
}
sleep(1);
if (recv(sockfd, message, sizeof(message), 0) < 0) //收到服务器是否有该文件的信息
{
perror("receive message error");
exit(-1);
}
if (strcmp(message, "file no exists") == 0) //服务器没有该文件
{
printf("file doesn't exists in the server\n");
close(sockfd);
exit(-1);
}
else
{
//准备接收文件
dfp = open(filename, O_RDWR | O_CREAT, 777);
printf("%d\n", dfp);
int flags = 0;
bzero(&dowBuff, MSGLEN);
while ((dow_len = recv(sockfd, dowBuff, MSGLEN, 0)) > 0)
{
flags++;
if (flags == 1)
{
printf("dowload file start");
}
else
{
printf(".");
}
if (write(dfp, dowBuff, dow_len))
{
bzero(&dowBuff, MSGLEN);
}
else
{
perror("write error");
break;
}
}
if (flags == 0)
{
perror("dowload file error");
close(sockfd);
}
if (flags > 0)
{
printf("\ndowload file success\n");
close(sockfd);
}
}
}
void file_look(char *addrname, char *option)
{
int sockfd;
char recv_Buff[MSGLEN];
int ok; //通信状态标识
int sent; //收发字节数
sockfd = Connect(addrname);//初始化套接字并进行连接
//发送ls命令到服务端
if (send(sockfd, option, sizeof(option), 0) < 0)
{
perror("send option error");
exit(-1);
}
// sleep(1);
//接收服务端目录
recv(sockfd, &ok, sizeof(ok), 0); //接收服务端的状态
if (ok == -1)
{
puts("open path error");
close(sockfd);
}
else
{
// 显示服务器目录内容
printf("the filelist in server:\n");
bzero(recv_Buff, MSGLEN);
while (1)
{
recv(sockfd, &sent, sizeof(sent), 0);
if (sent == 0)
{
break;
}
recv(sockfd, recv_Buff, sent, 0);
recv_Buff[sent] = 0;
puts(recv_Buff);
}
close(sockfd);
}
}
void menu(char *addrname, char *option, char *filename)
{
if (strcmp(option, "send") == 0)
{
file_send(addrname, option, filename);
}
else if (strcmp(option, "download") == 0)
{
file_dowload(addrname, option, filename);
}
else if (strcmp(option, "ls") == 0)
{
file_look(addrname, option);
}
else
{
printf("option error\n");
}
}
int main(int argc, char **argv)
{
char addrname[32];
char option[20];
char filename[50];
if (argc == 4)
{
strcpy(addrname, argv[1]);
strcpy(option, argv[2]);
strcpy(filename, argv[3]);
menu(addrname, option, filename);
}
else if (argc == 3)
{
strcpy(addrname, argv[1]);
strcpy(option, argv[2]);
menu(addrname, option, NULL);
}
else
{
printf("Usage: ./client [hostname] [option] [filename] or ./client [hostname] [ls]");
exit(-1);
}
return 0;
}
Server端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 5168
#define MSGLEN 1024
int main(int argc, char **argv)
{
int severFd, clientFd; //服务端套接字,客户端套接字
int fp, flags; //文件描述符,文件读取标志
socklen_t addrlen; //int
addrlen = sizeof(struct sockaddr);
struct sockaddr_in severAddr, clientAddr; //socket地址结构
char recvBuff[MSGLEN]; //文件接收缓冲区
char sendBuff[MSGLEN]; //文件发送缓冲区
char listBuff[MSGLEN]; //文件列表发送缓冲区
char recv_filename[50]; //文件名接收缓冲区
char filename[50]; //文件名
char option[20]; //命令接收缓冲区
char t_message[50] = "file exists"; //文件存在
char f_message[50] = "file no exists"; //文件不存在
int recv_len; //文件接收长度
int send_len; //文件发送长度
int list_len; //文件列表发送长度
// 声明并初始化一个服务器端的socket地址结构
if ((severFd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("sockst error"); //perror()用来将上一个函数发生错误的原因输出到标准设备 ,错误原因依照全局变量errno 的值来决定要输出的字符串
exit(-1);
}
severAddr.sin_family = AF_INET; //地址家族,AF_INET代表TCP/IP
severAddr.sin_port = htons(PORT); //端口,将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian) 参数:16位无符号整数 返回值:TCP / IP网络字节顺序.
severAddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,htonl()将一个32位数从主机字节顺序转换成网络字节顺序
bzero(&severAddr.sin_zero, 8); //结构的其余的部分须置 0
// 绑定sseverFD和socket地址结构
//参数1:用socket()函数创建的文件描述符
//参数2:指向一个结构为sockaddr参数的指针
//参数3:结构的长度
if (bind(severFd, (struct sockaddr *)&severAddr, sizeof(struct sockaddr)) == -1)
{
perror("bind error");
exit(-1);
}
// socket监听
// 参数1:需要进入监听状态的套接字
// 参数2:请求队列的最大长度
// 当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误
if (listen(severFd, 10) == -1)
{
perror("listen error");
exit(-1);
}
while (1)
{
// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信
// accept函数会把连接到的客户端信息写到client_addr中 ,accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来
// 参数1:服务器端套接字
// 参数2:sockaddr_in 结构体变量
if ((clientFd = accept(severFd, (struct sockaddr *)&clientAddr, &addrlen)) == -1)
{
perror("accept error");
exit(-1);
}
printf("connect ip:%s port: %d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
//接收客户端命令
//bzero() 会将内存块(字符串)的前n个字节清零
bzero(&option, sizeof(option));
//recv先等待s的发送缓冲区的数据被协议传送完毕,recv函数仅仅是copy数据,真正的接收数据是协议来完成的
if (recv(clientFd, option, sizeof(option), 0) < 0)
{
perror("receive option error");
break;
}
//执行对应操作
if (strcmp(option, "send") == 0) //客户端想要发文件
{
//接收客户端发来的文件名
bzero(&recv_filename, sizeof(recv_filename));
if (recv(clientFd, recv_filename, sizeof(recv_filename), 0) < 0)
{
perror("receive filename error");
break;
}
strcpy(filename, recv_filename);
//循环接收数据
bzero(&recvBuff, MSGLEN);
fp = open(filename, O_RDWR | O_CREAT, 777); //创建文件
flags = 0;
while ((recv_len = recv(clientFd, recvBuff, MSGLEN, 0)) > 0)
{
flags++;
if (flags == 1)
{
printf("receive file start");
}
else
{
printf(".");
}
if (write(fp, recvBuff, recv_len))
{
bzero(&recvBuff, MSGLEN);
}
else
{
perror("write error");
break;
}
}
if (flags == 0) //未接收到文件数据
{
perror("receive file error");
}
if (flags > 0)
{
printf("\nreceive file success\n\n");
}
close(clientFd);
}
else if (strcmp(option, "download") == 0) //客户端想要下载文件
{
//接收客户端发来的文件名
bzero(&recv_filename, sizeof(recv_filename));
if (recv(clientFd, recv_filename, sizeof(recv_filename), 0) < 0)
{
perror("receive filename error");
break;
}
bzero(&filename, sizeof(filename));
strcpy(filename, recv_filename);
FILE *sfp;
sfp = fopen(filename, "rb");
if (sfp != NULL)
{
//send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里
if (send(clientFd, t_message, sizeof(t_message), 0) < 0)
{
perror("send t_message error");
exit(1);
}
sleep(1);
printf("sending file");
bzero(&sendBuff, MSGLEN);
while (1)
{
if ((send_len = fread(sendBuff, 1, MSGLEN, sfp)) > 0)
{
if (send(clientFd, sendBuff, send_len, 0) < 0)
{
perror("send error");
exit(-1);
}
else
{
printf("...");
bzero(&sendBuff, MSGLEN);
}
}
else if (send_len == 0) //等于0表示文件已到末尾
{
printf("\nfile send success\n\n");
break;
}
else
{
perror("read error");
exit(-1);
}
}
fclose(sfp);
}
else //文件不存在
{
printf("no such file\n\n");
if (send(clientFd, f_message, sizeof(f_message), 0) < 0) //发送文件不存在的信息
{
perror("send f_message error");
exit(-1);
}
sleep(1);
}
close(clientFd);
sleep(1);
}
else if (strcmp(option, "ls") == 0)
{
int ok; //收发标识
int sent; //收发字节数
DIR *dir;
struct dirent *ptr;
char data_send[MSGLEN];
char path[5] = ".";
if ((dir = opendir(path)) == NULL)
{
ok = -1;
send(clientFd, &ok, sizeof(ok), 0); //打开路径失败则返回ok=-1
close(clientFd);
}
else
{
ok = 1;
send(clientFd, &ok, sizeof(ok), 0); //打开路径成功则返回ok=1
// 逐行内容读取并发送
while (1)
{
ptr = readdir(dir);
if (ptr == NULL)
sent = 0;
else
sent = strlen(ptr->d_name);
send(clientFd, &sent, sizeof(sent), 0);
if (sent == 0)
break;
strcpy(data_send, ptr->d_name);
send(clientFd, data_send, sent, 0);
}
closedir(dir); //关闭文
printf("send filelist success\n\n");
close(clientFd);
}
}
}
close(severFd);
return 0;
}
获取文件目录结果
代码有详细的注释,这里就不多做解释了
有一点就是,这个逻辑是:执行一次命令客户端就结束退出(因为后期跟SpringBoot结合,所以这个逻辑是这样)。如果看懂了代码可以自己修改逻辑,不过我这还有另外一个版本,在客户端给了一个菜单选项,你可以自行决定是否退出或者继续共享文件。放在下载了,感兴趣可以看看。