需求

在Linux上利用流套接字提供客户端和服务端面向连接的交互。进行登陆验证、查看服务器列表、下载文件、远程执行命令的几个操作。

设计

  • 每次请求客户端首先发出一个报头,得到服务端的响应后确保连接的正确建立,之后开始传文件。
  • 对于下载文件操作:客户端输入一个相对路径或者绝对路径以及文件名,等到服务端响应一个确认报文后开始进行收文件。其中文件的传递利用二进制流的形式发送,这样的优点是能够将文件分成许多块发送且能够方便客户端读取。
  • 对于执行命令操作服务端通过客户端传来的命令,将其切割成各个命令以及参数块,之后利用fork以及exec函数族的形式让子进程执行改命令。要注意的是,通过exec执行的命令很难与其他命令嵌套,比如我们要把操作结果返回给客户端需要用到文件重定向,这里需要使用system函数,其内部实现是exec+fork+文件;能够轻易把传来的命令正确执行最后将结果重定向到文件中reply给客户端。

报文设计:

实现

服务端:

#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h> // 提供属性操作函数 
#include <sys/types.h> // 提供mode_t 类型 
#include <malloc.h> 
#include <fcntl.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <dirent.h> // 提供目录流操作函数 

#define PORT 56193

#define MAX_UNAME 8
#define MAX_PASSWD 8
#define MAX_BUF_SZ 1024
#define MAX_CMD_SZ 20

#define TYPE_LOGIN 1
#define TYPE_READ 2
#define TYPE_EXIT 3
#define TYPE_EXEC_CMD 4
#define TYPE_GET_FILE 5
#define TYPE_NOT_FILE 6
#define TYPE_YES_FILE 7

struct hdr_st{
    unsigned short type;
    unsigned short len;
};

void scan_dir(char *dir, char *strbuf, int bufsz)   // 定义目录扫描函数 
{  
    DIR *dp;                      // 定义子目录流指针 
    struct dirent *entry;         // 定义dirent结构指针保存后续目录 
    struct stat statbuf;          // 定义statbuf结构保存文件属性 
    int sz, remain = bufsz;
    char *pos = strbuf;

    if((dp = opendir(dir)) == NULL) // 打开目录,获取子目录流指针,判断操作是否成功 
    {  
        puts("can't open dir.");  
        return;  
    }  
    chdir (dir);                     // 切换到当前目录 
    while((entry = readdir(dp)) != NULL)  // 获取下一级目录信息,如果未否则循环 
    {  
        lstat(entry->d_name, &statbuf); // 获取下一级成员属性 
        if(S_IFDIR &statbuf.st_mode)    // 判断下一级成员是否是目录 
        {  
            if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)  
              continue;  
        }
        sz = strlen(entry->d_name);
        if(sz < remain){
            remain -= sz;
            sprintf(pos, "%s\n",  entry->d_name);  // 输出属性不是目录的成员 
            pos += (sz+1);
        }else return ;
    }  
    chdir("..");                                                  // 回到上级目录 
    closedir(dp);                                                 // 关闭子目录流 
}  

void split(char cmd_list[][MAX_CMD_SZ], char *cmd, int *sz) {
	int i, j, len, flag = 0;
	len = strlen(cmd);
	*sz = 0;
	j = 0;
	for (i = 0; i < len; i++) {
		if (cmd[i] == ' ' && i && cmd[i-1] != ' ') {
			cmd_list[*sz][j] = 0;
			*sz = *sz + 1;
			j = 0;
			flag = 0;
		} else if (cmd[i] != ' ') {
			cmd_list[*sz][j++] = cmd[i];
			flag = 1;
		}
	}
	if (flag) {
		cmd_list[*sz][j] = 0;
		*sz = *sz + 1;
	}
}

