网络编程:CMD命令

要求:

写一个客户端程序和服务器程序,客户端程序连接上服务器之后,通过敲命令和服务器进行交互,支持的交互命令包括:

  • pwd:显示服务器应用程序启动时的当前路径。
  • cd:改变服务器应用程序的当前路径。
  • ls:显示服务器应用程序当前路径下的文件列表。
  • quit:客户端进程退出,但是服务器端不能退出,第二个客户可以再次连接上服务器端。

客户端向服务端发送交互命令,服务端将查询的结果返回给客户端

开干

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE  1024

int tcp_client(char *address, int port)
{
    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, address, &server_addr.sin_addr);

    socklen_t server_len = sizeof(server_addr);
    int connect_rt = connect(socket_fd, (struct sockaddr *)&server_addr, server_len);
    if(connect_rt < 0)
    {
        perror("connect failed");
        return -1;
    }

    return socket_fd;
}

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        perror("usage: cmd_client <IPaddress> <port>");
        return -1;
    }

    int port = atoi(argv[2]);
    printf("argv1:%s\n",argv[1]);
    int socket_fd = tcp_client(argv[1], port);

    char recv_line[MAXLINE],send_line[MAXLINE];
    int n;

    fd_set readmask;
    fd_set allreads;
    FD_ZERO(&allreads);
    FD_SET(0, &allreads);
    FD_SET(socket_fd, &allreads);

    for(;;)
    {
        readmask = allreads;
        int rc = select(socket_fd+1, &readmask, NULL, NULL, NULL);
        if(rc <= 0)
        {
            perror("select failed");
            return -1;
        }

        if(FD_ISSET(socket_fd, &readmask))
        {
            n = read(socket_fd, recv_line, MAXLINE);
            if(n < 0)
            {
                perror("read error");
                return -1;
            }
            else if(n == 0)
            {
                printf("server closed");
                break;
            }

            recv_line[n] = 0;
            fputs(recv_line, stdout);
            fputs("\n",stdout);
        }

        if(FD_ISSET(STDIN_FILENO, &readmask))
        {
            if(fgets(send_line, MAXLINE, stdin) != NULL)
            {
                int i = strlen(send_line);
                if(send_line[i - 1] == '\n')
                {
                    send_line[i - 1] = 0;
                }

                if(strncmp(send_line, "quit", strlen(send_line)) == 0)
                {
                    if(shutdown(socket_fd, 1))
                    {
                        perror("shutdown failed");
                        return  -1;
                    }
                }

                size_t rt = write(socket_fd, send_line, strlen(send_line));
                if(rt < 0)
                {
                    perror("write failed");
                    return -1;
                }

            }
        }

    }

    exit(0);
}

客户端的代码主要考虑的是使用 select 同时处理标准输入和套接字。select 如果发现标准输入有事件,读出标准输入的字符,就会通过调用 write 方法发送出去。如果发现输入的是 quit,则调用 shutdown 方法关闭连接的一端。

如果 select 发现套接字流有可读事件,则从套接字中读出数据,并把数据打印到标准输出上;如果读到了 EOF,表示该客户端需要退出,直接退出循环,通过调用 exit 来完成进程的退出。
服务端程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SERV_PORT  43211
#define LISTENQ 1024

static int count;

static void sig_int(int signo) {
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}


char *run_cmd(char *cmd)
{
    char *data = malloc(16384);
    bzero(data, sizeof(data));
    FILE *fdp;
    const int max_buffer = 256;
    char buffer[max_buffer];
    fdp = popen(cmd, "r");
    char *data_index = data;

    if (fdp)
    {
        while(!feof(fdp))
        {
            if(fgets(buffer, max_buffer, fdp) != NULL)
            {
                int len = strlen(buffer);
                memcpy(data_index, buffer, len);
                data_index += len;
            }
        }
        pclose(fdp);
    }
    return data;

}

int main(int argc, char *argv[])
{
    int listenfd;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERV_PORT);

    int on = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    int rt1 = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
    if(rt1 < 0)
    {
        perror("bind failed");
        return -1;
    }

    int rt2 = listen(listenfd, LISTENQ);
    if(rt2 < 0)
    {
        perror("listen failed");
        return -1;
    }

    signal(SIGPIPE, SIG_IGN);

    int connfd;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    char buf[256];
    count = 0;
    while(1)
    {
        if((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_len)) < 0)
        {
            perror("accept failed");
            return -1;
        }

        while(1)
        {
            bzero(buf, sizeof(buf));
            int n = read(connfd, buf, sizeof(buf));
            if(n < 0)
            {
                perror("error read message");
                continue;
            }
            else if(n == 0)
            {
                printf("client closed ");
                close(connfd);
                break;
            }

            count++;
            buf[n] = 0;
            if(strncmp(buf, "ls", n) == 0)
            {
                char *result = run_cmd("ls");
                if(send(connfd, result, strlen(result), 0) < 0)
                {
                    return 1;
                }
                free(result);
            }
            else if(strncmp(buf, "pwd", n) == 0)
            {
                char buf[256];
                char *result = getcwd(buf, 256);
                if(send(connfd, result, strlen(result), 0) < 0)
                {
                    return 1;
                }
            }
            else if(strncmp(buf, "cd ", 3) == 0)
            {
                char target[256];
                bzero(target, sizeof(target));
                memcpy(target, buf+3, strlen(buf) - 3);
                if(chdir(target) == -1)
                {
                    printf("change dir failed, %s\n",target);
                }
            }
            else
            {
                char *error = "error: unknow input type";
                if(send(connfd, errno, strlen(errno), 0) < 0)
                {
                    return 1;
                }
            }
        }

    }
    exit(0);

}

服务器端程序需要两层循环,第一层循环控制多个客户端连接,第二层循环控制和单个连接的数据交互
在第一层循环里通过 accept 完成了连接的建立,获得连接套接字。在第二层循环里,先通过调用 read 函数从套接字获取字节流。这里处理的方式是反复使用了 buf 缓冲,每次使用之前记得都要调用 bzero 完成初始化,以便重复利用。如果读取数据为 0,则说明客户端尝试关闭连接,这种情况下,需要跳出第二层循环,进入 accept 阻塞调用,等待新的客户连接到来

在读出客户端的命令之后,就进入处理环节。通过字符串比较命令,进入不同的处理分支。C 语言的 strcmp 或者 strncmp 可以帮助我们进行字符串比较,
如果命令的格式有错,需要把错误信息通过套接字传给客户端。
对于“pwd”命令,通过调用 getcwd 来完成的,getcwd 是一个 C 语言的 API,可以获得当前的路径。
对于“cd”命令,通过调用 chdir 来完成的,cd 是一个 C 语言的 API,可以将当前目录切换到指定的路径。
对于“ls”命令,通过了 popen 的方法,执行了 ls 的 bash 命令,把 bash 命令的结果通过文件字节流的方式读出,再将该字节流通过套接字传给客户端。

运行结果

posted @ 2022-03-20 11:18  牛犁heart  阅读(379)  评论(0编辑  收藏  举报