文章目录
前言
本文章是前段时间学习linux编程的一个小总结,如果不总结的话我怕我学完就忘emmm(其实已经忘的差不多了),本文是跟着上官可编程老师与朱有鹏老师学习之后的练手作品,本人学疏才浅,暂时只能到这个地步
提示:以下是本篇文章正文内容
一、实现的功能
利用socket,建立起服务端与客户端的对接;(服务端能支持多台客户端的同时连接)
1.客户端
1、客户端输入ls指令,能获取服务端上文件列表。
2、客户端输入cd指令+路径,可以切换服务端的目录。
3、在程序运行的过程中,客户端输入lls指令 能够查看自己的文件列表
4、在程序运行的过程中,客户端输入lcd指令+路径 能够切换自己的目录。
5、客户端输入get指令+文件名,能将服务端上面的某个文件下载到客户端
6、客户端输入put指令+文件名,能将客户端上面的某个文件上传到服务端
2.服务端
1、不断监听客户端的指令(等待指令)。
2、在接收上面客户端的指令后,去执行指令。
二、实现思路
1.建立socket连接
(1)服务端建立socket连接:socket—>blind—>listen—>accept
(2)客户端建立socket连接:socket—>connect
2.客户端和服务端进行信息的交互
(1)定义指令
(2)客户端匹配用户输入的指令,客户端发送指令到服务端,服务端解析指令作出响应
三、具体流程分析(代码)
第一步:建立socket连接
服务端建立socket连接
流程:socket—>blind—>listen—>accept
代码如下(示例):
int main(int argc,char **argv)
{
int c_fd;
int s_fd;
int n_read;
char readBuf[128];
struct Msg msg;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
/*调试信息,这里我想要用argv,argc,进行手动指定端口号和IP地址,所以在用这两个参数之前需要先判断*/
if(argc != 3){
printf("parameter error!\n");
exit(-1);
}
/*用之前清空,防止数据产生混乱*/
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
/*s_fd:这里socket返回的是监听fd,是用来监听客户端的,不能用来和任何客户端进行读写*/
/*AF_INET:IPv4,SOCK_STREAM:tcp协议,0:使用系统默认的方式*/
s_fd = socket(AF_INET,SOCK_STREAM,0);
/*如果返回值为-1说明错误,打印出错误号*/
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2.bind
/*有IPv4和IPv6两种,这里我们选IPv4*/
s_addr.sin_family = AF_INET; // 设置地址族为IPv4
/*htons:端口号大小端的转换(具体大小端要看对应的计算机)
atoi:字符串转换成整型,使我们输入的数字端口号可以被识别
*/
s_addr.sin_port = htons(atoi(argv[2])); // 设置地址的端口号信息
/*inet_aton:将我们输入的地址字符串转换成网络可以识别的api*/
inet_aton(argv[1],&s_addr.sin_addr); // 设置IP地址
/*(struct sockaddr *)&s_addr这之所以需要类型转换的原因是因为我们用的是IPV4的结构体,而给出的标准是IPV4和IPV6的通用结构体,所以我们一般用IPV4的结构体之后将其转换为系统给出的通用结构体,其实转不转都一样,就是会报警告而已,而这个警告是我们可以理解的警告,所以问题不大*/
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
/*设置最大监听10个*/
listen(s_fd,10);
//4.accept
int clen = sizeof(struct sockaddr_in);
/*while不停的循环接收客户端发来的信息,并且作出响应*/
while(1){
/*accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。*/
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
/*调试信息*/
if(c_fd != -1){
perror("accept");
}
/*成功打印出连接的地址*/
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
/*当我的accept每接收到一个信息就建立一个子进程*/
if(fork() == 0){
while(1){
/*每次循环接收信息之前,清空之前的信息*/
memset(msg.cmd,0,sizeof(msg.cmd));
/*用accept建立的连接fd来读写信息*/
n_read = read(c_fd,&msg,sizeof(msg));
if(n_read == 0){
printf("client out\n");
break;
}else if(n_read > 0){
/*一旦发现有信息,就执行这个函数*/
msg_handler(msg,c_fd);
}
}
}
}
/*关闭监听fd和连接fd*/
close(c_fd);
close(s_fd);
return 0;
}
客户端建立socket连接
流程:socket—>connect
客户端建立socket与服务端建立socket大同小异这里就不过多赘述了
代码如下(示例):
int main(int argc,char **argv)
{
int c_fd;
struct Msg msg;
struct sockaddr_in c_addr;
if(argc != 3){
printf("parameter error!\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
printf("connect ...\n");
return 0;
}
第二步:客户端和服务端进行信息的交互
定义指令
共有指令
代码如下(示例):
#define LS 0
#define PWD 1
#define GET 2
#define IFGO 3
#define CD 4
#define PUT 5
#define LLS 6
#define LCD 7
#define LPWD 8
#define QUIT 9
#define DOFILE 10
struct Msg
{
int type;
char cmd[1024];
char buf[128];
};
服务端指令
代码如下(示例):
int get_cmd_type(char *cmd) //检测命令并转为相应int
{
if(!strcmp("ls",cmd)) return LS;//strcmp,比较的字符串相等时返回0
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"cd")) return CD;//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
return 10;
}
客户端指令
代码如下(示例):
int get_cmd_type(char *cmd)
{
if(strstr(cmd,"lcd")) return LCD;
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"cd")) return CD;
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
if(!strcmp("lls",cmd)) return LLS;
return -1;
}
客户端匹配用户输入的指令,客户端发送指令到服务端,服务端解析指令作出响应
客户端:
代码如下(示例):
/*这是一个分割命令的函数,为了实现cd指令+路径*/
char *getDir(char *cmd)
{
char *p;
p = strtok(cmd," ");
p = strtok(NULL," ");
return p;
}
/*当客处理用户输入指令函数*/
int cmd_handler(struct Msg msg,int fd)
{
char *dir = NULL;
char buf[32];
int ret;
int filefd;
ret = get_cmd_type(msg.cmd); //cmd转为int类型
/*根据不同的指令,进行不同的操作*/
switch(ret){
case LS:
case CD:
case PWD:
case GET:
msg.type = 0;
write(fd,&msg,sizeof(msg));
break;
case PUT:
strcpy(buf,msg.cmd);
dir = getDir(buf); //获取第二个参数
if(access(dir,F_OK) == -1){ //判断文件是否存在
printf("%s not exsit\n",dir);
}else{
filefd = open(dir,O_RDWR);
read(filefd,msg.buf,sizeof(msg.buf)); //读文件内容
close(filefd);
write(fd,&msg,sizeof(msg));
}
break;
case LPWD:
printf("-------------------------------------\n\n");
system("pwd");
printf("\n-------------------------------------\n");
break;
case LLS:
printf("-------------------------------------\n\n");
system("ls");
printf("\n-------------------------------------\n");
break;
case LCD:
dir = getDir(msg.cmd); //获取第二个参数
int t = chdir(dir); //cd
break;
case QUIT:
strcpy(msg.cmd,"quit");
write(fd,&msg,sizeof(msg));
close(fd);
exit(-1);
}
return ret;
}
//处理服务端发来的指令
void handler_server_message(int c_fd,struct Msg msg)
{
int n_read;
struct Msg msgget;
int newfilefd;
n_read = read(c_fd,&msgget,sizeof(msgget));
if(n_read == 0){
printf("server is out,quit\n");
exit(-1);
}
else if(msgget.type == DOFILE){
char *p = getDir(msg.cmd);
newfilefd = open(p,O_RDWR|O_CREAT,0600);
write(newfilefd,msgget.cmd,strlen(msgget.cmd));
putchar('>');
fflush(stdout);
}
else{
printf("-------------------------------------\n");
printf("\n%s\n",msgget.cmd);
printf("-------------------------------------\n");
putchar('>');
fflush(stdout);
}
}
服务端
代码如下(示例):
/*解析客户端发来的指令,并对应作出响应*/
void msg_handler(struct Msg msg,int fd)
{
char dataBuf[1024] = {0};
char *file = NULL;
int fdfile;
printf("cmd: %s\n",msg.cmd); //打印命令
int ret = get_cmd_type(msg.cmd); //将命令转为int类型
switch(ret){
case LS:
case PWD:
msg.type = 0;
FILE *r = popen(msg.cmd,"r"); //执行命令,返回结果
fread(msg.cmd,sizeof(msg.cmd),1,r); //将结果读到msg.cmd
write(fd,&msg,sizeof(msg)); //写入客户端
break;
case CD:
msg.type = 1;
char *dir = getDesDir(msg.cmd); //获取第二个参数
printf("dir:%s\n",dir);
chdir(dir); //系统调用函数,同cd
break;
case GET:
file = getDesDir(msg.cmd);
if(access(file,F_OK) == -1){ //判断文件是否存在
strcpy(msg.cmd,"Have Not File!");
write(fd,&msg,sizeof(msg));
}else{
msg.type = DOFILE; //设置标志
fdfile = open(file,O_RDWR);
read(fdfile,dataBuf,sizeof(dataBuf)); //读文件内容
close(fdfile);
strcpy(msg.cmd,dataBuf);
write(fd,&msg,sizeof(msg));
}
break;
case PUT:
fdfile = open(getDesDir(msg.cmd),O_RDWR|O_CREAT,0600);
write(fdfile,msg.buf,strlen(msg.buf));
close(fdfile);
break;
case QUIT:
printf("client quit!\n");
exit(-1);
}
}
四、整体流程分析(代码)
1.客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "config.h"
#include <unistd.h>
int get_cmd_type(char *cmd)
{
if(strstr(cmd,"lcd")) return LCD;
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"cd")) return CD;
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
if(!strcmp("lls",cmd)) return LLS;
return -1;
}
/*==============以上是定义指令=====================*/
char *getDir(char *cmd)
{
char *p;
p = strtok(cmd," ");
p = strtok(NULL," ");
return p;
}
int cmd_handler(struct Msg msg,int fd)
{
char *dir = NULL;
char buf[32];
int ret;
int filefd;
ret = get_cmd_type(msg.cmd); //cmd转为int类型
switch(ret){
case LS:
case CD:
case PWD:
case GET:
msg.type = 0;
write(fd,&msg,sizeof(msg));
break;
case PUT:
strcpy(buf,msg.cmd);
dir = getDir(buf); //获取第二个参数
if(access(dir,F_OK) == -1){ //判断文件是否存在
printf("%s not exsit\n",dir);
}else{
filefd = open(dir,O_RDWR);
read(filefd,msg.buf,sizeof(msg.buf)); //读文件内容
close(filefd);
write(fd,&msg,sizeof(msg));
}
break;
case LPWD:
printf("-------------------------------------\n\n");
system("pwd");
printf("\n-------------------------------------\n");
break;
case LLS:
printf("-------------------------------------\n\n");
system("ls");
printf("\n-------------------------------------\n");
break;
case LCD:
dir = getDir(msg.cmd); //获取第二个参数
int t = chdir(dir); //cd
break;
case QUIT:
strcpy(msg.cmd,"quit");
write(fd,&msg,sizeof(msg));
close(fd);
exit(-1);
}
return ret;
}
/*==============以上是客户端匹配用户输入的指令=====================*/
void handler_server_message(int c_fd,struct Msg msg)
{
int n_read;
struct Msg msgget;
int newfilefd;
n_read = read(c_fd,&msgget,sizeof(msgget));
if(n_read == 0){
printf("server is out,quit\n");
exit(-1);
}
else if(msgget.type == DOFILE){
char *p = getDir(msg.cmd);
newfilefd = open(p,O_RDWR|O_CREAT,0600);
write(newfilefd,msgget.cmd,strlen(msgget.cmd));
putchar('>');
fflush(stdout);
}
else{
printf("-------------------------------------\n");
printf("\n%s\n",msgget.cmd);
printf("-------------------------------------\n");
putchar('>');
fflush(stdout);
}
}
/*==============以上是处理服务端发来的消息=====================*/
/*==============以下是建立socket连接=====================*/
int main(int argc,char **argv)
{
int c_fd;
struct Msg msg;
struct sockaddr_in c_addr;
if(argc != 3){
printf("parameter error!\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
printf("connect ...\n");
int mark = 0;
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
if(mark == 0) printf(">");
gets(msg.cmd);
if(strlen(msg.cmd) == 0){
if(mark == 1){
printf(">");
}
continue;
}
mark = 1;
int ret = cmd_handler(msg,c_fd);
if(ret > IFGO){
putchar('>');
fflush(stdout); //打印输出缓冲区到标准输出设备上
continue;
}
if(ret == -1){
printf("command not \n");
printf(">");
fflush(stdout);
continue;
}
handler_server_message(c_fd,msg);
}
return 0;
}
2.服务端
代码如下(示例):
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "config.h"
#include <unistd.h>
int get_cmd_type(char *cmd) //检测命令并转为相应int
{
if(!strcmp("ls",cmd)) return LS;//strcmp,比较的字符串相等时返回0
if(!strcmp("pwd",cmd)) return PWD;
if(!strcmp("quit",cmd)) return QUIT;
if(strstr(cmd,"cd")) return CD;//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
return 10;
}
/*==============以上是定义指令=====================*/
char *getDesDir(char *cmsg) //取空格分开的的第二个字符串
{
char *p;
p = strtok(cmsg," ");//char *strtok(char s[], const char *delim);分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
p = strtok(NULL," ");
return p;
}
void msg_handler(struct Msg msg,int fd)
{
char dataBuf[1024] = {0};
char *file = NULL;
int fdfile;
printf("cmd: %s\n",msg.cmd); //打印命令
int ret = get_cmd_type(msg.cmd); //将命令转为int类型
switch(ret){
case LS:
case PWD:
msg.type = 0;
FILE *r = popen(msg.cmd,"r"); //执行命令,返回结果
fread(msg.cmd,sizeof(msg.cmd),1,r); //将结果读到msg.cmd
write(fd,&msg,sizeof(msg)); //写入客户端
break;
case CD:
msg.type = 1;
char *dir = getDesDir(msg.cmd); //获取第二个参数
printf("dir:%s\n",dir);
chdir(dir); //系统调用函数,同cd
break;
case GET:
file = getDesDir(msg.cmd);
if(access(file,F_OK) == -1){ //判断文件是否存在
strcpy(msg.cmd,"Have Not File!");
write(fd,&msg,sizeof(msg));
}else{
msg.type = DOFILE; //设置标志
fdfile = open(file,O_RDWR);
read(fdfile,dataBuf,sizeof(dataBuf)); //读文件内容
close(fdfile);
strcpy(msg.cmd,dataBuf);
write(fd,&msg,sizeof(msg));
}
break;
case PUT:
fdfile = open(getDesDir(msg.cmd),O_RDWR|O_CREAT,0600);
write(fdfile,msg.buf,strlen(msg.buf));
close(fdfile);
break;
case QUIT:
printf("client quit!\n");
exit(-1);
}
}
/*==============以上是处理客户端发来的消息=====================*/
/*==============以下是建立socket连接=====================*/
int main(int argc,char **argv)
{
int c_fd;
int s_fd;
int n_read;
char readBuf[128];
struct Msg msg;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3){
printf("parameter error!\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int clen = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd != -1){
perror("accept");
}
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
n_read = read(c_fd,&msg,sizeof(msg));
if(n_read == 0){
printf("client out\n");
break;
}else if(n_read > 0){
msg_handler(msg,c_fd);
}
}
}
}
close(c_fd);
close(s_fd);
return 0;
}
3.共有文件:
代码如下(示例):
#define LS 0
#define PWD 1
#define GET 2
#define IFGO 3
#define CD 4
#define PUT 5
#define LLS 6
#define LCD 7
#define LPWD 8
#define QUIT 9
#define DOFILE 10
struct Msg
{
int type;
char cmd[1024];
char buf[128];
};
五、实现效果(图片)
六、反思与总结
1.socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写
2.(struct sockaddr *)&s_addr这之所以需要类型转换的原因是因为我们用的是IPV4的结构体,而给出的标准是IPV4和IPV6的通用结构体,所以我们一般用IPV4的结构体之后将其转换为系统给出的通用结构体
3. htons:端口号大小端的转换(具体大小端要看对应的计算机)
atoi:字符串转换成整型,使我们输入的数字端口号可以被识别
inet_aton:将我们输入的地址字符串转换成网络可以识别的api
除了上面用到的,我们常用到的一些函数
inet_addr、inet_ntoa、inet_aton
inet_pton、inet_ntop
4.记得在每次数据交换的时候都要清空数据,不然会有上一次的数据进行干扰
在
那么这个小练习到这里就结束了,里面用到了多进程,socket相关的一些知识,而且虽然是个小练习但是整个框架就是一个项目的缩影,对于刚接触linux编程的同学来说联系一下还是有很大的好处的