编译运行:

文档说明:

1、整体程序设计

服务器程序是一个死循环,处理一次连接之后不退出,而客户端程序只处理一个连接就可以了。

2、客户端程序设计

客户端程序的主要任务:

a、分析用户输入的命令;

b、根据命令向服务器端发出请求;

c、等待服务器返回请求的结果。

cd命令和ls命令处理客户机文件系统中的目录,不需要和服务器进行通信,因此不需要建立连接。其它的除了quit命令外,都需要和服务器建立连接,将请求发送给服务器,等待服务器返回结果。quit命令向服务器发出连接结束的请求,之后客户端程序退出,因此不需要等待服务器返回结果。

3、服务器端程序设计

a、分析请求代码;

b、根据请求代码做相应的处理;

c、等待返回结果或应答信息。

有两个主要的环节需要明确:通信协议与服务器模型。

本程序的通信协议分为两种:对于get命令、put命令和!ls命令需要传输文件内容的命令,采用”四次握手“的通信协议;对于!cd命令不需要传输文件内容的命令,采用”两次握手“的通信协议。

“四次握手”:

例如get命令:首先发出GET请求,服务器程序接收到请求后发送确认信息或错误应答码,接收到确认信息后客户端程序发送RDY应答信息,服务器端开始传输文件内容。

“两次握手”:

由于客户端程序是交互式的,因此本程序采用多进程并发服务器模型,如下:

int main(void)
{socker();bind();listen();while(1){accept();if(fork() == 0)   // 子进程处理客户端请求{while(1){close(); // 子进程关闭监听套接字read();    write();}close();   exit(); // 子进程关闭连接套接字并退出}elseclose();   // 父进程关闭连接套接字}close();  return 0;   // 父进程关闭监听套接字
}

服务器程序采用并发的方式处理客户端的连接,因此main函数调用fork函数创建一个子进程与客户端的通信,而父进程继续监听其他连接请求。对于交互式的网络程序,这种服务器程序模型不仅仅是可以提高程序执行的效率,更重要的是只有这样才能保证服务器程序不会因为一个连接请求而产生时间阻塞,导致其他连接请求得不到处理。

附:

#include <stdlib.h> // exit

#include <unistd.h> // STDOUT_FILENO

#include <sys/stat.h> // struct stat 获取文件状态

#include <fcntl.h>

#include <sys/socket.h>

#include <netinet/in.h>

宏定义调试语句

#define DEBUG_PRINT 1

#ifdef DEBUG_PRINT

#define DEBUG(format, ...) printf(“FILE: “__FILE__”, LINE: %d: “format”\n”, __LINE__, ##__VA_ARGS)

#else

#define DEBUG(format, ...)

#endif

Fflsuh(stdout); // 冲洗缓冲区,保证提示符显示

Bzero(&command, sizeof(struct str_command)); // 清零内存空间

Perror(“fail to close”); // 错误提示,系统将自动输出错误号对应的错误信息,此处无需自己添加换行符

System(“ls . > temp.txt”); // system 执行命令

Char *path;  chdir(path); // 改变当前工作目录

网络编程

客户端

Struct sockaddr_in serv_addr;

Bzero(serv_addr, sizeof(struct sockaddr_in)); // bzero 清空地址结构

Serv_addr->sin_family = AF_INET; // AF_INET为IPv4地址族

Inet_pton(AF_INET, ip, &(serv_addr->sin_addr)); // inet_pton 将点分十进制ip转换为二进制形式,并存储在地址结构中

Serv_addr->sin_port = htons(PORT); // htons 将端口号转换为网络字节序存储在地址结构中

*sock_fd = socket(AF_INET, SOCK_STREAM, 0); // socket创建套接字

Connect(*sock_fd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr_in)); // connect 使用该套接字,和填充好的地址结构进行连接

服务端

Struct sockaddr_in ser_addr;

Bzero(ser_addr, sizeof(struct sockaddr_in));

Ser_addr->sin_family = AF_INET;

Ser_addr->sin_addr.s_addr = htonl(INADDR_ANY);

Ser_addr->sin_port = htons(PORT);

Int listenfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听套接字

Int sock_opt;

Setsockopt(listenfd, SOL_SOCKET, SO_REUESADDR, &sock_opt, sizeof(int)); // setsockopt 设置套接字选项

Bind(listenfd, (struct sockaddr *)ser_addr, sizeof(struct sockaddr_in)); // bind 绑定客户端地址,具体地址没有限制,为INADDR_ANY

Listen(listenfd, 20); // listen 监听套接字,与客户端的connect函数相互作用

Int connectfd = accept(listenfd, (struct sockaddr *)&cli_addr); // accept

读写套接字

Write(sock_fd, buf, strlen(buf)+1);

Len = Read(sock_fd, buf, MAXBUF);

字符串操作函数

忽略大小写进行比较 Strcasecmp(command.name, “get”)

复制 strcpy(dest_file, dest);

寻找指定字符在字符串中最后出现的位置 Char *p = rindex(src, ‘/’);

连接字符串 strcat(dest_file, p+1);

是否为其字串,若是则返回首次出现的地址 strstr(buf, “GET”);

文件操作

