ubuntu下的自写ftp服务器

1 篇文章 0 订阅
1 篇文章 0 订阅

利用SOCKET 套接字网络编程实现的小型FTP服务器

SOCKET服务端与客户端的代码框架

一:服务端

1. 开始创建socket();

返回值为int型,命名s_fd;

 int socket(int domain, int type, int protocol)
 domain(域) : AF_INET          //选用IPv4因特网域
 type : SOCK_STREAM/ SOCK_DGRAM :  //SOCK_STREAM为TCP协议,SOCK_DGRAM为UDP协议
 protocol : 0;///通常赋值为零;

2.地址准备好bind(1网络描述符.s_fd, 2地址(struct socket*)&s_addr,3大小sizeof(struct sockaddr_in)));

       SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

*********************************************************************************
        struct sockaddr_in s_addr; //定义一个为sockaddr_in_s_addr的变量
        memset(&s_addr,0,sizeof(struct sockaddr_in));//初始化结构体(清空)

        s_addr.sin_family = AF_INET;//协议族为ipv4
        s_addr.sin_port = htons(atoi(argv[2]));//端口号通过main(int argc,char **argv)可自主设置
        inet_aton(argv[1],&s_addr.sin_addr);//  使用的IP地址

        htons(atoi()):联合使用就是把输入的整型数端口号变成网络字节序输出
   函数原型:uint16_t htons(uint16_t hostshort);
   函数作用:htons是将整型变量从主机字节顺序转变成网络字节顺序, 
   就是整数在地址空间存储方式变为高位字节存放在内存的低地址处,
   网络字节顺序采用big-endian排序方式。 

    aton()函数作用:将字符串形式的”192.168.xxx.xx“转换为网络能识别的格式
    int inet_aton(const char *strptr,struct in_addr *addrptr)
将字符串转换成一个32位的网络字节序二进制值,并同过addrptr指针来存储,成功返回1,失败返回0




        //2 bind
        bind(sk_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

、bind函数:
 函数原型:bind( SOCKET sockaddr, const struct sockaddr my_addr,int addrlen);
 函数作用:套接字绑定到一个地址,并制定一个端口号。
 将套接字绑定一个IP地址和端口号,因为这两个元素可以在网络环境中唯一地址表示一个进程。
 

3.监听 listen(1.s_fd, 2,最大可连接数)
listen函数:
 函数原型:int listen(SOCKET sockfd, int backlog);
 函数作用:listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,
 从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
 listen函数一般在调用bind之后-调用accept之前调用。
 

4.连接accept()函数

accept()系统调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET。它提取出所监听套接字的等待连接队列中第一个连接请求创建一个新的套接字,并返回指向该套接字的文件描述符。新建立的套接字不在监听状态,原来所监听的套接字也不受该系统调用的影响。

参数:

sockfd,    利用系统调用socket()建立的套接字描述符,通过bind()绑定到一个本地地址(一般为服务器的套接字),并且通过listen()一直在监听连接

addr,    指向struct sockaddr的指针,该结构用通讯层服务器对等套接字的地址(一般为客户端地址)填写,返回地址addr的确切格式由套接字的地址类别(比如TCP或UDP)决定;若addr为NULL,没有有效地址填写,这种情况下,addrlen也不使用,应该置为NULL;

备注:addr是个指向局部数据结构sockaddr_in的指针,这就是要求接入的信息本地的套接字(地址和指针)。

addrlen,    一个值结果参数,调用函数必须初始化为包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值;

       struct sockaddr_in c_addr;

       int con_fd;

       int cln = sizeof(struct sockaddr_in);

       con_fd = accept(sk_fd,(struct sockaddr *)&c_addr,&cln);

备注:addrlen是个局部整形变量,设置为sizeof(struct   sockaddr_in)。

备注:新建立的套接字准备发送send()和接收数据recv()。

备注:一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;

若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;

若没有,就阻塞等待

5.accept()完了之后

可创建父子进程来处理客户端发来的数据,父进程等待,子进程处理。

read()从客户端读;

 write()写进服务端;

**************************************************************************************************************

二:客户端

1. 开始创建socket();同上。

    

 struct sockaddr_in c_addr;
  struct Msg msg;

   memset(&c_addr,0,sizeof(struct sockaddr_in));
 //1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);

c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);

