LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 5178|回复: 46

试着编写了一个客户-服务器程序

[复制链接]
发表于 2003-4-7 21:53:16 | 显示全部楼层 |阅读模式
这是我在阅读<用TCP/IP进行网际互联>时为了加深对客户-服务器的理解所编写的小程序。程序设计的目的是用socket模拟一个server/client模型。其中client人终端接受输入的指令并传递到server,server执行传递过来的指令后把结果反馈给client。运行时先以root身份执行./server.o,然后再以普通用户身份执行./client.o,就可以输入诸如ls,mkdir等指令并看到执行结果(执行一次即退出)。 由于没有考虑安全性问题,我在单机上的试验表明:这个程序事实上给普通用户提供了访问/root目录的机会(而正常情况下普通这用户访问/root会被拒绝)。不知道在局域网上是不是也是这样,我没有条件测试,有兴起的兄弟可以试验一下。只要把socket_client.c中的地址从127.0.0.1改为server所在的主机地址就行了。
下面是socket_client.c的代码:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<stdio.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>

  7. /* exec_command()负责在与服务器连接成功后传送指令并显示结果。 */
  8. void exec_command(int sockfd)
  9. {
  10.     char buf[BUFSIZ];                /* 用于处理输入的缓冲区 */
  11.     FILE *tty = NULL;                /* 用户终端 */
  12.     int n;

  13.     /* 打开用户终端。这果没有使用标准输入输出,因为在重定向时,标准输入输出不会指向用户终端,
  14.      * 用户输入带来困难。
  15.      */
  16.     tty = fopen("/dev/tty", "r");
  17.     if (tty == NULL) {
  18.         perror("can't open /dev/tty.");
  19.         exit(1);
  20.     }

  21.     if (fgets(buf, BUFSIZ, tty) == NULL || buf[0] == 'q')
  22.         exit(0);
  23.     else                         /* 接受用户输入后,传递到server. */
  24.         write(sockfd, buf, sizeof(buf));
  25.     if((n = read(sockfd, buf, sizeof(buf))) > 0)
  26.             //write(1, buf, n);
  27.     //buf[n] = '\0';
  28.     //printf("%d %d %s", n, strlen(buf), buf);
  29.     fputs(buf, stdout);
  30. }

  31. int main()
  32. {
  33.     int sockfd;                        /* 套接字描述符 */
  34.     struct sockaddr_in address;        /* Internet 套接字地址结构 */
  35.     int result;                        /* 与server连接的结果 */

  36.     sockfd = socket(AF_INET, SOCK_STREAM, 0);

  37.     address.sin_family = AF_INET;
  38.     address.sin_addr.s_addr = inet_addr("127.0.0.1");
  39.     address.sin_port = htons(19734);

  40.     result =
  41.         connect(sockfd, (struct sockaddr *) &address, sizeof(address));
  42.     if (result == -1) {
  43.         perror("connect to server failed.");
  44.         exit(0);
  45.     }
  46.        
  47.     exec_command(sockfd);        /* 处理通信的内容 */

  48.     close(sockfd);
  49.     exit(0);
  50. }
复制代码
 楼主| 发表于 2003-4-7 21:55:23 | 显示全部楼层
socket_server.c的代码:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<stdio.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>

  7. void exec_command(int c_sockfd)
  8. {
  9.     char buf[BUFSIZ];
  10.     int n;

  11.     close(1);
  12.     dup(c_sockfd);
  13.     close(2);
  14.     dup(c_sockfd);
  15.    
  16.     n = read(c_sockfd, buf, sizeof(buf));
  17.     if (n > 0) {
  18.         printf("server%s", buf);
  19.         system(buf);
  20.     }
  21. }

  22. int main()
  23. {
  24.     int server_sockfd, client_sockfd;
  25.     int client_len;
  26.     struct sockaddr_in server_address;
  27.     struct sockaddr_in client_address;
  28.     char buf[BUFSIZ];

  29.     server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  30.     server_address.sin_family = AF_INET;
  31.     server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  32.     server_address.sin_port = htons(19734);
  33.     client_len = sizeof(client_address);

  34.     bind(server_sockfd, (struct sockaddr *) &server_address,
  35.          sizeof(server_address));
  36.     listen(server_sockfd, 5);
  37.     while (1) {
  38.         client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address,
  39.                    &client_len);
  40.         exec_command(client_sockfd);
  41.         close(client_sockfd);
  42.         printf("client_socket closed");
  43.     }
  44. }