int do_login(int sAccept)
{
char usrname[MAX_UNAME];
char passwd[MAX_PASSWD];
int sz;

    printf("Receive Login request!\n");
    /* TODO here do not handle the sending delay */
	sz = recv(sAccept, usrname, MAX_UNAME, 0);
    if(sz < 0){
       printf("Did not receive the enough Message in login.\n");
       return 0;
    }

    sz = recv(sAccept, passwd, MAX_UNAME, 0);
    if(sz < 0){
       printf("Did not receive the enough Message in login.\n");
       return 0;
    }
	
	if (strcmp(usrname, "root") != 0 || strcmp(passwd, "123456") != 0) {
		return 0;
	}
	
	printf("User name :%s\n", usrname);
    	printf("passwd: %s\n", passwd);
	
	return 1;
}

void do_get_file_list(int sockfd) {
	char file_list[MAX_BUF_SZ];
	scan_dir(".", file_list, MAX_BUF_SZ - 1);
	if ( send(sockfd, &file_list, sizeof(file_list), 0) < 0 ) {
		printf("send() failed\n");
	} else {
		printf("file list send success!\n");
	}
	
}

void exec_cmd(int sockfd) {
	char cmd_name[MAX_BUF_SZ];
	char cmd_list[MAX_CMD_SZ][MAX_CMD_SZ];
	char *p[MAX_CMD_SZ];
	int sz, cmd_sz,i,pfd,len;
	struct hdr_st hdr;
	FILE* fp;
	char file[MAX_BUF_SZ];

	sz = recv(sockfd, cmd_name, MAX_BUF_SZ, 0);	
	if ( sz < 0 ) {
		printf("recv() failed\n");				
		return ;			
	} else if (sz == 0) {
		printf("connect close\n");
		return ;
	}
	printf("recv : %s\n", cmd_name);

	strcat(cmd_name, " > result");
	split(cmd_list, cmd_name, &cmd_sz);
// strcpy(cmd_list[cmd_sz++], ">result");
	strcpy(cmd_list[cmd_sz++], "NULL");
	for (i = 0; i < cmd_sz; i++) p[i] = cmd_list[i];

//run the command
	if ((pfd = fork()) == 0) {
#if 0
		if (execvp(cmd_list[0], p)  < 0) {
			printf("execvp error!\n");		
		}
#else
		system(cmd_name);
		exit(0);
#endif
	} else {
		waitpid(pfd, NULL, 0);
		printf("run success!\n");

//reply ack or nak
		fp = fopen("./result", "rb");
		if (fp == NULL) {
			printf("open error!\n");
			hdr.type = TYPE_NOT_FILE;
			if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
				printf("Send error!\n");  
				return ;
			}
			return ;		
		}	
		hdr.type = TYPE_YES_FILE;
		fseek(fp, 0, SEEK_END);
		hdr.len = ftell(fp);
		fseek(fp, 0, SEEK_SET);
		if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
			printf("Send error!\n");  
			return ;
		}

//send command file
		while (1) {
			len = fread(file, 1, MAX_BUF_SZ, fp);
			if ( send(sockfd, file, len, 0) < 0 ) {
				printf("send() failed\n");
				fclose(fp);
				return ;
			}
			if (feof(fp)) break;		
		}
		fclose(fp);
	}

}

void send_file(int sockfd) {
	struct hdr_st hdr;
	FILE *fp;
	char file_name[MAX_BUF_SZ];
	char file[MAX_BUF_SZ];
	int sz, len;

	sz = recv(sockfd, file_name, MAX_BUF_SZ, 0);	
	if ( sz < 0 ) {
		printf("recv() failed\n");				
		return ;			
	} else if (sz == 0) {
		printf("connect close\n");
		return ;
	}
	printf("recv : %s\n", file_name);

//Reply getfile cmd
	fp = fopen(file_name, "rb");
	if (fp == NULL) {
		printf("open error!\n");
		hdr.type = TYPE_NOT_FILE;
		if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
			printf("Send error!\n");  
			return ;
		}
		return ;		
	}
	
	hdr.type = TYPE_YES_FILE;
	fseek(fp, 0, SEEK_END);
	hdr.len = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send error!\n");  
		return ;
	}

