1. 程式人生 > >linux下的簡單檔案伺服器和客戶端程式

linux下的簡單檔案伺服器和客戶端程式

本文是我的一次作業,由於花了很多精力,記下來以後可能還會用到。程式碼部分是從老師那拷貝的,作業是實現程式碼中沒有實現的put和delete命令對檔案的操作。我對程式碼的理解都做了標註,有點亂,但閱讀方便。本程式的命令要求

Dir/ls

後接字串,列出伺服器的某個目錄的內容

Get

後接兩個字串,下載遠端檔案到本地目錄

Delete

後接字串,刪除遠端檔案

Put

後接字串,上傳檔案到遠端目錄


服務端和客戶端都有一個user資料夾儲存上傳和下載的檔案

本文全部程式碼和資源在http://download.csdn.net/detail/u012296503/9513179可下載

服務端程式碼server.c

#include	"unp.h"
#include <string.h>
#include <unistd.h>
#include <dirent.h>
void ftpserv(int sockfd);
void sig_chld(int signo);
ssize_t Readline2(int fd, void *ptr, size_t maxlen);
int main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
    
    // 套接字地址結構的長度,一般為unit32_t
	socklen_t			clilen;
    
    // 網際套接字地址結構
	struct sockaddr_in	cliaddr, servaddr;
	void				sig_chld(int);
    
    // family是AF_INET表示地址族,type是SOCK_STREAM表示位元組流套接字,protocol是0表示TCP傳輸協議
    // listenfd是套接字描述符
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
    /*
      以下程式碼,一直到listen函式,表示在待捆綁到該TCP套接字的網際介面套接字地址結構中填入
      通配地址(INADDR_ANY)和伺服器的埠號為21000.捆綁通配地址是在告知系統,要是系統是
      多宿主機,我們將接受目的地址為任何本地介面的連線。在選擇通配地址和指定埠號時,核心
      選擇IP地址,程序指定埠號。
    
    */
    
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(21000);
    
    // bind將一個本地協議地址賦予一個套接字,對於TCP伺服器,就限定了該套接字只接受那些目的地為
    // 為這個IP地址的客戶連線。
	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
    // listen將該套接字裝換成一個監聽套接字,導致套接字從CLOSED狀態轉變成LISTEN狀態。
	Listen(listenfd, LISTENQ);
    
    // 當SIGCHLD訊號發生,函式sig_chld就會被呼叫
	Signal(SIGCHLD, sig_chld);	/* must call waitpid() */

	for ( ; ; )
	{
		clilen = sizeof(cliaddr);
        
		if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)
		{
			if (errno == EINTR)
				continue;		/* back to for() */
			else
				err_sys("accept error");
		}

		if ( (childpid = Fork()) == 0)
		{	/* child process */
			Close(listenfd);	/* close listening socket */
			ftpserv(connfd);	/* process the request */
			exit(0);
		}
		Close(connfd);			/* parent closes connected socket */
	}
}



