linux下的簡單檔案伺服器和客戶端程式
阿新 • • 發佈:2019-01-26
本文是我的一次作業,由於花了很多精力,記下來以後可能還會用到。程式碼部分是從老師那拷貝的,作業是實現程式碼中沒有實現的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); }
執行部分截圖
服務端
客戶端