//sendfile
	while (1) {
		len = fread(file, 1, MAX_BUF_SZ, fp);
		if ( send(sockfd, file, len, 0) < 0 ) {
			printf("send() failed\n");
			fclose(fp);
			return ;
		}
		if (feof(fp)) break;		
	}
	fclose(fp);
	printf("send file size is %d\n", hdr.len);
}

int main()
{
	struct hdr_st hdr;
	struct sockaddr_in serv ;//服务器的地址
	int sListen,sAccept;
	int rcv_sz;
	int rcv_send; //登陆的回传
	int tag;
	char buf[MAX_BUF_SZ];

    //创建TCP套接字
    sListen = socket(AF_INET, SOCK_STREAM, 0);
    if (sListen < 0){
        //创建套接字失败
        perror("socket failed:%d\n");//输出错误
        return;
    }

    //填写服务器地址到serv上
    serv.sin_family = AF_INET;
    serv.sin_port = htons(PORT);
    serv.sin_addr.s_addr = inet_addr("127.0.0.1");

    //将serv绑定到socket上
    if (bind(sListen, (struct sockaddr*)&serv, sizeof(serv))<0){
        perror("bind() failed:\n");
        return;
    }

    //让socket进入监听状态,等待连接的最大个数是5
    if (listen(sListen, 5)<0){
        perror("listen() failed:\n");
        return;
    }

	printf("Waiting for client to connnect.....\n");
    //进入循环等待客户端连接请求
    struct sockaddr_in cli;
	socklen_t peerlen;
	while (1) {
		peerlen = sizeof(cli);
		sAccept = accept(sListen, (struct sockaddr*)&cli, &peerlen);
		if (sAccept < 0){
			perror("accept() failed\n");
			break;
		}
		printf("accepted client\n");
		tag = 1;
		while(tag) {
			rcv_sz = recv(sAccept, &hdr, sizeof(hdr), 0);
			if ( rcv_sz < 0 ) {
				printf("recv() failed\n");				
				break;			
			}
			switch(hdr.type)
			{
		    		case TYPE_LOGIN:
		        		if (do_login(sAccept) == 0) {
						printf("login fail!\n");
						rcv_send = 0;
						if ( send(sAccept, &rcv_send, sizeof(rcv_send), 0) < 0 ) {
							printf("send() failed\n");
							tag = 0;
						}
					} else {
						printf("login success!\n");
						rcv_send = 1;
						if ( send(sAccept, &rcv_send, sizeof(rcv_send), 0) < 0 ) {
							printf("send() failed\n");
							tag = 0;
						}
					}
		        		break;
				case TYPE_READ:
					do_get_file_list(sAccept);
					break;				
				case TYPE_EXIT:
					printf("free connect!\n");
					tag = 0;
					break;
				case TYPE_EXEC_CMD:
					exec_cmd(sAccept);
                		break;
				case TYPE_GET_FILE:
					send_file(sAccept);
				break;
			}
			hdr.type = 0;
		 }
		 close(sAccept);    
	}
	close(sListen);
	return 0;
}

客户端

#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h> 
#include <sys/types.h> 
#include <malloc.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 

#define PORT 56193

#define MAX_UNAME 8
#define MAX_PASSWD 8
#define MAX_LINE 256
#define MAX_BUF_SZ 1024

#define TYPE_LOGIN 1
#define TYPE_READ 2
#define TYPE_EXIT 3
#define TYPE_EXEC_CMD 4
#define TYPE_GET_FILE 5
#define TYPE_NOT_FILE 6
#define TYPE_YES_FILE 7

struct hdr_st{
    unsigned short type;
    unsigned short len;
	
};

