Linux C小项目 —— 实现文件传输

2023-05-16

编译运行:



文档说明:

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();	// 子进程关闭连接套接字并退出
		}
		else
			close();	// 父进程关闭连接套接字
	}
	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_INETIPv4地址族

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.h
  3  * DATE: 20180201
  4  * ==============
  5  */
  6 
  7 #include <stdio.h>
  8 #include <stdlib.h>     // exit
  9 #include <unistd.h>     // STDOUT_FILENO
 10 #include <string.h>
 11 
 12 #include <sys/stat.h>   // struct stat
 13 #include <fcntl.h>      // O_WRONLY
 14 
 15 #include <sys/socket.h> // struct sockaddr_in
 16 #include <netinet/in.h>
 17 #define PORT 8000
 18 
 19 #define BUFFSIZE 32
 20 #define MAXBUFF 128
 21 
 22 #define DEBUG_PRINT 1
 23 #ifdef DEBUG_PRINT
 24 #define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)
 25 #else
 26 #define DEBUG(format, ...)
 27 #endif
 28 
 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.c
 64  * DATE: 20180201
 65  * ===============
 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



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux C小项目 —— 实现文件传输 的相关文章

  • IIC总线基础知识

    IIC总线基础知识 一 简介 IIC xff08 Inter Integrated Circuit xff09 是一个多主从的串行总线 xff0c 又叫I2C xff0c 是由飞利浦公司发明的通讯总线 xff0c 属于半双工同步传输类型总线
  • 常见RISC-V介绍

    当前一颗新出的CPU xff1a RISC V简直火透了半边天 xff0c 无论是财大气粗的阿里系的平头哥 xff0c 还是新创企业 xff0c 似乎只要和RISC V挂上钩就足可以实现赶英超美 那事实上RISC V是什么 xff1f 除了
  • qsort的compare函数

    qsort的compare函数 功能 xff1a 使用快速排序例程进行排序 头文件 xff1a stdlib h 用法 xff1a void qsort void base size t num size t width int cdecl
  • 深度相机(3D相机)

    二维图片 xff0c 人眼可以通过物体的相对位置关系判断物体距离的远近 xff0c 而相机则不可以 深度相机 xff08 3D相机 xff09 就是终端和机器人的眼睛 xff0c 其就是通过该相机能检测出拍摄空间的景深距离 通过深度相机获取
  • ubuntu只有一种分辨率的解决方案——4K显示屏与扩展屏幕

    4K显示屏的ubuntu系统在安装NVIDIA显卡后 xff0c 只有一个分辨率为 xff1a 3840 2160 xff0c 如图所示 xff0c 该分辨率在扩展显示器的使用时 xff0c 会出现4K主屏幕分辨率很高 xff0c 而扩展显
  • 使用RTSO-9003拓展板的TX2镜像备份与恢复

    须知 TX2进入recovery模式 在给TX2通电的时候 xff0c 马上按住板子上的recovery键 xff0c 持续3秒左右 xff0c 继续保持按住recovery键并按住reset键 xff0c 便可以进入TX2的recover
  • MATLAB学习笔记

    MATLAB学习笔记 一级目录备忘录HELP文件路径MATLAB函数定义与表达MATLAB特殊变量和常量MATLAB文件读写数组数组的创建常用操作常用操作常用操作 MATLAB画图plot函数 xff1a 改变图像中线的颜色和线条形式leg
  • ubuntu16.04 boot空间不足 no space left on device

    Linux 中 boot 是存放系统启动文件的地方 xff0c 安装 ubuntu 时单独分区给 200M 足够 xff0c 但是系统内核更新后 xff0c 老的内核依然保存在 boot 分区内 xff0c 几次升级后 xff0c 就会提示
  • 什么是死锁,产生死锁的原因及必要条件

    什么是死锁 xff1f 所谓死锁 xff0c 是指多个进程在运行过程中因争夺资源而造成的一种僵局 xff0c 当进程处于这种僵持状态时 xff0c 若无外力作用 xff0c 它们都将无法再向前推进 因此我们举个例子来描述 xff0c 如果此
  • nvidia jetson TX2 踩坑解决记录

    最近拿着一张多年前实验室买的Jetson想刷个软路由玩 xff0c 奈何折腾了一周才把clash meta内核装好 xff0c 记录一下自己踩的坑 xff0c 整理一下以免其他玩jetson TX2的兄弟掉大坑 已经过去一周了很多都记不太清
  • 接收灵敏度

    接收灵敏度是检验基站接收机接收微弱信号的能力 xff0c 它是制约基站上行作用距离的决定性技术指标 xff0c 也是RCR STD 28协议中 xff0c 空中接口标准要求测试的技术指标之一 合理地确定接收灵敏度直接地决定了大基站射频收发信
  • 16行,使用Python制作简易版QQ自动回复机器人(windows版)

    目录 1 安装go cqhttp 2 使用go cqhttp 2 1 发送信息 2 1 1发送 你好 2 1 2 在群里 64 人 2 2获取群成员列表 2 3 实现QQ机器人 1 安装go cqhttp 点此安装go cqhttp xff
  • FPGA在线升级实战应用篇

    FPGA在线升级实战应用篇 1 摘要 项目在运营过程中可能需要根据应用需求更改固件 xff0c 或者对现有产品进行升级及在产品使用过程出现的故障进行分析 xff0c 故需要对产品进行升级维护 以往的产品出现的故障或BUG问题只能通过产品寄回
  • (xTaskNotify)- assert failed! 错误的修复

    今日在测试ESP32代码的时候 xff0c 使用xTaskNotify发生错误 xff0c 提示如下 xff1a xTaskNotify assert failed xff0c 然后系统重启 找了一下原因 xff0c 在xTaskNotif
  • kubernetes dashboard用户界面安装使用

    原文 xff1a https www toocruel net kubernetes dashboardyong hu jie mian an zhuang shi yong 1 下载kubernetes dashboard yaml文件
  • 网络通信编程学习笔记(四):在Ubuntu下创建新用户、用puTTY/VNCViewer远程登录、用ftp上传和下载、用Xming远程连接

    前言 真的用不惯VNCViewer xff0c 树莓派还是外接显示屏来的舒服 xff0c 分辨率也是1080p xff0c 只有全高清壁纸才可以慰籍学习之痛 xff01 Xming也是不如按开机键来的方便 笑哭 目录 一 用puTTY VN
  • node.js和npm离线安装

    离线安装node js和npm 1 下载官方安装包并拷贝到离线机器上 官方下载地址 xff1a https nodejs org en download 2 解压文件 xff1a tar xJf node v8 9 4 linux x64
  • Github 创建新分支

    一 clone Repository clone Github 上的Repository xff0c 如下 xff1a git clone git 64 github span class hljs preprocessor com spa
  • ARM平台基于嵌入式Linux部署ROS

    By Toradex 秦海 随着ARM平台处理能力的日益强大 xff0c 越来越多的工业智能 机器人应用在ARM平台上面实现 xff0c 在这个过程中不可避免的就涉及到将机器人应用开发框架移植到ARM平台来运行 xff0c 因此本文就着重示
  • 如何设计一款低成本的计算机载板- 第一部分

    By Toradex Peter Lischer 1 简介 在以前的博客文章中 xff0c 我们已经在一个硬件项目中使用计算机模块提出了许多讨论 xff0c 因此 xff0c 这里我们假设你已经在项目中决定采用计算机模块SoM xff0c

