2016-04-15 85 views
-1

对于一个项目,我正在构建像inetd这样的“超级服务器”。它应该从配置文件读取一组端口和命令,并为每个端口启动一个侦听器套接字。然后它应该使用select()来确定这些套接字中的一个或多个是否准备好读取。当select找到一个套接字时,它应该使用accept()来连接这个套接字,然后fork()一个子进程来执行该命令。不幸的是,当我尝试调用“nc -l localhost 12345”来测试它(在config.txt文件中使用'12345 echo“hello world”')时,select始终是超时或失败。Select()调用不工作localhost

你能发现任何我可能做错了吗?提前致谢!我一直在疯狂试图弄清楚这一点!

#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <sys/select.h> 

#include <iostream> 
#include <sstream> 
#include <fstream> 
#include <map> 

using namespace std; 

map<int,string> parse_config_file() { 
    string line; 
    ifstream file; 
    stringstream ss; 
    int port; 
    string command; 
    map<int,string> port_to_command; 

    file.open("config.txt"); 

    while (getline(file,line)) { 
     ss = stringstream(line); 
     ss >> port; 
     getline(ss,command); 
     port_to_command[port] = command; 
    } 

    file.close(); 

    return port_to_command; 
} 

void handle_client(int socket, string command) { 
    dup2(socket, STDIN_FILENO); 
    dup2(socket, STDOUT_FILENO); 
    execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL); 
} 

int main(int argc, const char * argv[]) { 

    int rc; 
    int readyfd; 
    int peerfd; 
    int maxfd = 0; 
    int port; 

    pid_t child_pid; 
    fd_set readfds; 

    struct timeval tv; 
    tv.tv_sec = 10; 
    tv.tv_usec = 0; 

    struct sockaddr* server_address; 
    socklen_t server_address_length = sizeof(server_address); 
    struct sockaddr client_address; 
    socklen_t client_address_length = sizeof(client_address); 

    map<int,string> port_to_command = parse_config_file(); 
    map<int,string>::iterator pcitr; 
    map<int,int> socket_to_port; 
    map<int,int>::iterator spitr; 


    // Create, bind, and listen on the sockets: 
    for (pcitr = port_to_command.begin(); pcitr != port_to_command.end(); pcitr++) { 

     int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
     if (sockfd < 0) { 
      cerr << "ERROR opening socket"; 
      exit(EXIT_FAILURE); 
     } 

     port = pcitr->first; 
     struct sockaddr_in server_address_internet; 
     bzero((char *) &server_address_internet, sizeof(server_address_internet)); 
     server_address_internet.sin_family = AF_INET; 
     server_address_internet.sin_addr.s_addr = INADDR_ANY; 
     server_address_internet.sin_port = htons(port); 
     server_address = (struct sockaddr *)&server_address_internet; 

     bind(sockfd, server_address, server_address_length); 

     rc = listen(sockfd, 10); 
     if (rc < 0) { 
      cerr << "listen() failed"; 
      exit(EXIT_FAILURE); 
     } 

     socket_to_port[sockfd] = pcitr->first; 

     if (sockfd > maxfd) { 
      maxfd = sockfd; 
     } 
    } 

    // Server Loop 
    while (true) { 

     // Rebuild the FD set: 
     FD_ZERO(&readfds); 
     for (spitr = socket_to_port.begin(); spitr != socket_to_port.end(); spitr++) { 
      FD_SET(spitr->first, &readfds); 
     } 

     // Select 
     rc = select(maxfd + 1, &readfds, NULL, NULL, &tv); 
     if (rc == 0) { 
      // Timeout 
      continue; 
     } else if (rc < 0) { 
      cerr << "select failed" << endl; 
      exit(EXIT_FAILURE); 
     } 

     // Find the socket that is ready to be read: 
     readyfd = -1; 
     for (spitr = socket_to_port.begin(); spitr != socket_to_port.end(); spitr++) { 
      if (FD_ISSET(spitr->first, &readfds)) { 
       readyfd = spitr->first; 
       break; 
      } 
     } 

     // Accept 
     peerfd = accept(readyfd, &client_address, &client_address_length); 
     if (peerfd < 0) { 
      cerr << "accept failed" << endl; 
      exit(EXIT_FAILURE); 
     } 

     // Fork to handle request: 
     child_pid = fork(); 
     if (child_pid == 0) { 
      port = ((struct sockaddr_in*)&client_address)->sin_port; 
      handle_client(peerfd, port_to_command[port]); 
      close(peerfd); 
      exit(EXIT_SUCCESS); 
     } else { 
      close(peerfd); 
     } 
    } 

    return 0; 
} 
+0

这是没有代码审查或调试服务!并且不要为无关语言的垃圾邮件标签。 – Olaf

回答

1

好吧,我确实发现了一些你做错了的事情。

  1. using namespace std; - 那部分显然是错误的。

  2. parse_config_file()不验证并检查配置文件的语法。错字或错位的字符将导致operator>>失败,这将不会被检测到。所以,一个命令的端口可能是随机的,未初始化的,或者是前一个命令端口的副本。

  3. 而且,最后我们得出这样的:

    struct sockaddr* server_address; 
    socklen_t server_address_length = sizeof(server_address); 
    

突击测验:什么是的sizeof(结构sockaddr *)?那么,这是一个指针,所以它会是4或8字节,在这里。

bind(sockfd, server_address, server_address_length); 

我相当确定struct sockaddr_in比这个大。快速检查确认它的长度为16个字节。您传递4或8个字节,作为16字节结构的大小。

你在这里有两个。获取大小错误,并且无法检查bind()返回的错误代码,因此您完全不知道系统调用总是失败。

您不能假定系统调用总是会成功。无论是bind(),socket(),connect()accept()。每个系统调用都可能失败。始终检查每个系统调用的返回值。检查系统调用的返回值可能是单调乏味的,或无聊,但必须完成。如果你这样做,你会发现最初的错误,错误sizeof()