char *menu[]={
   "1.Login",
   "2.Get file list",
   "3.Quit",
   "4.Exec your command",
   "5.Get file",
};
int count = sizeof(menu)/sizeof(char *);

int show_menu()
{
int i;

    printf("===================\n");
    for(i=0; i< count; i++){
        printf("%s\n", menu[i]);
    }
}

char chose_menu()
{
int chose;
int item;


    while(1){
        show_menu();
        printf("Chose a item:");
        chose = getchar();
        getchar();
        item = chose - '0';

        if(item  > count || item <= 0){
            //printf("Error input: %d %d\n", item, chose); 
            continue;       
        }
	printf("Chose %d(%s)\n", chose, menu[chose-'1']);
        return item;
    }

}

void login(int sockfd)
{
struct hdr_st hdr;
char usrname[MAX_LINE];
char passwd[MAX_LINE];

    printf("Send Login message....\n");
    hdr.type = TYPE_LOGIN;
    hdr.len = MAX_UNAME+MAX_PASSWD;
    
    printf("Username:");
    scanf("%s", usrname);
    usrname[MAX_UNAME-1] = 0;
	getchar();
    printf("Password:");
    scanf("%s",passwd);
    getchar();
    passwd[MAX_PASSWD -1] = 0;
	
	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send msg error!");  
		return ;
	}
	
	if(send(sockfd, usrname, MAX_UNAME, 0) <= 0){  
		printf("Send msg error!"); 
		return ; 		
	}
	
	if(send(sockfd, passwd, MAX_PASSWD, 0) <= 0){  
		printf("Send msg error!");  
		return ;
	}
	
    printf("Send login ok\n");
}

void get_file_list(int sockfd) {
	struct hdr_st hdr;
	char buf[MAX_BUF_SZ];
	int rcv_sz;
	hdr.type = TYPE_READ;
    	hdr.len = 0;
	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send cmd error!");  
		return ;
	}

	rcv_sz = recv(sockfd, buf, MAX_BUF_SZ, 0);
	if ( rcv_sz < 0 ) {
		printf("recv() failed\n");				
		return ;			
	}
	buf[MAX_BUF_SZ - 1] = 0;
	fputs(buf, stdout);
}

void program_exit(int sockfd) {
	struct hdr_st hdr;
	hdr.type = TYPE_EXIT;
    	hdr.len = 0;
	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send cmd error!");  
		return ;
	}
}

void exec_cmd(int sockfd) {
	struct hdr_st hdr;
	int rcv_sz, len, filelen;
	char command[MAX_BUF_SZ],ch,file[MAX_BUF_SZ];
	char dir[MAX_BUF_SZ]="../";
	FILE* fp;
	
	printf("please input the command:\n");
	fgets(command, MAX_BUF_SZ, stdin);
	len = strlen(command) - 1;
	command[len] = 0;
	
	hdr.type = TYPE_EXEC_CMD;
    	hdr.len = len;
	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send cmd error!");  
		return ;
	}		
	
	if(send(sockfd, command, MAX_BUF_SZ, 0) <= 0){  
		printf("Send command error!\n");  
		return ;
	}
	printf("Send command success!\n");

//wait ack or nak
	rcv_sz = recv(sockfd, &hdr, sizeof(hdr), 0);
	if ( rcv_sz < 0 ) {
		printf("recv() failed\n");				
		return ;			
	} else if (rcv_sz == 0) {
		printf("connect close\n");
		return ;
	}
	if (hdr.type == TYPE_NOT_FILE) {
		printf("The command run error!\n");
		return ;
	}
	if (hdr.type == TYPE_YES_FILE) {
		printf("The command run success!\n");
		filelen = hdr.len;
	}