Int fd = open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); // open 打开

Struct stat stat_buf; fstat(fd, &stat_buf); // struct stat 文件状态

S_ISREG(stat_buf.st_mode) // S_ISREG 是否为普通文件

FIEL *fp = fopen(“temp.txt”, “r”); // fopen打开文件

Fgets(buf, MAXBUF, fp); // fgets

Fputs(buf, stdout); // fputs

Unlink(“temp.txt”); // unlink 删除文件

所有源代码文件汇总:

  1 /*2  * FILE: common.h3  * DATE: 201802014  * ==============5  */6 7 #include <stdio.h>8 #include <stdlib.h>     // exit9 #include <unistd.h>     // STDOUT_FILENO10 #include <string.h>11 12 #include <sys/stat.h>   // struct stat13 #include <fcntl.h>      // O_WRONLY14 15 #include <sys/socket.h> // struct sockaddr_in16 #include <netinet/in.h>17 #define PORT 800018 19 #define BUFFSIZE 3220 #define MAXBUFF 12821 22 #define DEBUG_PRINT 123 #ifdef DEBUG_PRINT24 #define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)25 #else26 #define DEBUG(format, ...)27 #endif28 29 // 全局变量声明:命令结构,存储用户输入的命令和参数30 struct str_command{31         char *name;32         char *argv[10];33 };34 35 /* 函数接口声明 */36 37 // 文件input.c中,处理用户输入38 extern int split(struct str_command *command, char *cline);39 40 // 文件command.c中,命令处理41 42 extern int do_connect(char *ip, struct sockaddr_in *serv_addr, int *sock_fd);43 44 extern int do_get(const char *src, const char *dest, int sock_fd);45 46 extern int do_put(const char *src, const char *dest, int sock_fd);47 48 extern int do_cd(char *path);49 50 extern int do_ls(char *path);51 52 extern int do_ser_ls(char *path, int sockfd);53 54 extern int do_ser_cd(char *path, int sockfd);55 56 extern int do_quit(int sock_fd);57 58 59 60 61 62 /*63  * FILE: main.c64  * DATE: 2018020165  * ===============66  */67 68 #include "common.h"69 70 int main(void)71 {72         char cline[MAXBUFF];    // 缓冲区,存储用户输入的命令73         struct str_command command;     // 命令结构,存储分解后的命令74         int sock_fd;75         struct sockaddr_in serv_addr;   // 服务器端的地址结构76 77         printf("myftp$: ");     // 打印提示符78         fflush(stdout); // fflush 冲洗,保证提示符显示79 80         while(fgets(cline, MAXBUFF, stdin) != NULL)     // fgets 得到一行命令81         {82                 // 自定义split 将命令行拆分为命令和参数83                 if(split(&command, cline) < 0)84                         exit(-1);85 86                 // strcasecmp 忽略大小写进行比较87                 if(strcasecmp(command.name, "get") == 0)88                 {89                         if(do_get(command.argv[1], command.argv[2], sock_fd) < 0)90                                 exit(-2);91                 }92                 else if(strcasecmp(command.name, "put") == 0)93                 {94                         if(do_put(command.argv[1], command.argv[2], sock_fd) < 0)95                                 exit(-3);96                 }97                 else if(strcasecmp(command.name, "cd") == 0)98                 {99                         if(do_cd(command.argv[1]) < 0)