void ftpserv(int sockfd)
{
	ssize_t		n;
	char		buf[MAXLINE];
	char		sendline[MAXLINE];
	char wdpath[200];
    
    // char * getcwd(char * buffer, size_t size);會將當前的工作目錄絕對路徑複製到引數buffer所指的記憶體空間,
    // 引數size 為buffer的空間大小。獲得當前目錄
	
    if(getcwd(wdpath,200)==NULL)return ;
	strcat(wdpath,"/user");

	char dirname[1000];
	char dirlen[7];

	DIR *pdir;
	struct dirent *pent;
	if ((pdir = opendir(wdpath)) == NULL)
	{
		fprintf(stderr, "open dir failed.\n");
		return;
	}


again:
	while ( (n = Readline2(sockfd, buf, MAXLINE)) > 0)
	{
	    //Writen(sockfd, buf, n);
        fprintf(stderr,"n=%d\nbuf=%s",n,buf);
        
        /*作用於字串buf,以包含在" \n"中的字元為分界符,將buf切分成一個個子串;
          cmd指向第一個子串。要想遍歷子串需要一個迴圈
          while(cmd != NULL){
              printf("%s",cmd);
              cmd = strtok(NULL," \n"); //指標後移
          }
        */
	    char * cmd=strtok(buf," \n");

	    if( strcmp(cmd,"dir") ==0 || strcmp(cmd,"ls")==0)
	    {
	        char * pdirname=dirname;
	        int lines=0;

            // 從目錄流中讀取所有的目錄項,並拷貝該目錄結構的d_name欄位
            while (1)
            {
                    pent = readdir(pdir);
                    if (pent == NULL) break;
                   // fprintf(stderr, "%5d %s\n",pent->d_ino, pent->d_name);
                    strcpy(pdirname,pent->d_name);
                    strcat(pdirname,"\r\n");
                    // 輸出目錄下的各個檔案
                    fprintf(stderr, "%d %s",strlen(pdirname), pdirname);
                    lines++;
                    
                    pdirname += strlen(pent->d_name)+2;

            }
            *pdirname=0;
            // 將lines列印到字串中
            sprintf(dirlen,"%d\n",lines);
            fprintf(stderr,"lines %d\n",lines);
            
            // 將dirlen寫入套接字,以便客戶端可以讀取,此處寫入行數
            Writen(sockfd, dirlen, strlen(dirlen));
            
            // 將目錄中的檔名寫入套接字中,以便客戶端讀取
            Writen(sockfd,dirname,strlen(dirname));
            
            // 目錄項歸起始位置,以便下次dir命令重新讀取目錄項。
            rewinddir(pdir);
	    }
	    else if(strcmp(cmd,"get")==0)
	    {

	        char filename[100];
            // 拼接目錄
            strcpy(filename,wdpath);
            strcat(filename,"/");
            
            // strtok(NULL," \n")是get後面的檔名
            strcat(filename, strtok(NULL," \n"));
            FILE *fp=0;

            if((fp=fopen(filename,"rb"))==NULL)
            {
                fprintf(stderr,"failed open file %s",filename);
                return;
            }
            fprintf(stderr, "open file %s.\n",filename);

            long filelen;
            
            /*
                定義函式 int fseek(FILE * stream,long offset,int whence);
                函式說明 fseek()用來移動檔案流的讀寫位置。引數stream為已開啟的檔案指標,引數offset為根據引數whence來移動讀寫位置的位移數。
                引數 whence為下列其中一種:
                SEEK_SET從距檔案開頭offset位移量為新的讀寫位置。SEEK_CUR 以目前的讀寫位置往後增加offset個位移量。
                SEEK_END將讀寫位置指向檔案尾後再增加offset個位移量。
                當whence值為SEEK_CUR 或SEEK_END時,引數offset允許負值的出現。
            
            */
            fseek(fp,0,SEEK_END);
            
            /*
                定義函式 long ftell(FILE * stream);
                函式說明 ftell()用來取得檔案流目前的讀寫位置。引數stream為已開啟的檔案指標。
                返回值 當呼叫成功時則返回目前的讀寫位置,若有錯誤則返回-1,errno會存放錯誤程式碼。
                錯誤程式碼 EBADF 引數stream無效或可移動讀寫位置的檔案流。
            
            */
            filelen=ftell(fp);
            // rewind()用來把檔案流的讀寫位置移至檔案開頭
            rewind(fp);
            char flen[7];
            sprintf(flen,"%d\n",(int)filelen);
            fprintf(stderr,"filesize %s\n",flen);
            
            // 伺服器端算出檔案的大小,並寫入套接字以供客戶端讀取
            Writen(sockfd, flen, strlen(flen));
            // 同時顯示出來檔案的大小 
            fprintf(stderr, "sent filesize=%s",flen);

            long left=filelen;
            
            
            
            /*
            .一般呼叫形式 
              fread(buffer,size,count,fp); 
              fwrite(buffer,size,count,fp); 
             說明 
              (1)buffer:是一個指標,對fread來說,它是讀入資料的存放地址。對fwrite來說,是要輸出資料的地址。 
              (2)size:要讀寫的位元組數; 
              (3)count:要進行讀寫多少個size位元組的資料項; 
              (4)fp:檔案型指標。 
            
            
            */
            
            // 如果檔案大小大於MAXLINE就分次讀,然後寫入套接字。
            while(left>MAXLINE)
            {
                fread(sendline,MAXLINE,1,fp);
                Writen(sockfd, sendline, MAXLINE);
                left-=MAXLINE;
            }
            
            // 對於剩下的部分再處理
            if(left<=MAXLINE)
            {
                fread(sendline,left,1,fp);
                Writen(sockfd, sendline, left);
            }

            fprintf(stderr, "file sent done.\n");
            fclose(fp);




        } /**/
	   else if(strcmp(cmd,"put")==0){
            if(Readline2(sockfd,recvline,MAXLINE) == 0){
                err_quit("str_cli: client terminated prematurely");
            }
            int bytes = atoi(recvline);
            fprintf(stderr,"file length = %d.\n",bytes);
            int left = bytes;
            FILE *fp = 0;
            if(left > 0){
                char filename[100];
                strcpy(filename,wdpath);
                strcat(filename,"/");
                strcat(filename,strtok(NULL," \n"));
                if((fp = fopen(filename,"wb")) == NULL){
                    fprintf(stderr,"failed open file %s",filename);
                    return;
                }

                fprintf(stderr,"file create: %s.\n",filename);

            }

            while(left > MAXLINE){
                    Readn(sockfd,recvline,MAXLINE);
                    left -= MAXLINE;
                    fwrite(recvline,MAXLINE,1,fp);
            }
            if(left <= MAXLINE){

                Readn(sockfd,recvline,left);
                fwrite(recvline,left,1,fp);
            }

            fprintf(stderr,"file writen done.\n");
            fclose(fp);




        }
	    else if(strcmp(cmd,"delete")==0){
            char filename[100];
            char name[30];
            strcpy(filename,wdpath);
            strcat(filename,"/");

            strcat(filename,strtok(NULL," \n"));
            printf("filename is %s\n",filename);
            sprintf(name,"%s\n",filename);
            Writen(sockfd,name,strlen(name));
            if(remove(filename) == 0){
                fprintf(stderr,"file delete successfully.\n");

            }else{
                fprintf(stderr,"file don't exist.\n");
            }


        }


        if (n < 0 && errno == EINTR)
            goto again;
        else if (n < 0)
            err_sys(" read error");
        //closedir(pdir);

	}
	fprintf(stderr,"n=%d\nbuf=%s",n,buf);


}