2.连接服务端connect();

struct sockaddr_in c_addr;
struct Msg msg;

memset(&c_addr,0,sizeof(struct sockaddr_in));
 c_addr.sin_family = AF_INET;
 c_addr.sin_port = htons(atoi(argv[2]));
 inet_aton(argv[1],&c_addr.sin_addr);

// connect
con = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));

1)connect描述

定义函数:
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
connect函数通常用于客户端建立tcp连接。

参数:
sockfd:标识一个套接字。
serv_addr:套接字s想要连接的主机地址和端口号。
addrlen:name缓冲区的长度。

返回值:
成功则返回0,失败返回-1,错误原因存于errno中。

错误代码: 
EBADF 参数sockfd 非合法socket处理代码
EFAULT 参数serv_addr指针指向无法存取的内存空间
ENOTSOCK 参数sockfd为一文件描述词,非socket。
EISCONN 参数sockfd的socket已是连线状态
ECONNREFUSED 连线要求被server端拒绝。
ETIMEDOUT 企图连线的操作超过限定时间仍未有响应。
ENETUNREACH 无法传送数据包至指定的主机。
EAFNOSUPPORT sockaddr结构的sa_family不正确。
EALREADY socket为不可阻塞且先前的连线操作还未完成。

(2)SOCKET中连接过程比较
      connect是套接字连接操作,connect操作之后代表对应的套接字已连接,UDP协议在创建套接字之后,可以同多个服务器端建立通信,而TCP协议只能与一个服务器端建立通信,TCP不允许目的地址是广播或多播地址,UDP允许。当然UDP协议也可以像TCP协议一样,通过connect来指定对方的ip地址、端口。
      UDP协议经过connect之后,在通过sendto来发送数据报时不需要指定目的地址、端口,如果指定了目的地址、端口,那么会返回错误。通过UDP协议可以给同一个套接字指定多次connect操作,而TCP协议不可以,TCP只能指定一次connect操作。UDP协议指定第二次connect操作之后会先断口第一次的连接,然后建立第二次的连接。


(3)客户端在建立同服务器端的连接过程
第一步都会通过socket建立连接套接字;
第二步通过bind来绑定本地地址、本地端口,当然绑定操作可以不用指定;
      对于UDP协议:若未指定绑定操作,那么可以通过下面connect操作来由内核负责套接字的绑定操作,若
connect又未指定,那么绑定操作只好通过套接字的写操作(sendto、sendmsg)来指定目的地址、端口,这时
套接字本地地址不会指定,为通配地址,而本地端口由内核指定,第一次sendto操作之后,插口的本地端口经
过内核指定之后就不会更改。
     对于TCP协议:若未指定绑定操作,可以通过下面connect操作来由内核负责套接字的绑定操作。内核会根
据套接字中的目的地址来判断外出接口,然后指定该外出接口的IP地址为插口的本地地址。Connect操作对于TCP
协议的客户端是必不可少的,必须指定。

3.connect()之后

客户端就能从服务端获取数据,也能发送数据给服务端。

从而实现网络通讯。

**************************************************************************************************************​​




简易ftp服务器带有的功能

客户端输入指令

1 LS:可列出服务端当前目录下的所以文件。

2 PWD:可获取服务端当前路径。

3 CD :可进入服务端相应文件夹,也可返回服务端上层目录利用cd ..指令。

4 GET:可从服务端获取对应文件到客户端。

5 PUT:可将客户端对应文件上传到服务端。

客户端本地指令

1 LLS:查看客户端本地文件。