100                                 exit(-4);
101                 }
102                 else if(strcasecmp(command.name, "ls") == 0)
103                 {
104                         if(do_ls(command.argv[1]) < 0)
105                                 exit(-5);
106                 }
107                 else if(strcasecmp(command.name, "connect") == 0)
108                 {
109                         if(do_connect(command.argv[1], &serv_addr, &sock_fd) < 0)
110                                 exit(-6);
111                 }
112                 else if(strcasecmp(command.name, "!ls") == 0)
113                 {
114                         if(do_ser_ls(command.argv[1], sock_fd))
115                                 exit(-9);
116                 }
117                 else if(strcasecmp(command.name, "!cd") == 0)
118                 {
119                         if(do_ser_cd(command.argv[1], sock_fd))
120                                 exit(-10);
121                 }
122                 else if(strcasecmp(command.name, "quit") == 0)
123                 {
124                         if(do_quit(sock_fd) < 0)
125                                 exit(-8);
126                 }
127                 else
128                 {
129                         printf("ERROR: wrong command\n");
130                         printf("Usage: command argv1 argv2, ...\n");
131                 }
132                 bzero(&command, sizeof(struct str_command));
133                 printf("myftp$: ");     // 再次打印提示符,准备接受新的命令
134                 fflush(stdout);
135         }
136
137         if(close(sock_fd) < 0)
138         {
139                 perror("fail to close");
140                 exit(-7);
141         }
142         return 0;
143 }
144
145
146
147
148
149
150 /*
151  * FILE: input.c
152  * DATE: 20180201
153  * ==============
154  */
155
156 #include "common.h"
157
158 // 宏定义的续行符后 不能有空格或其它内容
159 #define del_blank(p, cline) do{ \
160         while(cline[p]!='\0' && (cline[p]==' ' || cline[p]=='\t')) \
161                 p++; \
162         }while(0)
163
164 // 宏定义中的变量 不能与函数中的变量 同名
165 #define get_arg(arg, p, cline) do{ \
166         int j = 0; \
167         while(cline[p]!='\0' && cline[p]!=' ' && cline[p]!='\t') \
168                 arg[j++] = cline[p++]; \
169         }while(0)
170 /* 将用户输入的命令字符串分割为命令和参数,
171  * 并存储在自定义的 struct str_command中
172  * command: 存储命令和参数的结构体; cline 用户输入的命令字符串
173  */
174 int split(struct str_command *command, char *cline)
175 {
176         int i=0, p=0;
177
178         cline[strlen(cline)-1] = '\0';  // 将换行符\n替换为结束符\0
179         del_blank(p, cline);    // 过滤空格,直到遇到第一个参数
180
181         while(cline[p] != '\0')
182         {
183                 if((command->argv[i]=(char *)malloc(sizeof(char) * BUFFSIZE)) == NULL)
184                 {
185                         perror("fail to malloc");
186                         return -1;
187                 }
188                 get_arg(command->argv[i], p, cline);
189                 /*{
190                         int j = 0;
191                         while(cline[p]!='\0' && cline[p]!=' ' && cline[p]!='\t')
192                                 command->argv[i][j++] = cline[p++];
193
194                 }*/
195                 i++;
196                 del_blank(p, cline);
197         }
198
199         command->argv[i] = NULL;        // 命令参数数组以NULL结尾
200         command->name = command->argv[0];       // 命令名和第一个参数指向同一内存区域
201         return i;
202 }
203
204
205
206
207
208
209 /*
210  * FILE: command.c
211  * DATE: 20180201
212  * ===============
213  */
214
215 #include "common.h"
216 /* 处理connect命令:connect <ip-address>
217  * 与服务器进行连接
218  * ip: 字符指针,指向服务器地址
219  * serv_addr: 地址结构指针,指向服务器地质结构,在connect函数中填充
220  * sock_fd: 整型指针,指向通信套接字描述符,在connect函数中设置
221  */
222 int do_connect(char *ip, struct sockaddr_in *serv_addr, int *sock_fd)
223 {
224         bzero(serv_addr, sizeof(struct sockaddr_in));   // bzero 清空地址结构
225         serv_addr->sin_family = AF_INET;        // 使用IPv4地址族
226         // inet_pton 将点分十进制的ip地址转换为二进制形式,并存储在地址结构中
227         inet_pton(AF_INET, ip, &(serv_addr->sin_addr));
228         serv_addr->sin_port = htons(PORT);      // htons 将端口号转换为网络字节序存储在地址结构中
229
230         *sock_fd = socket(AF_INET, SOCK_STREAM, 0);     // 创建套接字
231         if(*sock_fd < 0)
232         {
233                 perror("fail to creat socket");
234                 return -1;
235         }
236         // 使用该套接字,和填充好的地址结构进行连接
237         if(connect(*sock_fd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr_in)) < 0)
238         {
239                 perror("fail to connect");
240                 return -2;
241         }
242         return 0;
243 }
244
245 /* 处理get命令:get arg1 arg2
246  * 从服务器端取得文件,文件已存在则覆盖
247  * src:源文件的绝对路径,dest:目的目录的绝对路径,sock_fd: 通信用的套接字描述符
248  * client将src_filename传递给server,由server读取文件内容并写入套接字,client读取套接字并写入至文件
249  */
250 int do_get(const char *src, const char *dest, int sock_fd)
251 {
252         char *dest_file;        // 目的路径,dest+filename
253         struct stat stat_buf;   // struct stat 文件状态
254         char *p, buf[MAXBUFF];
255         int fd, len;
256         int res = -1;   // 返回值
257
258         if(src==NULL || dest==NULL)     // 检查源文件和目的地址是不是空串
259         {
260                 printf("ERROR: wrong command\n");
261                 return -1;
262         }
263         // 如果源文件路径的最后一个字符是/,则说明源文件不是普通文件,而是目录
264         if(src[strlen(src)-1] == '/')
265         {
266                 printf("source file should be a regular file\n");
267                 return -2;
268         }
269
270         // malloc 为目标文件路径分配存储空间,由目标目录dest和源文件名组成
271         if((dest_file=(char *)malloc(sizeof(char)*(strlen(dest)+strlen(src)))) == NULL)
272         {
273                 perror("fail to malloc");
274                 return -3;
275         }
276         strcpy(dest_file, dest);
277         if(dest_file[strlen(dest)-1] != '/')
278                 strcat(dest_file, "/");
279         p = rindex(src, '/');   // rindex 取源文件路径中最后一个/的位置指针
280         strcat(dest_file, p+1);
281
282         if((fd=open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0)
283         {
284                 perror("fail to open dest_file");
285                 goto end2;
286         }
287
288         if(fstat(fd, &stat_buf) < 0)
289         {
290                 perror("fail to stat dest_file");
291                 goto end1;
292         }
293         // S_ISREG
294         // 如果目标文件已存在,但不是一个普通文件,则无法传输
295         // 否则会造成已存在的目录等其它特殊文件被覆盖
296         if(!S_ISREG(stat_buf.st_mode))
297         {
298                 printf("dest-file should be a regular file\n");
299                 goto end1;
300         }
301
302         // 向服务器server发送GET请求
303         sprintf(buf, "GET %s", src);
304         write(sock_fd, buf, strlen(buf)+1);
305         // 服务器的确认信息格式为:“OK 文件名”
306         len = read(sock_fd, buf, MAXBUFF);
307         // 如果收到的信息是ERR,表示出错
308         if(buf[0] == 'E')
309         {
310                 write(STDOUT_FILENO, buf, len);
311                 res = 0;
312                 goto end1;
313         }
314         // len = atoi(&buf[3]);
315         // 告知服务器已准备好RDY,服务器将开始传送文件内容
316         write(sock_fd, "RDY", 3);
317         // 循环读写
318         // read 套接字中服务器传入的内容,write 将读取的内容写至目标文件
319         while((len = read(sock_fd, buf, MAXBUFF)) > 0)
320         {
321                 write(fd, buf, len);
322         }
323         if(len < 0)
324         {
325                 printf("ERROR: read\n");
326                 goto end1;
327         }
328         printf("OK\n");
329         res = 0;
330 end1:
331         close(fd);
332 end2:
333         free(dest_file);        // free 释放malloc分配的内存空间
334         return res;
335 }
336
337 /*
338  * 处理put命令:put arg1 arg2
339  * 向服务器传送文件,若已存在则覆盖
340  * src:源文件的绝对路径,dest: 目标目录的绝对路径, sock_fd 通信用的套接字描述符
341  * client读取用户指定的src_filename文件内容,并写入至通信套接字;server读取套接字,并写入至文件
342  */
343 int do_put(const char *src, const char *dest, int sock_fd)
344 {
345         char *dest_file;        // 目标文件路径,由dest+filename
346         struct stat stat_buf;   // struct stat 文件状态
347         char *p, buf[MAXBUFF];
348         int fd, len;
349
350         if(src==NULL || dest==NULL)     // 检查源文件和目的地址是不是空串
351         {
352                 printf("ERROR: wrong command\n");
353                 return -1;
354         }
355         if(src[strlen(src)-1] == '/')   // 源文件名及其绝对路径
356         {
357                 printf("source file should be a regular file.\n");
358                 return -1;
359         }
360         // malloc 为目标文件名及其路径分配内存空间
361         if((dest_file=(char *)malloc(sizeof(char)*(strlen(src)+strlen(dest)))) == NULL)
362         {
363                 perror("fail to malloc");
364                 return -1;
365         }
366         strcpy(dest_file, dest);
367         if(dest_file[strlen(dest_file)-1] != '/')
368                 strcat(dest_file, "/");
369         p = rindex(src, '/');
370         strcat(dest_file, p+1);
371         // open 打开需要传输的源文件
372         if((fd=open(src, O_RDONLY)) < 0)        // open, int fd, write read
373         {
374                 perror("fail to src-file");
375                 goto end1;
376         }
377         if(fstat(fd, &stat_buf) < 0)    // struct stat 源文件状态
378         {
379                 perror("fail to open src-file");
380                 goto end2;
381         }
382         if(!S_ISREG(stat_buf.st_mode))  // 只能是普通文件
383         {
384                 fprintf(stderr, "src-file should be a regular file\n");
385                 goto end2;
386         }
387         sprintf(buf, "PUT %s", dest_file);      // 向服务器发送PUT请求
388         write(sock_fd, buf, strlen(buf)+1);
389         read(sock_fd, buf, BUFFSIZE);   // 接收服务器的确认信息
390         if(buf[0] == 'E')       // 若收到的信息是ERR,表示出错;否则得到RDY应答
391         {
392                 write(STDOUT_FILENO, buf, strlen(buf)+1);
393                 goto end2;
394         }
395         // 循环读取文件内容,并写入至通信套接字传输给服务端
396         while((len=read(fd, buf, MAXBUFF)) > 0)
397                 write(sock_fd, buf, len);
398         if(len<0)       // 读操作出错
399         {
400                 perror("fail to read");
401                 goto end2;
402         }
403         printf("OK\n");
404 end1:
405         close(fd);
406 end2:
407         free(dest_file);        // free 释放malloc分配的内存空间
408         return 0;
409 }
410
411 /*
412  * 处理ls命令:ls arg1
413  * path: 指定的目录,绝对路径
414  */
415 int do_ls(char *path)
416 {
417         char cmd[64], buf[MAXBUFF];
418         FILE *fp;
419         sprintf(cmd, "ls %s > temp.txt", path); // 拼接命令
420         system(cmd);    // system 执行命令
421         if((fp = fopen("temp.txt", "r")) == NULL)       // fopen, FILE *fp, fgets fputs
422         {
423                 perror("fail to ls");
424                 return -1;
425         }
426         while(fgets(buf, MAXBUFF, fp) != NULL)  // fgets, fputs
427                 fputs(buf, stdout);
428         fclose(fp);
429         //unlink("temp.txt");
430         return 0;
431 }
432
433 /* 处理!ls命令: !ls arg1
434  * 列出服务器中指定目录的所有文件
435  * path: 指定的目录,绝对路径
436  */
437 int do_ser_ls(char *path, int sockfd)
438 {
439         char cmd[BUFFSIZE], buf[MAXBUFF];
440         int len;
441
442         sprintf(cmd, "LS %s", path);
443         if(write(sockfd, cmd, strlen(cmd)+1) < 0)       // write 向服务器发送LS请求
444                 return -1;
445         DEBUG("===to server: %s", cmd);
446         if((len=read(sockfd, cmd, BUFFSIZE)) < 0)       // read 读取服务器的应答码
447                 return -2;
448         if(cmd[0] == 'E')       // 若应答码为ERR,表示出错
449         {
450                 write(STDOUT_FILENO, cmd, len);
451                 return 0;
452         }
453         //len = atoi(&buf[3]);
454         DEBUG("===from server: %s", cmd);
455         if(write(sockfd, "RDY", 4) < 0) // 告知服务器已准备好RDY
456                 return -3;
457         // read, write 循环读取服务端传输的内容,并输出到屏幕
458         while((len=read(sockfd, buf, MAXBUFF))>0)
459                 write(STDOUT_FILENO, buf, len);
460         if(len < 0)
461         {
462                 perror("fail to read");
463                 return -4;
464         }
465         printf("!ls OK\n");
466         return 0;
467 }
468
469 /* 处理cd命令:cd arg1
470  * path: 指定的目录,绝对路径
471  */
472 int do_cd(char *path)
473 {
474         if(chdir(path) < 0)     // chdir 改变当前工作目录
475         {
476                 perror("fail to change directory");
477                 return -1;
478         }
479         return 0;
480 }
481
482 /*
483  * 处理!cd命令: !cd arg1
484  * 进入服务器中指定的目录
485  * path: 指定的目录,绝对路径
486  * sockfd: 通信套接字描述符
487  */
488 int do_ser_cd(char *path, int sockfd)
489 {
490         char buf[BUFFSIZE];
491         int len;
492
493         sprintf(buf, "CD %s", path);
494         if(write(sockfd, buf, strlen(buf)) < 0) // write 向服务器发送CD请求
495                 return -1;
496         if((len=read(sockfd, buf, BUFFSIZE)) < 0)       // read 读取服务器的应答信息
497                 return -2;
498         if(buf[0] == 'E')       // 若应答码为ERR,表示出错
499                 write(STDOUT_FILENO, buf, len);
500         return 0;
501
502 }
503
504 /* 处理quit命令: quit
505  * 向服务器发送 关闭连接的请求,然后退出客户端程序
506  * sockfd: 通信用的套接字描述符
507  * 这次通信不需要应答码,因为客户端程序发送命令后已退出,无法处理应答码
508  */
509 int do_quit(int sock_fd)
510 {
511         char buf[4];
512         sprintf(buf, "BYE");
513         // write 向服务器发送关闭连接的请求
514         if(write(sock_fd, buf, strlen(buf)+1) != strlen(buf))
515                 return -1;
516         return 0;
517 }
518
519
520
521
522
523 #
524 # FILE: Makefile
525 # DATE: 20180201
526 # ==============
527
528 OBJECTS = main.o input.o command.o
529
530 clinet: $(OBJECTS)
531         gcc -o client -g $(OBJECTS)
532
533 main.o: common.h main.c
534         gcc -c -g main.c
535 input.o: common.h input.c
536         gcc -c -g input.c
537 command.o: common.h command.c
538         gcc -c -g command.c
539
540 .PHONY: clean
541 clean:
542         rm *.o
543
547
548
549
550
551 // ================================================
552 //              server 服务端
553 // ================================================
554
555
556 /*
557  * FILE: common.h
558  * DATE: 20180201
559  * ==============
560  */
561
562 #include <stdio.h>
563 #include <stdlib.h>
564 #include <string.h>
565 #include <fcntl.h>
566 #include <sys/stat.h>
567 #include <errno.h>
568
569 #include <sys/socket.h>
570 #include <netinet/in.h>
571 #define PORT 8000       // 端口号
572
573 #define BUFFSIZE 64
574 #define MAXBUFF 128
575
576 /* 函数结构声明, command.c文件中定义函数 */
577 int init(struct sockaddr_in *ser_addr, int *lis_fd, int sock_opt);
578 int do_put(int sockfd, char *file);
579 int do_get(int sockfd, char *file);
580 int do_ls(int sockfd, char *path);
581 int do_cd(int sockfd, char *path);
582
583
584
585
586 /* FILE: main.c
587  * DATE: 20180201
588  * ==============
589  */
590
591 #include "common.h"
592
593 int main(void)
594 {
595         struct sockaddr_in ser_addr, cli_addr;  // 服务/客户端地址结构
596         char buf[BUFFSIZE];
597         int listenfd, connfd;
598         int sock_opt, len;
599         pid_t pid;
600
601         // 自定义init初始化,得到地址结构和监听套接字描述符
602         if(init(&ser_addr, &listenfd, sock_opt) < 0)
603                 exit(-1);
604         printf("waiting connections ...\n");
605         while(1)        // while死循环,处理客户端请求
606         {
607                 // accept 接收请求
608                 if((connfd=accept(listenfd, (struct sockaddr *)&cli_addr, &len)) < 0)
609                 {
610                         perror("fail to accept");
611                         exit(-2);
612                 }
613                 if((pid=fork()) < 0)    // fork 创建子进程
614                 {
615                         perror("fail to fork");
616                         exit(-3);
617                 }
618                 if(pid == 0)    // 子进程处理连接请求,父进程继续监听
619                 {
620                         close(listenfd);        // 子进程中关闭继承而来的监听套接字
621                         // 本程序的客户端是一个交互式程序,服务器端也是交互的
622                         while(1)
623                         {
624                                 if(read(connfd, buf, BUFFSIZE) < 0)
625                                         exit(-4);
626                                 // strstr(str1, str2) 判断str2是否为str1的字串。
627                                 // 若是,则返回str2在str1中首次出现的地址;否则,返回NULL
628                                 if(strstr(buf, "GET") == buf)
629                                 {
630                                         if(do_put(connfd, &buf[4]) < 0)
631                                                 printf("error occours while putting\n");
632                                 }
633                                 else if(strstr(buf, "PUT") == buf)
634                                 {
635                                         if(do_get(connfd, &buf[4]) < 0)
636                                                 printf("error occours while getting\n");
637                                 }
638                                 else if(strstr(buf, "CD") == buf)
639                                 {
640                                         if(do_cd(connfd, &buf[4]) < 0)
641                                                 printf("error occours while changing directory\n");
642                                 }
643                                 else if(strstr(buf, "LS") == buf)
644                                 {
645                                         if(do_ls(connfd, &buf[3]) < 0)
646                                                 printf("error occours while listing\n");
647                                 }
648                                 else if(strstr(buf, "BYE") == buf)
649                                         break;
650                                 else
651                                 {
652                                         printf("wrong command\n");
653                                         exit(-5);
654                                 }
655                         }
656                         close(connfd);  // 跳出循环后关闭连接套接字描述符,通信结束
657                         exit(0);        // 子进程退出
658                 }
659                 else
660                         close(connfd);  // 父进程关闭连接套接字,继续监听
661         }
662         return 0;
663 }
664
665
666
667
668
669 /*
670  * FILE: command.c
671  * DATE: 20180201
672  * ===============
673  */
674
675 #include "common.h"
676
677 // 初始化服务器
678 // ser_addr: 服务端地址结构指针; lis_fd: 监听套接字描述符; sock_opt: 套接字选项
679 int init(struct sockaddr_in *ser_addr, int *lis_fd, int sock_opt)
680 {
681         int fd;
682
683         bzero(ser_addr, sizeof(struct sockaddr_in));    // bzero
684         ser_addr->sin_family = AF_INET; // AF_INET
685         ser_addr->sin_addr.s_addr = htonl(INADDR_ANY);  // htonl(INADDR_ANY)
686         ser_addr->sin_port = htons(PORT);       // htons(PORT)
687
688         if((fd=socket(AF_INET, SOCK_STREAM, 0)) < 0)    // socket 创建监听套接字
689         {
690                 perror("fail to creat socket");
691                 return -1;
692         }
693         // 设置套接字选项
694         setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(int));
695         // bind 绑定客户端地址
696         if(bind(fd, (struct sockaddr *)ser_addr, sizeof(struct sockaddr_in)) < 0)
697         {
698                 perror("fail to bind");
699                 return -2;
700         }
701         //  listen 监听套接字,与客户端的connec函数相互作用
702         if(listen(fd, 20) < 0)
703         {
704                 perror("fail to listen");
705                 return -3;
706         }
707         *lis_fd = fd;
708         return 0;
709 }
710
711 /* 处理来自客户端的GET命令: GET arg1 arg2
712  * 服务端读取客户端指定的文件,并写入至套接字
713  * sock_fd: 连接套接字描述符
714  * file: 客户端请求的文件及其路径
715  */
716 int do_put(int sockfd, char *file)
717 {
718         struct stat stat_buf;
719         int len, fd;
720         char buf[BUFFSIZE];
721         int res = -1;
722
723         if((fd=open(file, O_RDONLY)) < 0)       // open 客户端请求的文件
724         {
725                 write(sockfd, "ERROR: fail to open server file\n",
726                         strlen("ERROR: fail to open server file\n"));
727                 return -1;
728         }
729         if(fstat(fd, &stat_buf) < 0)    // struct stat 文件状态
730         {
731                 write(sockfd, "ERROR: fail to stat server file\n",
732                         strlen("ERROR: fail to stat server file\n"));
733                 goto end;
734         }
735         if(!S_ISREG(stat_buf.st_mode)) // 若不是普通文件,则报错
736         {
737                 write(sockfd, "ERROR: not a regular file\n",
738                         strlen("ERROR: not a regular file\n"));
739                 goto end;
740         }
741         sprintf(buf, "OK. FILE SIZE: %d", stat_buf.st_size);
742         write(sockfd, buf, strlen(buf));        // 向客户端发送应答信息:OK 文件大小
743         read(sockfd, buf, MAXBUFF);     // 等待客户端的应答信息,应答码为RDY
744         while((len=read(fd, buf, MAXBUFF)) > 0) // 循环读取文件内容,并写入通信套接字
745                 write(sockfd, buf, len);
746         if(len<0 && errno==EINTR)
747         {
748                 perror("fail to read");
749                 goto end;
750         }
751         printf("OK\n");
752         res = 0;
753 end:
754         close(fd);      // 关闭文件,注意不是关闭套接字
755         return res;
756 }
757
758 /* 处理客户端的PUT请求: put arg1 arg2
759  * 读取客户端写在通信套接字中的文件内容,并写入至文件
760  * sockfd: 连接套接字的描述符
761  * file: 指定的目标文件名及其路径
762  */
763 int do_get(int sockfd, char *file)
764 {
765         struct stat stat_buf;
766         char buf[MAXBUFF];
767         int fd, len;
768         int res = -1;
769         fprintf(stdout, "===getting file: %s\n", file);
770         // open 打开文件。打开方式是覆盖写,若文件存在则覆盖,但若是一个同名的目录则报错
771         if((fd=open(file, O_RDONLY | O_CREAT | O_TRUNC, 0644)) < 0)
772         {
773                 if(errno == EISDIR)     // 不是普通文件,而是一个目录
774                 {
775                         write(sockfd, "ERROR: server has a dir with the same name\n",
776                                 strlen("ERROR: server has a dir with the same name\n"));
777                         goto end;
778                 }
779                 else
780                 {
781                         write(sockfd, "ERROR: fail to open server file\n",
782                                 strlen("ERROR: fail to open server file\n"));
783                         goto end;
784                 }
785         }
786         if(fstat(fd, &stat_buf) < 0)    // fstat 获取文件状态
787         {
788                 write(sockfd, "ERROR: fail to stat server file\n",
789                         strlen("ERROR: fail to stat server file\n"));
790                 goto end;
791         }
792         if(!S_ISREG(stat_buf.st_mode))  // 如果不是普通文件,则报错
793         {
794                 write(sockfd, "ERROR: not a regular file\n",
795                         strlen("ERROR: not a regular file\n"));
796                 res = 0;
797                 goto end;
798         }
799         // 向客户端发送应答码
800         write(sockfd, "OK\n", 4);
801         while((len=read(sockfd, buf, MAXBUFF)) > 0)
802                 write(fd, buf, len);
803         if(len<0 && errno==EINTR)
804         {
805                 perror("fail to read");
806                 goto end;
807         }
808         printf("OK\n");
809         res = 0;
810 end:
811         close(fd);
812         return res;
813 }
814
815 /* 处理LS命令: LS arg1
816  * sockfd: 已连接的通信套接字描述符
817  * path: 客户端指定的路径
818  */
819 int do_ls(int sockfd, char *path)
820 {
821         struct stat stat_buf;
822         char cmd[BUFFSIZE], buf[MAXBUFF];
823         int fd, len;
824         int res = -1;
825
826         sprintf(cmd, "ls %s > temp.txt", path); // 拼接命令
827         fprintf(stdout, "===from client: system(%s)\n", cmd);
828         system(cmd);    // system 执行命令
829
830         if((fd=open("temp.txt", O_RDONLY)) < 0) // open 打开文件
831         {
832                 write(sockfd, "ERROR: fail to ls server file\n",
833                         strlen("ERROR: fail to ls server file\n"));
834                 return -1;
835         }
836 /*      if(fstat(fd, &stat_buf) < 0)
837         {
838                 write(sockfd, "ERROR: fail to stat server file\n",
839                         strlen("ERROR: fail to stat server file\n"));
840                 goto end;
841         }
842         if(!S_ISREG(stat_buf.st_mode))
843         {
844                 write(sockfd, "ERROR: not a regular file\n",
845                         strlen("ERROR: not a regular file\n"));
846                 res = 0;
847                 goto end;
848         }
849         fprintf(stdout, "===to client: OK %d\n", stat_buf.st_size);
850         sprintf(cmd, "OK %d", stat_buf.st_size);
851         write(sockfd, cmd, strlen(cmd)+1);
852 */
853         write(sockfd, "OK\n", 4);       // 向客户端发送应答信息
854         read(sockfd, cmd, BUFFSIZE);    // 等待客户端的应答信息,应答码为RDY
855
856         while((len=read(fd, buf, MAXBUFF)) > 0) // 循环读写文件内容,并写入至通信套接字
857                 write(sockfd, buf, len);
858         if(len < 0)
859         {
860                 perror("fail to read");
861                 goto end;
862         }
863         printf("!ls OK\n");
864         res = 0;
865
866 end:
867         close(fd);
868 //      unlink("temp.txt");     // unlink 删除该临时文件
869         return res;
870 }
871
872 /* 处理客户端的CD命令: CD arg1 */
873 int do_cd(int sockfd, char *path)
874 {
875         if(chdir(path) < 0)     // chdir 改变当前工作目录,进入指定目录
876         {
877                 perror("fail to change directory\n");
878                 write(sockfd, "ERROR: cannot change server directory\n",
879                         strlen("ERROR: cannot change server directory\n"));
880                 return -1;
881         }
882         write(sockfd, "OK\n", 3);
883         return 0;
884 }
885
886
887
888
889
890
891 # FILE: Makefile
892 # DATE: 20180201
893 # ==============
894
895
896 #OBJECTS = common.h main.c command.c
897
898 #all: server
899 #server: $(OBJECTS)
900 #       gcc -o server $(OBJECTS)
901 #
902 OBJECTS = main.o command.o
903
904 server: $(OBJECTS)
905         gcc -o server -g $(OBJECTS)
906
907 main.o: common.h main.c
908         gcc -c -g main.c
909
910 command.o: common.h command.c
911         gcc -c -g command.c