复制代码
 楼主| 发表于 2003-4-7 22:01:30 | 显示全部楼层
这是Makefile。

  1. all: server.o client.o
  2. server.o : socket_server.c
  3.         gcc -o server.o socket_server.c
  4. client.o : socket_client.c
  5.         gcc -o client.o socket_client.c
复制代码

把socket_server.c,socket_client.c,Makefile保存在一个目录下,执行make 即可生成server.o和client.o。
 楼主| 发表于 2003-4-7 22:23:17 | 显示全部楼层
编写这个程序的意外收获就是加深了对端口的理解,过去一直不知道端口所起的作用,现在明白了,如果没有进程打开端口号,在/etc/services下的端口根本没有用处。而服务器端的编程之所以比客户端困难,是因为服务器还要考虑安全性和并发控制等问题。
发表于 2003-4-8 23:32:49 | 显示全部楼层
这个程序里面没有看到并发客户的支持,一次只能服务一个客户的咯。赶快加上吧!
 楼主| 发表于 2003-4-9 12:29:10 | 显示全部楼层
这个很简单,用select处理一下就行了。
 楼主| 发表于 2003-4-9 19:56:07 | 显示全部楼层
呵呵,说起来是一句话,做起来还是要费点功夫。改进后的程序还有两个问题没有解决。一。执行命令有迟滞。上一步的执行结果要下一次才能看到。二。在同时连接多个client的情况下,如果有一个client退出,server会中止。
改进后的socket_server.c如下:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<sys/time.h>
  4. #include<sys/ioctl.h>
  5. #include<stdio.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include<unistd.h>

  9. void exec_command(int c_sockfd)
  10. {
  11.     char buf[256];        /* 用BUFSIZ太大了(有8192个字节),改成256。呵呵。。。 */
  12.     int n;
  13.        
  14.     /* 把stdout和stderr重定向到sockfd */       
  15.     close(1);
  16.     dup(c_sockfd);
  17.     close(2);
  18.     dup(c_sockfd);
  19.    
  20.     n = read(c_sockfd, buf, sizeof(buf));
  21.    
  22.     if (n > 0) {
  23.            
  24.         //printf("%d server %s", n, buf);
  25.         system(buf);
  26.     }
  27. }

  28. int main()
  29. {
  30.     int server_sockfd, client_sockfd;
  31.     int client_len;
  32.     struct sockaddr_in server_address;
  33.     struct sockaddr_in client_address;
  34.     //char buf[BUFSIZ];
  35.     int result;
  36.     fd_set readfds, testfds;

  37.     server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  38.     server_address.sin_family = AF_INET;
  39.     server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  40.     server_address.sin_port = htons(19734);
  41.     client_len = sizeof(client_address);

  42.     bind(server_sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
  43.    
  44.     listen(server_sockfd, 5);
  45.     FD_ZERO(&readfds);
  46.     FD_SET(server_sockfd, &readfds);
  47.    
  48.     while (1) {
  49.             char ch;
  50.         int fd;
  51.         static int max_fd = 10;        /* 为了节省资源,这里最大只使用了10个文件描述符。 */
  52.         int n;
  53.        
  54.         testfds = readfds;
  55.         printf("server waiting\n");
  56.                
  57.         result = select(max_fd, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
  58.        
  59.         if(result < 1) {                /* 如果小于1 ,说明select执行出错。 */
  60.                 perror("server");
  61.                 exit(1);
  62.         }
  63.        
  64.         for(fd = 0; fd < max_fd; fd++) {
  65.                
  66.                 if(FD_ISSET(fd, &testfds)) {
  67.                         //printf("checking%d serverfd%d", fd, server_sockfd);
  68.                         if(fd == server_sockfd) {        /* 如果是来自server,则产生新的连接。 */
  69.                                 client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
  70.                                 FD_SET(client_sockfd, &readfds);
  71.                                 if(client_sockfd > max_fd) max_fd = client_sockfd;
  72.                                 printf("adding client on fd %d\n",client_sockfd);
  73.                         }
  74.                         else {        /* 检查有没有输入,如果没有,则关闭连接; 如果有,则执行命令。 */
  75.                                 ioctl(fd, FIONREAD, &n);
  76.                                
  77.                                 if(n == 0) {
  78.                                         close(client_sockfd);
  79.                                         FD_CLR(fd, &readfds);
  80.                                         printf("removing client on fd %d\n", fd);
  81.                                 }
  82.                                 else {
  83.                                         exec_command(fd);
  84.                                         //printf("exec_fd:%d\n", fd);
  85.                                 }
  86.                         }
  87.                        
  88.                 }
  89.         }
  90.     }
  91. }

复制代码
 楼主| 发表于 2003-4-9 19:58:03 | 显示全部楼层
这是改进后的socket_client.c:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. #include<stdio.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>

  7. /* exec_command()负责在与服务器连接成功后,处理传送过来的指令。 */
  8. void exec_command(int sockfd)
  9. {
  10.     char buf[256];                /* 用于处理输入的缓冲区 */
  11.     FILE *tty = NULL;                /* 用户终端 */
  12.     int n;
  13.     int i;

  14.     /* 打开用户终端。这果没有使用标准输入输出,因为在重定向时,标准输入输出不会指向用户终端,
  15.      * 用户输入带来困难。
  16.      */
  17.     tty = fopen("/dev/tty", "r");
  18.     if (tty == NULL) {
  19.         perror("can't open /dev/tty.");
  20.         exit(1);
  21.     }

  22.     while(1) {
  23.             if (fgets(buf, BUFSIZ, tty) == NULL || buf[0] == 'q')
  24.                 exit(0);
  25.             else                         /* 接受用户输入后,传递到server. */
  26.                 write(sockfd, buf, sizeof(buf));
  27.                
  28.         for(i = 0; i < 256; i++) buf[i] = 0;        /* 清空缓冲区 */
  29.                
  30.             if((n = read(sockfd, buf, sizeof(buf))) > 0) {
  31.                 fputs(buf, stdout);
  32.                 fflush(stdout);
  33.         }
  34.     }
  35. }

  36. int main()
  37. {
  38.     int sockfd;                        /* 套接字描述符 */
  39.     struct sockaddr_in address;        /* Internet 套接字地址结构 */
  40.     int result;                        /* 与server连接的结果 */

  41.     sockfd = socket(AF_INET, SOCK_STREAM, 0);

  42.     address.sin_family = AF_INET;
  43.     address.sin_addr.s_addr = inet_addr("127.0.0.1");
  44.     address.sin_port = htons(19734);

  45.     result =
  46.         connect(sockfd, (struct sockaddr *) &address, sizeof(address));
  47.     if (result == -1) {
  48.         perror("connect to server failed.");
  49.         exit(0);
  50.     }
  51.        
  52.     exec_command(sockfd);        /* 处理通信的内容 */

  53.     close(sockfd);
  54.     exit(0);
  55. }
复制代码
 楼主| 发表于 2003-4-9 19:59:38 | 显示全部楼层
最后还要注意的是,安全性问题仍然存在。
发表于 2003-4-10 01:45:43 | 显示全部楼层
好啊,从这个程序里我也学到了不少东西。

问:为什么不为每个用户创建单独的进程?或者thread?

服务器段的标准输入和输出都重新定向到了 socket 上面,标准错误呢?
客户端,用户不输入下一个命令的时候,从服务器那段来的输出也不会显示出来吧?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表