//download file
	strcat(dir, "result");
	fp = fopen(dir, "wb");
	while(filelen > 0) {
		rcv_sz = recv(sockfd, file, MAX_BUF_SZ, 0);
		if ( rcv_sz < 0 ) {
			printf("recv() failed\n");
			fclose(fp);				
			return ;			
		}
		fwrite(file, 1, rcv_sz, fp);
		filelen -= rcv_sz;
	}
	fflush(fp);
	fclose(fp);

//show result
	fp = fopen(dir, "r");
	if (fp == NULL) {
		printf("result file read fail!\n");
		return ;
	}
	while ((ch = fgetc(fp)) != EOF)
		putchar(ch);
	fclose(fp);
	printf("\n");
}

void get_file(int sockfd) {
	FILE* fp;
	struct hdr_st hdr;
	int rcv_sz, filelen;
	char filename[MAX_BUF_SZ];
	char dir[MAX_BUF_SZ]="../";

//getfilename cmd
	printf("please input the file name:(such as fdir/fname)\n");
	fgets(filename, MAX_BUF_SZ, stdin);

	hdr.type = TYPE_GET_FILE;
    	hdr.len = strlen(filename);

	filename[hdr.len - 1] = 0;

	if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){  
		printf("Send head error!");  
		return ;
	}
	if(send(sockfd, filename, MAX_BUF_SZ, 0) <= 0){  
		printf("Send filename error!");  
		return ;
	}	
	printf("the get command send success!\n");

//wait ack or nak
	rcv_sz = recv(sockfd, &hdr, sizeof(hdr), 0);
	if ( rcv_sz < 0 ) {
		printf("recv() failed\n");				
		return ;			
	} else if (rcv_sz == 0) {
		printf("connect close\n");
		return ;
	}
	if (hdr.type == TYPE_NOT_FILE) {
		printf("No such file\n");
		return ;
	}
	if (hdr.type == TYPE_YES_FILE) {
		printf("Ready to download the file!\n");
		filelen = hdr.len;
	}
	
//download file
	strcat(dir, filename);
	fp = fopen(dir, "wb");
	while(filelen > 0) {
		rcv_sz = recv(sockfd, filename, MAX_BUF_SZ, 0);
		if ( rcv_sz < 0 ) {
			printf("recv() failed\n");
			fclose(fp);				
			return ;			
		}
		fwrite(filename, 1, rcv_sz, fp);
		filelen -= rcv_sz;
	}
	fclose(fp);
	printf("get file success!\n");
}

int main()  
{
int sockfd;
int   server_port = PORT;  
char  server_ip[] = "127.0.0.1"; 
struct sockaddr_in addr;
char chose;
int result;
    
	/* * 创建socket,通知系统建立一个通信端口 * AF_INET表示使用IPv4协议 * SOCK_STREAM表示使用TCP协议 */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){  
        printf("Init socket error!");  
        return -1;  
    }  
    memset(&addr,0, sizeof(addr));  
    /*填写地址addr,填写协议、端口号、服务器地址 */
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr(server_ip);
    /* 连接使用填写好的addr地址连接服务器 */
    if(connect(  sockfd, (struct sockaddr *)&addr, sizeof(addr)  )<0){ 
        printf("Connect server fail!");  
        goto socket_close; //0表示成功,-1表示失败 
    }  
	
	while (1) 
	{
		chose = chose_menu();
		switch(chose)
        	{

            		case TYPE_LOGIN: {
                		login(sockfd);
				recv(sockfd, &result, sizeof(result), 0);
				if (result == 0) {
					printf("login fail!\n");
				} else {
					printf("login success!\n");
				}
			
                		break;
			}
            		case TYPE_READ:
				get_file_list(sockfd);
                		break;
			case TYPE_EXIT:
				program_exit(sockfd);
				goto socket_close;
				break;
			case TYPE_EXEC_CMD:
				exec_cmd(sockfd);
                		break;
			case TYPE_GET_FILE:
				get_file(sockfd);
				break;
        	}
	}
	
socket_close:
    //关闭socket
close(sockfd);
	return 0;  
}