一、项目实现功能
-
在客户端对服务端的操作:
1.获取服务器的文件(get)
2.查看服务器当前路径下的所有文件(ls)
3.进入服务器的某个路径(cd)
4.上传文件到服务器(put)
5.查看服务器当前在那个文件夹(pwd)
6.退出 (quit)
-
在客户端本地的功能实现:
1.查看客户端本地当前路径有哪些文件(lls)
2.进入客户端的某个路径(lcd)
3.查看客户端当前在那个文件夹下(lpwd)
4.退出连接(quit)
二、项目实现思路
1.创建套接字(socket)
2.将socket与IP地址和端口绑定(bind)
3.监听被绑定的端口(listen)
4.接收连接请求(accept)
5.当有客户端接入时创建子进程对接并处理客户端发来的请求
1.创建套接字(socket)
2.连接指定计算机的端口(connect)
3.接收输入向socket中写入信息(write)
4.获取客户端发送的内容像ls,pwd,get指令,需要进行(read)
ls与pwd:调用popen
函数,执行命令,并获取执行命令后的输出内容,将内容发回到客户端
cd:将cd指令发送到服务器,在服务器端调用chdir
实现路径的切换
get:客户端获取服务器的某个文件,服务器首先通过access
函数判断文件是否存在,存在则将文件的内容发送到客户端,客户端创建文件,并将服务器发送的内容保存至文件中,实现get指令。
put:客户端向服务器发送文件,客户端首先通过access
函数判断文件是否存在,存在则将文件的内容发送到服务器,服务器创建文件,并将客户端发送的内容保存至文件中,实现put指令。
lls,lpwd:调用system
函数即可。
lcd:在客户端直接调用chdir
函数即可
三、项目用到的函数
1.access函数(判断文件是否存在的函数)
int access(const char *pathname, int mode);
- 函数说明:
判断参数中的文件民是否存在
- 参数:
pathname:需要检测的文件路径名
mode:需要测试的操作模式
R_OK
测试读许可权
W_OK
测试写许可权
X_OK
测试执行许可权
F_OK
测试文件是否存在
- 返回值:成功执行时,返回0,失败返回-1,errno被设为以下的某个值
2.chdir函数(进入某个文件夹的函数)
int chdir(const char *path);
- 函数说明:切换到参数所设置的路径
- 参数:所指代的工作目录(即将要进入的工作目录)
- 返回值:成功返回0,失败返回-1,errno为错误代码
3.fgets函数(可以获取空格,且较为安全)
char *fgets(char *s, int size, FILE *stream);
- 函数说明:
虽然用 gets()
时有空格也可以直接输入,但是 gets()
有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()
。但是调用次函数会把回车\n也作为字符串的一部分,因此需要将去掉换行符删掉:
if(msg.cmd[strlen(msg.cmd) - 1] == '\n'){
msg.cmd[strlen(msg.cmd) - 1] = '\0';
}
- 参数:
s:代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名
size:代表的是读取字符串的长度
stream:表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取
- 返回值:返回该数组的指针(首地址),如果读完,则返回空指针NULL
四、代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>
char *get_cmd_dir(char *cmd)
{
char *p=NULL;
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}//用此函数将用户输入的指令中的文件名分割开来 如cd xx,将得到xx
int get_cmd_type(char *cmd)
{
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"get")!=NULL) return GET;
if(strstr(cmd,"cd")!=NULL) return CD;
if(strstr(cmd,"put")!=NULL) return PUT;
return -1;
}//用此函数将用户输入的指令对应与之相应的宏,便于之后对指令进行判断处理
void message_handler(struct Msg msg,int fd)//指令处理函数
{
int ret;
char *dir;
char *fileName;
int fdFile;
int getSize;
char *dataBuf;
printf("get cmd: %s\n",msg.cmd);//打印客户端传来的指令
ret =get_cmd_type(msg.cmd);
switch (ret){
case LS:
case PWD:
msg.type=0;
//用popen()函数,获取运行的结果,可以存放在字符串当
FILE *fp=popen(msg.cmd,"r");
fread(msg.data,sizeof(msg.data),1,fp);//用popen函数打开的文件可以用fread函数读到相应的地方
write(fd,&msg,sizeof(msg));//将数据发回到客户端
pclose(fp);
break;
case CD:
msg.type=1;
dir=get_cmd_dir(msg.cmd);//首先需要获取cd 后的文件名字,这里勇封装函数获取文件名字
chdir(dir);//调用chdir指令,相当于cd
break;
case GET:
fileName=get_cmd_dir(msg.cmd);//获取需要拷贝的文件名
if(access(fileName,F_OK)==-1){//用access 函数判断文件是否存在,若不存在返回-1
strcpy(msg.data,"This File is not exist");
write(fd,&msg,sizeof(msg));//将不存在的提升发回客户端
}else{
msg.type=DOFILE;//若存在,则执行对文件的拷贝
fdFile=open(fileName,O_RDWR);//存在打开文件
getSize = lseek(fdFile,0,SEEK_END);
lseek(fdFile,0,SEEK_SET);
read(fdFile,msg.secondBuf,getSize);//把文件内容写入secondBuf中
close(fdFile);
write(fd,&msg,sizeof(msg));//将文件内容存放在结构体中并发给客户端
}
break;
case PUT://客户端上传文件到服务器,客户端函数与服务器的GET函数类似,将要上传的文件内容拷贝到结构体中
fdFile=open(get_cmd_dir(msg.cmd),O_RDWR|O_CREAT,0666);//服务器首先创建并打开要上出的文件
write(fdFile,msg.secondBuf,sizeof(msg.secondBuf));//然后将客户端上传给服务器文件里的内容拷贝到新创建的文件中
close(fdFile);
break;
case QUIT://客户端发送quit指令,服务器得到指令后打印客户端退出
printf("client quit\n");
exit(-1);
}
}
int main(int argc,char * argv[])
{
int socket_fd;
int connect_fd;
int nread;
int clen;//struct sockaddr_in 结构体长度
char readBuf[128];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
struct Msg message;
if(argc != 2){
printf("输入端口号");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr));
memset(&c_addr,0,sizeof(struct sockaddr));
memset(&message,0,sizeof(struct Msg));
socket_fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(socket_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[1]));
inet_aton(" 127.0.0.1",&(s_addr.sin_addr));
bind(socket_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));//为套接字添加信息
listen(socket_fd,10);//监听,等待客户端连接
clen=sizeof(struct sockaddr_in);
while(1){
connect_fd=accept(socket_fd,(struct sockaddr*)&c_addr,&clen);//不断的检测是否有客户端接入,若有就创建一个子进程对接接入的客户端
if(connect_fd==-1){
perror("accept");
}
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork()==0){//创建子进程进行对客户端的对接
while(1){
memset(&message,0,sizeof(message));//首先将用来传输数据的结构体里的内容置0
nread=read(connect_fd,&message,sizeof(message));//读取客户端向服务器传送的数据结构体
if(nread==0){
printf("client out\n");
break;
}else if(nread>0){
message_handler(message,connect_fd);//将读取出来的数据送入此函数处理
}
}
}
}
close (connect_fd);
close(socket_fd);
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>
char *get_cmd_dir(char *cmd)
{
char *p=NULL;
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}//用此函数将用户输入的指令中的文件名分割开来 如cd xx,将得到xx
int get_cmd_type(char *cmd)
{
if(strcmp("lls",cmd)==0) return LLS;
if(strstr(cmd,"lcd")!=NULL) return LCD;//注意,lcd包含cd需要在cd之前
if(strcmp("ls",cmd)==0) return LS;
if(strcmp("pwd",cmd)==0) return PWD;
if(strcmp("quit",cmd)==0) return QUIT;
if(strstr(cmd,"cd")!=NULL) return CD;
if(strstr(cmd,"get")!=NULL) return GET;
if(strstr(cmd,"put")!=NULL) return PUT;
if(!strcmp(cmd,"lpwd")) return LPWD;
return -1;
}//用此函数得到客户端输入指令的宏,指令处理函数的判断
int cmd_handler(struct Msg msg,int fd)
{
int ret;
char *dir;
char cmdTemp[128];
int fdFile;
int putSize;
ret=get_cmd_type(msg.cmd);
switch (ret)
{
case LS:
case PWD:
case CD:
case GET:
write(fd,&msg,sizeof(msg));
break;//当输入的指令需要在服务器上完成时,将指令直接发送给服务器
case PUT://与服务器对于GET指令处理的方法类似
strcpy(cmdTemp,msg.cmd);//防止strok破坏msg.cmd
dir=get_cmd_dir(cmdTemp);
printf("cmd :%s\n",msg.cmd);
if(access(dir,F_OK)==-1){//用access 函数判断文件是否存在
printf("%s this file no exit\n",dir);
}else{
fdFile=open(dir,O_RDWR);//存在打开文件
putSize = lseek(fdFile,0,SEEK_END);//获取文件的字节数
lseek(fdFile,0,SEEK_SET);
read(fdFile,msg.secondBuf,putSize);
close(fdFile);
write(fd,&msg,sizeof(msg));
}
break;
case LCD://客户端的cd指令
dir=get_cmd_dir(msg.cmd);
printf("the dir:%s\n",dir);
chdir(dir);
break;
case LLS://客户端的lls指令
system("ls");
break;
case QUIT:
strcpy(msg.cmd,"quit");
write(fd,&msg,sizeof(msg));
close(fd);
exit(-1);
case LPWD:
system("pwd");
break;
}
return ret;
}
void c_msg_handler(struct Msg msg,int fd)//信息处理函数,显示结果
{
int n_read;
int filefd;
struct Msg getmsg;//服务器发送到客户端的结构体消息
char *filename;
//getmsg.secondBuf = (char *)malloc(1024);
//memset(getmsg.secondBuf,'\0',1024);
n_read =read(fd,&getmsg,sizeof(getmsg));//读取服务器向客户端发送数据
if(n_read == 0){
printf("sever is quit\n");
exit(-1);
}
if(getmsg.type == DOFILE){//当客户端输入的指令是GET时,需要在客户端创建需要获取的文件
filename=get_cmd_dir(msg.cmd);//msg.cmd 客户端要发送给服务器的消息结构体
filefd=open(filename,O_RDWR|O_CREAT,0666);
write(filefd,getmsg.secondBuf,strlen(getmsg.secondBuf));
close(filefd);
fflush(stdout); //调用printf()时,输出的结果一般会被标准库缓存起来,可能不会及时打印写出到输出设备上面,此时就可以用fflush(stdout)强制把缓存内容进行输出
}
else{//将需要在服务器执行的指令的运行结果打印
printf("---------------------\n");
printf("%s\n",getmsg.data);
printf("---------------------\n");
}
}
int main(int argc,char *argv[])
{
int client_fd;
int nread;
int ret;
char readBuf[128];
struct sockaddr_in client_addr;
struct Msg msg;
if(argc !=3){
printf("请输入IP地址和端口号\n");
exit(-1);
}
memset(&client_addr,0,sizeof(struct sockaddr));
client_fd=socket(AF_INET,SOCK_STREAM,0);
if(client_fd==-1){
perror("socket");
exit(-1);
}
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(client_addr.sin_addr));
if(connect(client_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr))==-1){
perror("connect");
exit(-1);
}
printf("connecting.....\n");//连接客户端
while(1){
memset(&msg,0,sizeof(msg));
putchar('>');
fgets(msg.cmd,1024,stdin);
if(msg.cmd[strlen(msg.cmd) - 1] == '\n') { // 去掉换行符
msg.cmd[strlen(msg.cmd) - 1] = '\0'; }
//gets(msg.cmd);
ret=cmd_handler(msg,client_fd);//将获取的指令送入指令处理函数中,并将返回的值判断
if(ret>IFGO){//如果输入的指令像ls,pwd等需要在服务器完成处理的则进行下面的操作,而像lls这样在客户端
continue;//完成的指令不需要向服务器进行数据交流,完成后直接退出循环进行下一次指令的输入
}
if(ret==-1){
printf("cmd error,please put new cmd\n");
continue;
}
c_msg_handler(msg,client_fd);
}
return 0;
}
#define LS 0
#define GET 1
#define PWD 2
#define IFGO 3
#define LCD 4
#define LLS 5
#define CD 6
#define PUT 7
#define QUIT 8
#define DOFILE 9
#define LPWD 10
struct Msg
{
int type;
char cmd[1024];
char data[1024];
char secondBuf[1024];
};
五、项目总结(遇到的问题)
1.在客户端进行put操作时,为防止strtok
将字符串破坏,导致无法获取put的目标文件,需要在引入一个字符串的变量保存。
strcpy(cmdTemp,msg.cmd);//防止strok破坏msg.cmd
dir=get_cmd_dir(cmdTemp);
2.在进行对输入字符串进行检测的时候,lcd
的检测一定要放在cd
的前面,因为cd
是lcd
中的一个子字符串,所以如果cd
的检测是放在lcd
的前面的话,那么无论是输入的是cd
还是lcd
,都会是返回cd
。