随机推荐

  • git rebase后commit id的变化

    经测试发现 xff0c 在执行完 git rebase 之后 xff0c 1 xff09 会生成的新的 commit id 2 xff09 新 commit 与旧 commit 的父节点不相同 3 xff09 旧 commit 的父节点保持
  • 嵌入式Linux下串口调试

    By Toradex秦海 1 简介 UART串口是嵌入式设备最为常用的调试和通讯接口之一 xff0c 无论是RS232还是RS422 485都有着非常广泛的应用 xff0c 因此本文就基于嵌入式Linux演示在User Space进行串口调
  • [LeetCode刷题笔记] 关于LeetCode的前言

    原创文章 转载请注册来源http blog csdn net tostq 又到了一年毕业就业季了 xff0c 三年前的校招季我逃避了 xff0c 可这一次终于还是要轮到我了 61 61 作为要准备踏入码农行业的人来说 xff0c 要准备校招
  • 关于机器视觉标定的pnp问题

    https blog csdn net cocoaqin article details 77485436 https blog csdn net cocoaqin article details 77848588利用二维码求解相机世界坐标
  • kvaser在linux中的应用

    本文主要讲解 xff0c kvaser如何使用简单socketcan 1 硬件 kvaser USBcan Pro 2xHS v2 2 准备系统 ubuntu 16 04 由于项目需要在程序中使用socketcan xff0c 所以需要将k
  • 编译错误-build stopped: subcommand failed. 解决方法

    make 1 Leaving directory 96 home sunhz sl8541e out target product sp8541e srvm obj u boot15 39 make Leaving directory 96
  • 如何备份jetson nano 的u盘系统?

    使用工具 xff1a Win32DiskImager 备份步骤 xff1a 1 在本地盘 xff08 C盘或D盘都行 xff0c 盘符剩余内存大于u盘系统内存就行 xff09 新建文本文档 xff0c 连后缀名字一起改成backup img
  • Security Onboard Communication-SecOC

    一 通讯加密的必要性 随着汽车电子的发展及整车功能复杂性的提高 xff0c 车载控制器数量从之前的寥寥几个增加至规模复杂的上百个 基于功能的需求 xff0c 各个控制器每时每刻需要进行大量数据的交互 xff0c 数据交互的方式也多种多样 x
  • 解决VNC远程连接树莓派,窗口显示不全的问题,亲测可行!!

    哇 xff0c 就在刚刚才百度到解决VNC远程连接树莓派 xff0c 窗口显示不全的问题 xff0c 昨晚上查了一晚上都没搞定 xff0c xff0c xff0c 首先说下问题吧 xff0c 就是用VNC远程连接树莓派后 xff0c 会出现
  • Avoid mutating a prop directly since the value will be overwritten whenever

    在vue中 父组件向子组件传值 并在子组件改变了父组件的值 就会发出警告 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定 xff1a 父级 prop 的更新会向下流动到子组件中 xff0c 但是反过来则不行 这样会防止
  • 【Linux】异步网络库dyad代码阅读

    简介 dyad是一个基于C编写的异步网络库 xff0c 非常精简 xff0c 单C文件 xff0c 仅实现TCP xff0c 很适合用来学习Linux网络编程和异步非阻塞处理 链接 Github链接 基于Dyad的echo server实现
  • 明文签署文件不可用,结果为‘NOSPLIT’

    快速记录一下在 debian上使用 apt update时遇到的一个小问题 明文签署文件不可用 xff0c 结果为 NOSPLIT xff08 您的网络需要认证吗 xff1f xff09 报错现场如下所示 xff1a span class
  • Ubuntu 16.04 安装 Gazebo

    Gazebo 是一款 3D 动态模拟器 xff0c 能够准确有效地模拟复杂室内和室外环境中的机器人群体 虽然类似于游戏引擎 xff0c Gazebo 提供了更高保真度的物理模拟 一套传感器以及用户和程序接口 Gazebo 的典型用途包括 x
  • 设计一款STM32的BootLoader

    参考文章 xff1a https blog csdn net qingtian506 article details 9128899 之前很想做一个属于STM32的BootLoader xff0c 但是想想没什么实际用处就没有下手 xff0
  • vscode之C/C++代码自动补全

    目录 准备 xff1a 步骤 xff1a 安装插件重启加载更改配置选项找到 设置 打开json配置添加配置json保存并重新打开vscode即可自动补全 准备 xff1a IDE xff1a vscode 安装就不展开啦 步骤 xff1a
  • Velodyne 16线三维激光雷达

    Velodyne 16线三维激光雷达VLP 16介绍 16线激光雷达VLP 16是Velodyne公司出品的小型的3维激光雷达 xff0c 保留了电机转速可调节的功能 实时上传周围距离和反射率的测量值 16线激光雷达VLP 16具有100米
  • [gazebo仿真]添加RealSense双目相机传感器

    下载双目相机模型和插件 xff0c 其中包含了T265 R200 D435模型 git clone https span class token operator span span class token operator span sp
  • actionlib的应用:STDR仿真器定点巡航

    关于STDR仿真器的定点巡航 xff0c ROS小课堂提供了非常详细的教程http www corvin cn 892 html xff0c 作者是用python写的巡航脚本 xff0c 下面提供一下C 43 43 版本的巡航代码 若有错误
  • ROS动态调节参数

    参考 xff1a ROS学习之路07 xff1a 编写动态重配置 dynamic reconfigure 参数的节点 https blog csdn net l1216766050 article details 79575423 ROS使
  • Linux C小项目 —— 实现文件传输

    编译运行 xff1a 文档说明 xff1a 1 整体程序设计 服务器程序是一个死循环 xff0c 处理一次连接之后不退出 xff0c 而客户端程序只处理一个连接就可以了 2 客户端程序设计 客户端程序的主要任务 xff1a a 分析用户输入