Linux C小项目 —— 实现文件传输相关推荐

  1. linux间服务器间文件传输,Linux命令scp服务器间文件传输教程

    scp就是secure copy,是用来进行远程文件拷贝的.数据传输使用 ssh,并且和ssh 使用相同的认证方式,提供相同的安全保证 .那么你知道Linux命令scp服务器间文件传输教程么?接下来是 ...

  2. Springboot项目修改文件传输(minio)限制大小

    Springboot项目修改文件传输(minio)限制大小 nginx 配置文件 springboot 项目配置文件 公司文件管理服务使用的 minio,很方便,也很快捷. 有天新来小同事说,mini ...

  3. Linux平台基于socket的文件传输服务器和客户端

    Linux平台基于socket的文件传输服务器和客户端 目录 前言 一.服务器程序结构 二.客户程序结构 三.代码 1.服务器主程序video_serv_fork.c 2.服务器子程序video_tr ...

  4. 我使用过的Linux命令之sftp - 安全文件传输命令行工具

    用途说明 sftp命令可以通过ssh来上传和下载文件,是常用的文件传输工具,它的使用方式与ftp类似,但它使用ssh作为底层传输协议,所以安全性比ftp要好得多. 常用方式 格式:sftp <h ...

  5. Linux命令之sftp - 安全文件传输命令行工具

    用途说明 sftp命令可以通过ssh来上传和下载文件,是常用的文件传输工具,它的使用方式与ftp类似,但它使用ssh作为底层传输协议,所以安全性比ftp要好得多. 常用方式 格式:sftp <h ...

  6. Linux 网络服务之FTP 文件传输

    Linux FTP 文件传输 --王宇然qq:496488051 实验一:配置匿名上传FTP 1.软件包安装: [root@localhost ~]# cd /misc/cd/Server       ...

  7. linux下scp提示文件名过长,Linux中crontab下scp文件传输的两种方式

    Linux下文件传输一般有两个命令scp.ftp(工具需要下载安装) 本文主要讲讲scp的文件传输脚本 1.scp ssh-keygen -t rsa免输入密码,传输 这里假设主机A 用来获到主机B的 ...

  8. Linux远程SSH终端和文件传输工具

    2019独角兽企业重金招聘Python工程师标准>>> 在Windows上打开Linux终端的工具很多,这里介绍三款目前在用的终端工具,非常好用! 1.putty 这款工具支持tel ...

  9. Linux下利用ssh远程文件传输 传输命令 scp

    在linux下一般用scp这个命令来通过ssh传输文件. 一.scp是什么? scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进 ...