2 LCD:进入客户端本地的某个文件。

退出:QUIT  输入quit客户端断开与服务端连接。




一.服务端代码实现

服务端需要包含的头文件

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>

编写的头文件config.h

#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


struct Msg
{
	int type;
	char data[1024];   //存放一些读取数据,服务端与客户端都可访问
	char secondBuf[128];  //用于GET或者PUT指令,为对需要执行的文件的内容的读和写。
 
};

先看主函数

int main(int argc,char**argv)//可自主输入ip地址,和端口号
{
	
	int con_fd;
	int n_read;
	int sk_fd;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	struct Msg msg;

	memset(&s_addr,0,sizeof(struct sockaddr_in));  //使用结构体前先初始化
	memset(&c_addr,0,sizeof(struct sockaddr_in));  //使用结构体前先初始化
	//1.socket
	sk_fd = socket(AF_INET,SOCK_STREAM,0);  ///创建套接字
	if(sk_fd == -1)
	{
		perror("socket:");
		exit(-1);
	}
	if(argc != 3)  //调试信息如果输入的执行文件(1.文件名,2.ip地址,3.端口号)不足3个,则退出程序,重新输入
	{
	perror("main");
	exit(-2);
	}
	///可参考文章前面
	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(sk_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

	//3.listen   ///监听
	listen(sk_fd,10);
	
	//4.accept      //连接
		int cln = sizeof(struct sockaddr_in);
	while(1){
		 con_fd = accept(sk_fd,(struct sockaddr *)&c_addr,&cln);
		if(con_fd == -1)
		{
			perror("accept:");
		}

		printf("gain connect from client:%s\n",inet_ntoa(c_addr.sin_addr));
		//5.read
		if(fork() == 0)    
		{
	
		while(1){	
			memset(msg.data,0,sizeof(msg.data));
			 n_read = read(con_fd,&msg,sizeof(msg));
			if(n_read == 0)
			{
				printf("client out\n");
				break;
			}
			else if(n_read>0){
				msg_handler(msg,con_fd);	
				}
	

			}
		}
	}
	close(con_fd);
	close(sk_fd);
	return 0;
}

单独来看while(1)里的程序

int cln = sizeof(struct sockaddr_in);
    while(1){
         con_fd = accept(sk_fd,(struct sockaddr *)&c_addr,&cln);
        if(con_fd == -1)   调试信息 ,当没有客户端连接时,accept()会阻塞。当有客户端连入
        {                            时,成功返回0,失败返回-1,并且利用perror显示原因
            perror("accept:");
        }

        printf("gain connect from client:%s\n",inet_ntoa(c_addr.sin_addr));//如果用户成功接入,在服务端打印接入的客户端的ip地址。
        //5.read
        if(fork() == 0)  ///利用父子进程,使父子进程同时执行不同的代码段,这在网络服务进程中很常见,父进程等待客户端的服务请求。当这种请求到达时父进程调用fork(),使子进程处理此请求。父进程继续等待下一个请求到来。 fork的返回值大于>0时为父进程,返回值等于0时为子进程。
        {
    ///用子进程来处理客户端的请求
        while(1){    
            memset(msg.data,0,sizeof(msg.data));  //清空msg.data里的数据,初始化
             n_read = read(con_fd,&msg,sizeof(msg)); //读取从客户端写入的内容,读到msg里用&地址符号,读取的大小为整个msg变量的大小。
            if(n_read == 0)
            {
                printf("client out\n");
                break;
            }
            else if(n_read>0){       /如果读到数据了 调用msg_handler(msg,con_fd)函数处理数据,

该函数看下文。
                msg_handler(msg,con_fd);    
                }
    

            }
        }
    }

**************************************************************************************************************

 msg_handler(msg,con_fd)函数

void msg_handler(struct Msg msg,int fd) //两个参数,1.为存放数据的结构体,2.为accept()函数返回的描述符。
{
    char dataBuf[1024]={0};  
    char *file = NULL;
    int fdfile;
    int w;    
    printf("cmd:%s\n",msg.data);    ///打印客户端输入的指令。
    int ret = get_cmd_type(msg.data); /// 利用get_cmd_type()函数判断 ret的返回值,再进行选择分支。
    

int get_cmd_type(char *cmd) 
{
	if(!strcmp("ls",cmd))          return  LS;  
	if(!strcmp("quit",cmd))        return  QUIT;///利用比较函数strcmp()来处理这些只有单个字符串的指令
	if(!strcmp("pwd",cmd))         return  PWD;///客户端输入的指令与相对指令比较,如果完全相同,返回相对应的指令
												///用于switch函数。

	if(strstr(cmd,"cd")!=NULL)      return  CD;
	if(strstr(cmd,"get")!=NULL)     return  GET;///strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。
	                                            如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
	if(strstr(cmd,"put")!=NULL)     return  PUT;
	
	return 100;
}
switch(ret)
	{
		case LS:
		case PWD:
			msg.type = 0;
			FILE *r = popen(msg.data,"r");//popen函数利用第一个参数的指令执行一个shell以运行命令来开启一个进程,第二个参数是"r"则文件指针连接到
			//command的标准输出;如果type是"w"则文件指针连接到command的标准输入。


			fread(msg.data,sizeof(msg.data),1,r);//从popen的返回指针”r“里面读取数据到msg.data,一次读sizeof(msg.data)这么多,读一次。
			write(fd,&msg,sizeof(msg));//1.写进fd文件指针,在msg地址下写msg的内容,写多大?整个msg的大小。
			break;
		case CD:
			msg.type = 1;
			char *dir = getDir(msg.data); ///利用getDir()函数取出cd xx后,的返回值为xx,
			printf("dir:%s\n",dir);//将xx的字符串形式打印出来。
			chdir(dir);//chdir 是C语言中的一个系统调用函数(同cd),用于改变当前工作目录,其参数为Path 目标目录,可以是绝对目录或相对目录
						//成功返回0 ,失败返回-1
			break;	
		case GET:
			file = getDir(msg.data); //将get xx 的返回值xx等于file这个字符指针
			if(access(file,F_OK) == -1)///access函数用来判断指定的文件或目录是否存在(F_OK),
			//已存在的文件或目录是否有可读(R_OK)、可写(W_OK)、可执行(X_OK)权限。F_OK、R_OK、W_OK、X_OK这四种方式通过access函数中的第二个参数mode指定。如果指定的方式有效,则此函数返回0,否则返回-1。

			{
				strcpy(msg.data,"NO this file");//若get的文件不存在向客户端写”NO this file“,客户端打印。
				write(fd,&msg,sizeof(msg));
			}
			else{
				msg.type = DOFILE;//若文件存在,提醒客户端创建文件,详情看客户端代码。
				fdfile = open(file,O_RDWR);//open一个get xx的xx这个文件,权限为可读可写。
				read(fdfile,dataBuf,sizeof(dataBuf));//读open的这个文件的内容,读到dataBuf里面来,读整个databuf的大小。
				close(fdfile);//读完之后关闭这个文件。
				
				strcpy(msg.data,dataBuf);//利用拷贝函数,将读到的dataBuf的内容拷贝进msg.data里。
				write(fd,&msg,sizeof(msg));//写进fd里,写整个msg的内容和大小,主要为了客户端那边读取数据在创建的文件里写入。

			}
			break;
		case PUT:
			fdfile = open(getDir(msg.data),O_RDWR|O_CREAT,0777);//open一个客户端发来的put xx ,xx这个文件,若文件不存在则创建,权限为可读可写
																//0777代表该文件拥有者对该文件拥有读写操作的权限
																//该文件拥有者所在组的其他成员对该文件拥有读写操作的权限
																//其他用户组的成员对该文件也拥有读写操作权限	
			w = write(fdfile,msg.secondBuf,strlen(msg.secondBuf));//会把msg.secondBuf的内容写到fdfile这个文件指针里。
			if( w != -1)
			{
				printf("put success\n");// 调试信息,如果写成功则打印信息提醒。
				fflush(stdout);//
			}
			close(fdfile);//open的文件,进行读写操作完之后,必须关闭。
			break;
		case QUIT://客户端退出。
			printf("client quit!\n");
			exit(-1);	
	
	}


}

getDir()函数

char * getDir(char *cmd)
{
	char *p;
	p = strtok(cmd," ");//strok分割字符串,以” 空格“分割字符串,返回值为cmd里的字符串
                        //
	p = strtok(NULL," ");//当执行完这个后返回值为空格后的字符串。一般搭配使用,用来返回第二个
                           //字符串来实现我们ftp服务器想要的功能。

	return p;

}

 



二、客户端代码实现



客户端需要包含的头文件

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>



编写的config.h头文件

#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


struct Msg
{
	int type;
	char data[1024];   //存放一些读取数据,服务端与客户端都可访问
	char secondBuf[128];  //用于GET或者PUT指令,为对需要执行的文件的内容的读和写。
 
};

先看主函数

int main(int argc,char **argv)
{

	int c_fd;
	int con;
	int n_read;
	struct sockaddr_in c_addr;
	struct Msg msg;

	memset(&c_addr,0,sizeof(struct sockaddr_in));//清空结构体,初始化。
	//1.socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);//创建socket套接字,返回网络描述符。
	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);

	// connect

	con = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));//与服务端进行连接。与服务端accept函数相似,参数也相似,可参考上文。	
	if(con == -1)
	{
		perror("connect:");//连接失败的调试信息
		exit(0);
	
	}
	printf("connect ...\n");//连接成功打印该信息
	int mark = 0;
	
	while(1){
		memset(msg.data,0,sizeof(msg.data));
		if(mark == 0)  printf(">");

		gets(msg.data);
		
		if(strlen(msg.data) == 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;
}

单独来看while(1)里的程序

int mark ==0;

while(1){
        memset(msg.data,0,sizeof(msg.data));
//清空,初始化
        if(mark == 0)  printf(">");//为了客户端输入界面看起来比较舒服

        gets(msg.data);//输入对应指令。
        
        if(strlen(msg.data) == 0)
//如果输入的内容为空,则重新再次输入,利用continue。
        {
            if(mark == 1)
            {
                printf(">");
            }
            continue;
//下面程序不走了,重新从循环开始走,回到while(1)开头,重新循环
        }
        mark = 1;
//如果输入不为空;往下继续
        
        int ret = cmd_handler(msg,c_fd);
//利用cmd_handler函数判断ret的返回值。

int cmd_handler(struct Msg msg,int fd)
{
	char *dir = NULL;
	char buf[32];
	int ret;
	int filefd;

	ret = get_cmd_type(msg.data);

	switch(ret)
	{
		case LS:
		case PWD:
		case CD:
			msg.type = 0;
			write(fd,&msg,sizeof(msg));//write()会把参数地址所指的内存写入sizeof(msg)个字节到参数fd所指的文件内。写的时服务端写入的内容。
			break;

		case GET:
			msg.type = 2;
			write(fd,&msg,sizeof(msg));///write()会把参数地址所指的内存写入sizeof(msg)个字节到参数fd所指的文件内。写的是客户端需要从服务端上得到的文件的内容。
			break;

		case PUT:
			strcpy(buf,msg.data);//输入的指令拷贝给buf。
			dir = getdir(buf);//一样的取 put xx ,xx这个字符串
			if(access(dir,F_OK) == -1)//判断xx这个文件是否存在。
			{
				printf("%s not exsit\n",dir);//不存在的话,打印提示信息。
			}
			else{
				filefd = open(dir,O_RDWR);//存在的话,打开这个xx文件权限为可读可写。
				read(filefd,msg.secondBuf,sizeof(msg.secondBuf));//读xx文件里的内容读到
                 //msg.secondBuf里,读的大小为整个seconBuf的大小。
				close(filefd);//读完后关闭。
				write(fd,&msg,sizeof(msg));//把整个msg里的内容写到fd,为服务端读写xx文件的 
  //内容做准备。
			}
			break;

		case LLS:
			system("ls");//本地客户端展示客户端当前目录的文件。
			break;

		case LCD://进入到客户端的相对应文件。
			dir = getdir(msg.data);
			chdir(dir);
			break;

		case QUIT://断开连接
			strcpy(msg.data,"quit");//告诉服务端,我断开连接了
			write(fd,&msg,sizeof(msg));
			close(fd);
			exit(-1);

	}

	return ret;

}

        if(ret>IFGO)//
        {
            putchar('>');
            fflush(stdout);
//立刻输出,puchar的内容
            continue;
        }
        if(ret == -1)
//没有这个指令,重新输入
        {
            printf("command not \n");
//打印提醒信息
            printf(">");
            fflush(stdout);
            continue;
//回到while(1)开头,重新循环。
        }
        handler_server_message(c_fd,msg);
//处理服务端的函数
    }
    return 0;
}