void sig_chld(int signo)
{
	pid_t	pid;
	int		stat;

	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}

ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t	n, rc;
	char	c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
again:
		if ( (rc = read(fd, &c, 1)) == 1) {
			*ptr++ = c;
			if (c == '\n')
				break;	/* newline is stored, like fgets() */
		} else if (rc == 0) {
			*ptr = 0;
			return(n - 1);	/* EOF, n - 1 bytes were read */
		} else {
			if (errno == EINTR)
				goto again;
			return(-1);		/* error, errno set by read() */
		}
	}

	*ptr = 0;	/* null terminate like fgets() */
	return(n);
}
/* end readline */

ssize_t
Readline2(int fd, void *ptr, size_t maxlen)
{
	ssize_t		n;

	if ( (n = readline(fd, ptr, maxlen)) < 0)
		err_sys("readline error");
	return(n);
}


客戶端client.c

#include <stdio.h>
#include <stdlib.h>
#include	"unp.h"
void ftp_cli(FILE *fp, int sockfd);
ssize_t Readline2(int fd, void *ptr, size_t maxlen);
int main(int argc, char **argv)
{
	int					sockfd;
    
    // 網際套接字地址結構
	struct sockaddr_in	servaddr;
    
	if (argc != 2) 	err_quit("usage: tcpcli <IPaddress>");
    
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(21000);
    // 將IP地址從數值形式轉化為表示式形式
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    // 呼叫connect函式是激發TCP的三路握手過程
	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
	ftp_cli(stdin, sockfd);		/* do it all */
	exit(0);
}