最新文章

  1. css中position的几个值
  2. mysql 打开文件数_MySQL打开的文件描述符限制
  3. 网页html是什么语言程序,html是什么
  4. boost::hana::monadic_fold_left用法的测试程序
  5. ABAP Pattern usage - define own custom user exit
  6. 基于纤程(Fiber)实现C++异步编程库(一):原理及示例
  7. php按数字分页类,PHP简单实现数字分页功能示例
  8. C#异或运算符的使用【C#】
  9. js引用类型和基本类型、隐式类型转换以及强制类型转换面试题
  10. RAID-4与模2和
  11. 电脑卸载了bandzip,但是在文件的打开方式里面还是有bandzip,注册表里又搜不到相关文件,如何解决?
  12. 2022年诺贝尔物理学奖背后的故事——贝尔不等式诞生之后
  13. 滴滴章文嵩:一个人的20年开源热情和国内互联网开源运动
  14. 创建create-react-app myapp项目报错
  15. 计算机图形学学习:GAMES101
  16. 某网站电商运营数据分析案例
  17. 3d打印技术应用实例_3D打印(工作,优势和应用)
  18. 举个栗子!Tableau技巧(53):添加跳转按钮实现页面切换
  19. 四月IDO第四期,12个热门项目即将上线
  20. LiteOS学习笔记-5通信模组之LiteOS的SAL及socket编程

热门文章

  1. Time Changes Everything
  2. 国服Dota2无法更新故障解决方法一例
  3. python人机对话性别年龄名字_人机交互程序 python实现人机对话
  4. 模拟键盘对应的keycode
  5. PHP高级编程知识点总结~超详细
  6. Adobe photoshop与bridge更改文件位置重新关联
  7. DataGridView绑定DataTable出现大红叉
  8. 人际沟通要有的心里准备
  9. Acrel-2000型电力监控系统采用智能电力仪表实现配电回路用电的实时监控和管理
  10. 智能无障碍轮椅—— 520编码器直流减速电机