handler_server_message()函数:在客户端输出从服务端获取的数据信息。

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));//read函数(读取文件)read函数可以读取文件。读取文件指从某一个已打开地文件中,读取一定数量地字符,然后将这些读取的字符放入某一个预存的缓冲区内,供以后使用
	
	if(n_read == 0)//如果读的数据为0,打印服务器已退出。
	{
		printf("server is out quit\n");
		exit(-1);
	}
	else if(msgget.type == DOFILE)//如果有数据读到,判断type是不是等于DOFILE。可参考服务端的GET指令。如果等于执行以下操作。
		{
			char *p = getdir(msg.data);//返回get xx ,xx这个字符串,赋值给字符型指针。
			newfilefd = open(p,O_RDWR|O_CREAT,0600);//open这个xx文件,如果不存在则创建
                                                   //0600->仅拥有者具有文件的读取和写入权限
			write(newfilefd,msgget.data,strlen(msgget.data));//从msgget.data里写数据到newfilefd这个文件描述符,写全部内容。为了让xx这个文件和服务端中的xx文件内容一致。
			putchar('>');
			fflush(stdout);//这个简单的c程序中用到了fflush(stdout),目的是清空缓冲,强制结果马上显示到屏幕上。
			
		}

	else{
		putchar('\n');
		printf("*************************************\n");//为了客户端显示数据的美观
		printf("\n%s\n",msgget.data);//打印得到的服务端的数据
		printf("*************************************\n");
		putchar('\n');

		putchar('>');
		fflush(stdout);
	}
}

**************************************************************************************************************

成果展示: 

1.左边为服务端,右边为客户端。连接成功后服务端显示客户端的ip地址。客户端显示连接成功

2.客户端输入 “ls”,客户端显示服务端当前目录的所有文件。

3.客户端输入“pwd”,客户端显示服务端的当前路径。

4.输入cd xx指令

cd .. 为返回上层路径

cd ftp文件

5.输入"lls"指令,显示客户端当前路径下的所有文件。

6.输入“lcd”指令,可进入或返回客户端下的文件。

例如,进入IPC文件

7.输入“get xx”指令

需要从服务端get得到a.out 文件到客户端的路径下,如图所示。

 8.输入“put xx”指令

需要把客户端路径下的struct.c文件放到服务端当前路径下。



总结

这是一个简易的ftp服务器,可以实现多机间的通讯,同时可对服务器端的文件进行操作,可以得到服务端文件,也可以发送客户端文件给服务端,功能不多,适合刚接触linux网络编程的伙伴练手,将来有空会继续添加功能。一个linux编程小白。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值