void ftp_cli(FILE *fp, int sockfd)
{
	char sendline[MAXLINE], recvline[MAXLINE];
	char cmd [MAXLINE];

    // 獲得當前目錄
	char wdpath[200];
	if(getcwd(wdpath,200)==NULL)return ;
	strcat(wdpath,"/user");

	for ( ; ; )
	{
	    fprintf(stderr, "myftpclient-->");
        
        // 使用fputs和fgets從檔案指標或者標準輸入輸出裝置讀寫行資料。
        if (Fgets(sendline, MAXLINE, fp) == NULL) return;
		else fputs(sendline,stdout);
		strcpy(cmd,sendline);
		//int i;
		//for(i=0;i<strlen();i++)
		//fprintf(stderr,"%d\n",(int)(sendline[i]));
		char * pcmd=strtok(cmd," \n");
		if(strcmp(pcmd,"dir")==0||strcmp(pcmd,"ls")==0)
		{
           // 將輸入的字串序列寫入套接字,以便在伺服器端讀取
           Writen(sockfd, sendline, strlen(sendline));
           
           // 從伺服器讀取行數在recvline中
		    if (Readline2(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
            // 將字串轉化為整數
            int lines=atoi(recvline);
            int i;
			for(i=0;i<lines;i++)
			{
			    Readline2(sockfd, recvline, MAXLINE);
			    fprintf(stderr,recvline);
			}
        }
        else if(strcmp(pcmd,"get")==0)
		{
            Writen(sockfd, sendline, strlen(sendline));
		    if (Readline2(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
            int bytes=atoi(recvline);
            fprintf(stderr, "file length = %d.\n",bytes); 
            int left=bytes;
            FILE *fp=0;
            if(left>0)
            {
                char filename[100];
                strcpy(filename,wdpath);
                strcat(filename,"/");
                // strtok(NULL," \n")是get後面的檔名
                strcat(filename, strtok(NULL," \n"));
                if((fp=fopen(filename,"wb"))==NULL)
                {
                    fprintf(stderr,"failed open file %s",filename);
                    return;
                }
                // 打印出檔案路徑
                fprintf(stderr, "file create: %s.\n",filename);
            }
            // 從套接字中讀出資料然後寫入檔案中
            while(left>MAXLINE)
            {
                Readn(sockfd, recvline, MAXLINE);
                left-=MAXLINE;
                fwrite(recvline,MAXLINE,1,fp);
            }
            if(left<=MAXLINE)
            {
                Readn(sockfd, recvline, left);
			    //fprintf(stderr,recvline);
			    fwrite(recvline,left,1,fp);
            }
            fprintf(stderr, "file writen done.\n");
            fclose(fp);
        }
        else if(strcmp(pcmd,"quit")==0){
		    fprintf(stderr, "See you!.\n");
		    return;
	}<pre name="code" class="plain"><span style="white-space:pre">	</span>else if(strcmp(pcmd,"delete")==0){
		    Writen(sockfd,sendline,strlen(sendline));

            if (Readline2(sockfd, recvline, MAXLINE) == 0){
				err_quit("str_cli: server terminated prematurely");
            }else
                fprintf(stderr,"file %s delete successfully.\n",recvline);

        }else if(strcmp(pcmd,"put") == 0){

            Writen(sockfd, sendline, strlen(sendline));
            char filename[100];
            strcpy(filename,wdpath);
            strcat(filename,"/");

            strcat(filename,strtok(NULL," \n"));
            FILE *fp = 0;
            if((fp = fopen(filename,"rb")) == NULL){
                fprintf(stderr,"failed open file %s",filename);
                return;
            }
            fprintf(stderr,"open file %s.\n",filename);

            long filelen;

            fseek(fp,0,SEEK_END);
            filelen = ftell(fp);
            rewind(fp);
            char flen[7];
            sprintf(flen,"%d\n",(int)filelen);
            fprintf(stderr,"filesize %s\n",flen);

            Writen(sockfd,flen,strlen(flen));
            fprintf(stderr,"sent filesize=%s",flen);
            long left = filelen;

            while(left > MAXLINE){

                    fread(sendline,MAXLINE,1,fp);
                    Writen(sockfd,sendline,MAXLINE);
                    left -= MAXLINE;
            }

            if(left <= MAXLINE){

                fread(sendline,left,1,fp);
                Writen(sockfd,sendline,left);

            }

            fprintf(stderr,"file send done.\n");

            fclose(fp);


        }else{
            fprintf(stderr, "usage:cmd [args].\ncmd=\tdir\tls\tget\tquit\n");
        }


	}
}

ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t	n, rc;
	char	c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
again:
		if ( (rc = read(fd, &c, 1)) == 1) {
			*ptr++ = c;
			if (c == '\n')
				break;	/* newline is stored, like fgets() */
		} else if (rc == 0) {
			*ptr = 0;
			return(n - 1);	/* EOF, n - 1 bytes were read */
		} else {
			if (errno == EINTR)
				goto again;
			return(-1);		/* error, errno set by read() */
		}
	}

	*ptr = 0;	/* null terminate like fgets() */
	return(n);
}
/* end readline */

ssize_t
Readline2(int fd, void *ptr, size_t maxlen)
{
	ssize_t		n;

	if ( (n = readline(fd, ptr, maxlen)) < 0)
		err_sys("readline error");
	return(n);
}

執行部分截圖

服務端



客戶端