1.基本功能
get + xxx 下载服务器的某个文件到客户端。
put + xxx 上传客户端的某个文件到服务器。
cd 切换服务器的当前目录
ls 列出服务器当前路径下的所有文件
lcd 切换客户端的当前目录
lls 列出客户端当前路径下的所有文件
pwd 展示服务器的当前路径
quit 关闭当前客户端的连接
2.编程步骤
双端通讯流程图
Socket 服务端步骤:
- socket() 创建一个 Socket
- bind() 绑定ip和端口号
- listen() 监听
- accept() 接收连接的请求
- write() 和 read() 进行会话
- close() 关闭 Socket
Socket 客户端步骤:
- socket() 创建一个 Socket
- connect() 与服务器连接
- write() 和 read() 进行会话
- close() 关闭 Socket
3.客户端和服务端共用代码
//config.h
#define LS 1
#define PWD 2
#define GET 3
#define IFGO 4
#define CD 5
#define PUT 6
#define LLS 7
#define LCD 8
#define QUIT 9
//采用结构体存储指令信息
struct Message {
int fileFlag; //处理get指令的文件判断标志
char cmd[1024];//指令
char contentBuf[4096];//文件内容
};
服务端代码
//头文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <fcntl.h>
main函数:建立客户端与服务端的连接,接收客户端的指令
//argc:命令行参数个数 argv:字符串数组
int main(int argc,char **argv){
//变量定义
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
struct Message msg;
//参数个数判断
if(argc!=3){
printf("error:params expected 3 but %d given\n",argc);
exit(-1);
}
//memset()函数 初始化内存
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//socket() 创建一个套接字
// int socket(int domain, int type, int protocol);
s_fd=socket(AF_INET,SOCK_STREAM,0);
//ip and port
s_addr.sin_family=AF_INET;
//htons() 函数:将整型变量从主机字节顺序转变成网络字节顺序
s_addr.sin_port=htons(atoi(argv[2]));
//inet_aton()函数:字符串IP地址转换为一个32位的网络序列IP地址
inet_aton(argv[1],&s_addr.sin_addr);
//bind()函数:绑定ip和端口
// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//listen 监听连接
listen(s_fd,8);
int clen=sizeof(struct sockaddr_in);
while(1){
//accept 队列中提取客户端连接
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//主进程不断获取多个客户端连接,因此用while
c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&clen);
//客户端连接失败
if(c_fd==-1){
perror("create client socket failed!\n");
}
//客户端ip
char *cip=inet_ntoa(c_addr.sin_addr);
//已连接客户端(交互信息)
printf("a new connection [%s] has been established...\n",cip);
//创建子进程处理
if(0==fork()){
//进入此说明当前是在子进程中
while(1){
//子进程不断处理当前客户端的请求,因此用while
memset(msg.cmd,0,sizeof(msg.cmd));
//ssize_t read(int fd, void *buf, size_t count);
n_read=read(c_fd,&msg,sizeof(msg));
//TCP 协议中,当一方关闭了连接,另一方通过 read() 函数从套接字中读取数据时,会返回一个值为 0 的结果,表示当前没有数据可以读取
//如果读取到的结果是0个字节,说明客户端退出了连接
if(n_read==0){
puts("client disconnected...");
break;
}
//有效的指令,读取到的字节数>0
else if(n_read>0){
//messageHandler函数处理客户端发来的信息
messageHandler(msg,c_fd,cip);
}
}
}
}
return 0;
}
getCommandType函数:将字符串指令转换为整形,供messageHandler函数中switch分支单独处理
int getCommandType(char *cmd){
if(!strcmp("ls",cmd))
return LS;
if(!strcmp("quit",cmd))
return QUIT;
if(!strcmp("pwd",cmd))
return PWD;
/*char *strstr(const char *str1, const char *str2)
返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回NULL指针。 */
if(strstr(cmd,"cd")!=NULL)
return CD;
if(strstr(cmd,"get")!=NULL)
return GET;
if(strstr(cmd,"put")!=NULL)
return PUT;
//所有指令都不匹配,即未知指令,返回-1
return -1;
}
getDestDir函数:获取文件名或路径
char *getDestDir(char *cmd){
char *p;
//char *strtok(char *str, const char *delim);
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}
messageHandler函数:处理客户端指令的业务代码
void messageHandler(struct Message msg,int fd,char *cip){
char *filePath=NULL;
char dataBuf[4096]={0};
int file_fd;
FILE *fp;
char *dir;
/*cmdType是指令的类型(如ls、cd、get)
封装getCommandType()函数获取指令类别
配合switch函数处理相应的功能*/
int cmdType=getCommandType(msg.cmd);
//调试信息,方便查询问题
printf("-----------------------\ncommand:%s cmdType: %d\n",msg.cmd,cmdType);
if(cmdType==-1){
puts("command not found");
}
switch(cmdType){
case LS:
case PWD:
//FILE *popen(const char *command, const char *type);
fp=popen(msg.cmd,"r");
//1:执行ls 或pwd 指令 2:返回执行结果对应的文件指针fp
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread(msg.cmd,sizeof(msg.cmd),1,fp);
//读取1次fp,读到msg.cmd中
//ssize_t write(int fd, const void *buf, size_t count);
write(fd,&msg,sizeof(msg));
//把结果写到客户端(fd)
break;
case CD:
dir=getDestDir(msg.cmd);
printf("dir:%s\n",dir);
// int chdir(const char *path);
//changes the current working directory of the calling process to the directory specified in path.
chdir(dir);
break;
case GET:
filePath=getDestDir(msg.cmd);
//文件不存在
if(access(filePath,F_OK)==-1){
strcpy(msg.cmd,"no such file!");
write(fd,msg.cmd,sizeof(msg.cmd));
}else{
//文件存在
msg.fileFlag=1;
printf("filePath is %s\n",filePath);
file_fd=open(filePath,O_RDWR);
//把文件读到dataBuf中
read(file_fd,dataBuf,sizeof(dataBuf));
close(file_fd);
//将dataBuf数据覆盖msg的cmd,要求cmd足够大,不然获取的文件稍大就会导致strcpy后的文件不完全的情况
strcpy(msg.cmd,dataBuf);
write(fd,&msg,sizeof(msg));
}
break;
case PUT:
//1.获取待上传文件的名称 2.创建文件及权限 3.内容为空,待拷贝...
file_fd=open(getDestDir(msg.cmd),O_RDWR|O_CREAT,0644);
//write(file_fd,msg.contentBuf,sizeof(msg.contentBuf));
//写文件用strlen 区别看这里blog.youkuaiyun.com/sinat_25457161/article/details/48572033
//客户端发送过来的文件的内容存在msg.contentBuf中,将contentBuf写到file_fd中
write(file_fd,msg.contentBuf,strlen(msg.contentBuf));
close(file_fd);
break;
case QUIT:
printf("connection[%s] disconnected...\n",cip);
exit(-1);
}
}
客户端代码
//头文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <fcntl.h>
main函数:建立客户端与服务端的连接,客户端发送指令给服务端
int main(int argc,char **argv){
//变量定义
int c_fd;
int n_read;
struct sockaddr_in c_addr;
struct Message msg;
//参数个数判断
if(argc!=3){
printf("error:params expected 3 but %d given\n",argc);
exit(-1);
}
//初始化内存
memset(&c_addr,0,sizeof(struct sockaddr_in));
//socket
// int socket(int domain, int type, int protocol);
c_fd=socket(AF_INET,SOCK_STREAM,0);
//ip and port
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
//字符串IP地址转换为一个32位的网络序列IP地址
inet_aton(argv[1],&c_addr.sin_addr);
int clen=sizeof(struct sockaddr_in);
//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if(connect(c_fd,(struct sockaddr *)&c_addr,clen)==-1){
perror("create client socket failed...\n");
exit(-1);
}
//消息提示:客户端连接到服务端
printf("we have been connected with the server\n");
//调节指令输入格式的标志位
int mark=0;
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
if(mark==0){
putchar('>');
}
mark=1;
//指令存到cmd中
gets(msg.cmd);
int commandType=commandHandler(msg,c_fd);
printf("count=%d\n",count);
if(commandType>IFGO){
putchar('>');
fflush(stdout);
continue;
}
//无效指令
if(commandType==-1){
puts("command not found");
putchar('>');
fflush(stdout);
continue;
}
//处理服务端响应的结果
serverMessageHandler(c_fd,msg);
}
return 0;
}
getCommandType函数:将字符串指令转换为整形
//字符串指令转换为整形 供switch处理
int getCommandType(char *cmd){
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("lls",cmd)) return LLS;
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"lcd")) return LCD;
if(strstr(cmd,"cd")) return CD;
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
//无效指令 返回-1
return -1;
}
getLocalDir函数:获取文件名或路径
char *getLocalDir(char *cmd){
char *p=NULL;
//::char *strtok(char *str, const char *delim);
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}
commandHandler函数:处理客户端发送服务端的指令
int commandHandler(struct Message msg,int fd){
char *filePath=NULL;
char *dir;
int file_fd;
int cmdType=getCommandType(msg.cmd);
char tempBuf[32];
//调试信息
printf("commandHandler:msg.cmd is %s\n",msg.cmd);
switch(cmdType){
case LS:
case PWD:
case CD:
count++;
write(fd,&msg,sizeof(msg));
break;
case GET:
count++;
write(fd,&msg,sizeof(msg));
break;
case PUT:
count++;
strcpy(tempBuf,msg.cmd);
//strtok函数会改变msg.cmd ,采用临时变量替代msg.cmd
filePath=getLocalDir(tempBuf);
printf("filePath is %s\n",filePath);
if(access(filePath,F_OK)==-1){
printf("%s doesn't exist!\n",filePath);
}else{
//本地存在和待上传的文件
file_fd=open(filePath,O_RDWR);
memset(msg.contentBuf,0,sizeof(msg.contentBuf));
//把文件读到contentBuf中
read(file_fd,msg.contentBuf,sizeof(msg.contentBuf));
close(file_fd);
//把结构体写到服务端
write(fd,&msg,sizeof(msg));
}
break;
case LLS:
count++;
//列出服务端当前路径下的所有文件
system("ls");
break;
case LCD:
count++;
dir=getLocalDir(msg.cmd);
chdir(dir);
break;
case QUIT:
count++;
write(fd,&msg,sizeof(msg));
close(fd);
exit(-1);
}
return cmdType;
}
serverMessageHandler函数:客户端处理服务端响应的信息
//处理服务端响应的信息
void serverMessageHandler(int fd,struct Message msg){
struct Message recMsg;
int nread=read(fd,&recMsg,sizeof(recMsg));
//TCP协议某一方关闭连接时,read()返回0,读不到数据
if(nread==0){
puts("server disconnected...");
exit(-1);
}
//处理get指令获取的文件
else if(recMsg.fileFlag==1){
char *dir=getLocalDir(msg.cmd);
printf("local fileName:%s\n",dir);
//创建文件,名为dir
int newFilefd=open(dir,O_RDWR|O_CREAT,0600);
write(newFilefd,recMsg.cmd,strlen(recMsg.cmd));
putchar('>');
fflush(stdout);
}else{
puts("----------------------------");
printf("\n%s\n",recMsg.cmd);
puts("----------------------------");
putchar('>');
fflush(stdout);
}
}
4.整合代码
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <fcntl.h>
static int count=0;
char *getLocalDir(char *cmd){
char *p=NULL;
//::char *strtok(char *str, const char *delim);
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}
int commandHandler(struct Message msg,int fd){
char *filePath=NULL;
char *dir;
int file_fd;
int cmdType=getCommandType(msg.cmd);
char tempBuf[32];
printf("commandHandler:msg.cmd is %s\n",msg.cmd);
switch(cmdType){
case LS:
case PWD:
case CD:
count++;
write(fd,&msg,sizeof(msg));
break;
case GET:
count++;
write(fd,&msg,sizeof(msg));
break;
case PUT:
count++;
strcpy(tempBuf,msg.cmd);
//strtok函数会改变msg.cmd ,采用临时变量替代msg.cmd
filePath=getLocalDir(tempBuf);
printf("filePath is %s\n",filePath);
if(access(filePath,F_OK)==-1){
printf("%s doesn't exist!\n",filePath);
}else{
//本地存在待和上传的文件
file_fd=open(filePath,O_RDWR);
memset(msg.contentBuf,0,sizeof(msg.contentBuf));
read(file_fd,msg.contentBuf,sizeof(msg.contentBuf));
close(file_fd);
write(fd,&msg,sizeof(msg));
}
break;
case LLS:
count++;
system("ls");
break;
case LCD:
count++;
dir=getLocalDir(msg.cmd);
chdir(dir);
break;
case QUIT:
count++;
write(fd,&msg,sizeof(msg));
close(fd);
exit(-1);
}
return cmdType;
}
//指令转换为整形 供switch处理
int getCommandType(char *cmd){
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("lls",cmd)) return LLS;
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"lcd")) return LCD;
if(strstr(cmd,"cd")) return CD;
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
return -1;
}
//处理服务端响应的信息
void serverMessageHandler(int fd,struct Message msg){
struct Message recMsg;
int nread=read(fd,&recMsg,sizeof(recMsg));
//TCP协议某一方关闭连接时,read()返回0,读不到数据
if(nread==0){
puts("server disconnected...");
exit(-1);
}else if(recMsg.fileFlag==1){
char *dir=getLocalDir(msg.cmd);
printf("local fileName:%s\n",dir);
int newFilefd=open(dir,O_RDWR|O_CREAT,0600);
write(newFilefd,recMsg.cmd,strlen(recMsg.cmd));
putchar('>');
fflush(stdout);
}else{
puts("----------------------------");
printf("\n%s\n",recMsg.cmd);
puts("----------------------------");
putchar('>');
fflush(stdout);
}
}
int main(int argc,char **argv){
//变量定义
int c_fd;
int n_read;
struct sockaddr_in c_addr;
struct Message msg;
//参数个数判断
if(argc!=3){
printf("error:params expected 3 but %d given\n",argc);
exit(-1);
}
//初始化内存
memset(&c_addr,0,sizeof(struct sockaddr_in));
//socket
// int socket(int domain, int type, int protocol);
c_fd=socket(AF_INET,SOCK_STREAM,0);
//ip and port
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
//字符串IP地址转换为一个32位的网络序列IP地址
inet_aton(argv[1],&c_addr.sin_addr);
int clen=sizeof(struct sockaddr_in);
//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if(connect(c_fd,(struct sockaddr *)&c_addr,clen)==-1){
perror("create client socket failed...\n");
exit(-1);
}
//客户端连接到服务端
printf("we have been connected with the server\n");
int mark=0;
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
if(mark==0){
putchar('>');
}
mark=1;
gets(msg.cmd);
int commandType=commandHandler(msg,c_fd);
printf("count=%d\n",count);
if(commandType>IFGO){
putchar('>');
fflush(stdout);
continue;
}
if(commandType==-1){
puts("command not found");
putchar('>');
fflush(stdout);
continue;
}
serverMessageHandler(c_fd,msg);
}
return 0;
}
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <fcntl.h>
char *getDestDir(char *cmd){
char *p;
//::char *strtok(char *str, const char *delim);
p=strtok(cmd," ");
p=strtok(NULL," ");
return p;
}
void messageHandler(struct Message msg,int fd,char *cip){
char *filePath=NULL;
char dataBuf[4096]={0};
int file_fd;
FILE *fp;
char *dir;
int cmdType=getCommandType(msg.cmd);
printf("-----------------------\ncommand:%s cmdType: %d\n",msg.cmd,cmdType);
if(cmdType==-1){
puts("command not found");
}
switch(cmdType){
case LS:
case PWD:
//FILE *popen(const char *command, const char *type);
fp=popen(msg.cmd,"r");
//1:执行ls 或pwd 指令 2:返回执行结果对应的文件指针fp
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread(msg.cmd,sizeof(msg.cmd),1,fp);
//读取1次fp,读到msg.cmd中
//ssize_t write(int fd, const void *buf, size_t count);
write(fd,&msg,sizeof(msg));
//把结果写到客户端(fd)
break;
case CD:
dir=getDestDir(msg.cmd);
printf("dir:%s\n",dir);
// int chdir(const char *path);
chdir(dir); //changes the current working directory of the calling process to the directory specified in path.
break;
case GET:
filePath=getDestDir(msg.cmd);
//文件不存在
if(access(filePath,F_OK)==-1){
strcpy(msg.cmd,"no such file!");
write(fd,msg.cmd,sizeof(msg.cmd));
}else{
//文件存在
msg.fileFlag=1;
printf("filePath is %s\n",filePath);
file_fd=open(filePath,O_RDWR);
read(file_fd,dataBuf,sizeof(dataBuf));
close(file_fd);
strcpy(msg.cmd,dataBuf);
write(fd,&msg,sizeof(msg));
}
break;
case PUT:
file_fd=open(getDestDir(msg.cmd),O_RDWR|O_CREAT,0644);
//write(file_fd,msg.contentBuf,sizeof(msg.contentBuf));
//写文件用strlen 区别看这里blog.youkuaiyun.com/sinat_25457161/article/details/48572033
write(file_fd,msg.contentBuf,strlen(msg.contentBuf));
close(file_fd);
break;
case QUIT:
printf("connection[%s] disconnected...\n",cip);
exit(-1);
}
}
//指令转换为整形 供switch
int getCommandType(char *cmd){
if(!strcmp("ls",cmd))
return LS;
if(!strcmp("quit",cmd))
return QUIT;
if(!strcmp("pwd",cmd))
return PWD;
if(strstr(cmd,"cd")!=NULL)
return CD;
if(strstr(cmd,"get")!=NULL)
return GET;
if(strstr(cmd,"put")!=NULL)
return PUT;
return -1;
}
int main(int argc,char **argv){
//变量定义
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
struct Message msg;
//参数个数判断
if(argc!=3){
printf("error:params expected 3 but %d given\n",argc);
exit(-1);
}
//初始化内存
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//socket
// int socket(int domain, int type, int protocol);
s_fd=socket(AF_INET,SOCK_STREAM,0);
//ip and port
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
//字符串IP地址转换为一个32位的网络序列IP地址
inet_aton(argv[1],&s_addr.sin_addr);
// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//listen
listen(s_fd,8);
//accept
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int clen=sizeof(struct sockaddr_in);
while(1){
c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&clen);
if(c_fd==-1){
perror("create client socket failed!\n");
}
//客户端ip
char *cip=inet_ntoa(c_addr.sin_addr);
//已连接客户端
printf("a new connection [%s] has been established...\n",cip);
//创建子进程处理
if(0==fork()){
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
//ssize_t read(int fd, void *buf, size_t count);
n_read=read(c_fd,&msg,sizeof(msg));
//TCP 协议中,当一方关闭了连接,另一方通过 read() 函数从套接字中读取数据时,会返回一个值为 0 的结果,表示当前没有数据可以读取
if(n_read==0){
puts("client disconnected...");
break;
//有效指令
}else if(n_read>0){
messageHandler(msg,c_fd,cip);
}
}
}
}
return 0;
}
5.演示
diff 指令 查看ftp下的hello.c和ftp/folder/hello.c(从服务端get到客户端的) 内容一致
diff 指令 查看ftp下的herman.iu((客户端put到服务端的)和ftp/folder/herman.iu 内容一致
6.总结
Llinux网络及系统文件编程暂时告一段落,这个课其实9月底就学完了,期间因为工作缘故加上个人惰性,迟迟没有学习,今天好不容易抽出时间整理了一下这个小demo,算是监督一下自己吧。有看到这部分的朋友,存在疑惑的点可以相互交流。
2023-10-30 02:08:00 Herman