【问题标题】:select() in a proxy server代理服务器中的 select()
【发布时间】:2012-10-22 13:04:34
【问题描述】:

我正在用 c 构建一个代理服务器,我正在尝试理解 select() 函数。我已经完成了代码,以便从客户端建立连接,然后提取网址,以便可以建立另一个连接以连接到实际的 Web 服务器。然后页面被代理接收并传回给客户端。

我知道select() 将允许它处理多个客户端请求,但我不明白它如何帮助(或者更确切地说如何实现)与 Web 服务器的第二个连接。据我了解,我将不再需要 while 循环来继续从 Web 服务器接收数据并将其传递回客户端。

我是否需要为 Web 服务器连接设置第二个文件描述符?如果我正在处理两个或多个客户端请求,我如何确保它们与 Web 服务器的正确连接链接在一起?无论如何,我会很感激任何帮助。我已经完成了网络教程和其他一些在线教程,但几天后我还没有完全理解这一点。

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

string getHostString(const char *buf);

int main(int argc, char *argv[])
{   
    int sockfd, newsockfd, portno;
    int fdmax; //maximum file descriptor number
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_storage remoteaddr; //client address
    char clientIP[INET6_ADDRSTRLEN];

    //fd_set readfds, writefds, exceptfds;
    fd_set masterfds, readfds;
    struct timeval timeout;
    int rc;

    /*Set time limit. */
    timeout.tv_sec = 3;
    timeout.tv_usec = 0;

    /*Create a descriptor set containing the sockets */
    FD_ZERO(&readfds);
    FD_ZERO(&masterfds);
    /*FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);
    FD_SET(newsockfd, &readfds);

    rc = select(sizeof(readfds)*8, &readfds, NULL, NULL, &timeout);
    if (rc==-1){
        perror("select failed");
        return -1;
    }*/

    struct sockaddr_in serv_addr, cli_addr;
    int n;
    if (argc < 2) {
        fprintf(stderr,"ERROR, no port provided\n");
        exit(1);
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
       error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
             error("ERROR on binding");

    if((listen(sockfd,5)) == -1 )
        error("Server-listen() error!!!");
    printf("Server-listen() is OK...\n");


    FD_SET(sockfd, &masterfds);

    //keep track of the biggest file descriptor so far
    fdmax = sockfd; //so far it's this one

    for(;;){

        readfds = masterfds;

        if(select(fdmax+1, &readfds, NULL, NULL, NULL) == -1){
            error("Server-select() error !");
        }
        printf("Server-select() is OK...\n");

        for(int i = 0; i <= fdmax; i++){
            printf("i: %d sockfd %d\n", i, sockfd);
            if(FD_ISSET(i, &readfds)){
                if(i == sockfd){ //sockfd is the listener
                    //following handles new connections
                    clilen = sizeof(cli_addr);
                    if((newsockfd = accept(sockfd,(struct sockaddr *) &cli_addr, &clilen)) == -1)
                        error("Server-accept() error!!!");
                    else{
                        printf("Server-accept() is OK...\n");
                        FD_SET(newsockfd, &masterfds); //add to master set
                        if(newsockfd > fdmax) 
                            fdmax = newsockfd;
                        printf("selectserver: New connection from %s on  "
                                "socket %d\n",
                                inet_ntop(remoteaddr.ss_family,
                                    get_in_addr((struct sockaddr*)&cli_addr),
                                    clientIP, INET6_ADDRSTRLEN),
                                newsockfd);
                    }
                }
                else{
                    //handle data from a client
                    //Here is where the FIRST read occurs
                    bzero(buffer,256);
                    //n = read(newsockfd,buffer,255);
                    n = read(i,buffer,255);
                    if (n < 0) error("ERROR reading from socket");
                    printf("Here is the message: \n\n%s\n\n",buffer);

                    //string hoststring(buffer);
                    string hoststring(getHostString(buffer));

                    int html_port = 80;
                    int html_socket;

                    printf("Prior to struct addrinfo\n");

                    struct addrinfo hints, *res;

                    memset(&hints, 0, sizeof hints);
                    hints.ai_family = AF_UNSPEC;
                    hints.ai_socktype = SOCK_STREAM;
                    hints.ai_flags = AI_PASSIVE;

                    printf("Prior to getaddrinfo()\n");

                    char *address = new char[hoststring.size() +1];
                    address[hoststring.size()] = 0;
                    memcpy(address, hoststring.c_str(), hoststring.size());

                    //getaddrinfo(token, (char *)html_port, &hints, &res);
                    getaddrinfo(address, "80", &hints, &res);

                    printf("We are past getaddrinfo()\n");

                    //if (html_socket = socket(PF_INET, SOCK_STREAM, 0) < 0){
                    if ((html_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0){
                        printf("socket connection error\n");
                    }

                    printf("We are past socket()\n");

                    //char* address;
                    //address = new char[256];
                    //strncpy(address, "www.cs.ucr.edu", sizeof("www.cs.ucr.edu"));


                    printf("address: %s\n", address);    
                    struct hostent * host;
                    if ((host = gethostbyname(address)) == NULL){
                        printf("Problem with gethostbyname()\n");
                    }

                    printf("We are past gethostbyname() and about to connect.\n");

                    if ( connect(html_socket, res->ai_addr, res->ai_addrlen) < 0)
                        printf("Unsuccessful completion of connect()");

                    int bytes_sent, bytes_recvd;
                    char recv_buff[1024];
                    bytes_sent = send(html_socket, buffer, 256, 0);
                    cout << "bytes_sent: " << bytes_sent << endl;

                    //do{
                        bytes_recvd = recv(html_socket, recv_buff, 1024, 0);
                        cout << "bytes_rcvd: " << bytes_recvd << endl;
                        cout << recv_buff << endl;

                        //FD_ZERO(&readfds);
                        //FD_SET(newsockfd, &writefds);

                        //n = write(newsockfd,"I got your message\n",20);
                        //n = write(newsockfd,recv_buff,bytes_recvd);
                        n = write(i,recv_buff,bytes_recvd);
                        if (n < 0) error("ERROR writing to socket");
                        bzero(recv_buff, 1024);
                    //}while(bytes_recvd !=0);
                }
            }
        }

    }

    close(newsockfd);
    close(sockfd);
    return 0; 
}


string getHostString(const char *buf){
    string hoststring(buf); 
    hoststring = hoststring.substr(11);

    cout << "hoststring: " << hoststring << endl;
    int slashpos;
    slashpos = hoststring.find("/");
    int suffixendpos = hoststring.find("H") - slashpos;
    string suffix = hoststring.substr(slashpos, suffixendpos);
    hoststring = hoststring.substr(0, slashpos);

    cout << "hoststring: " << hoststring << endl;
    cout << "suffix: " << suffix << endl;

    return hoststring;

}

【问题讨论】:

    标签: c tcp proxy client-server select-function


    【解决方案1】:

    我知道您正在尝试编写一个单进程服务器来处理循环中的多个连接,并使用select() 来确定套接字描述符何时需要读取数据。然而,有时另一种方法实际上更容易且更具可扩展性。

    您是否考虑过使用multi-process 服务器,在每个套接字连接建立后,您fork() 使用一个新进程来处理请求?这样就无需担心select() 的工作原理以及将请求映射到正确的套接字描述符。

    请参阅Handle Multiple Connections here 作为一个合理的例子。

    除了使用fork() 之外,您还可以将线程与POSIX pthreads 库一起使用,这可能会提高您的效率。这里是a good multi-threaded tcp/ip server sample that uses pthreads.

    【讨论】:

    • 谢谢。是的,计划是最终使用 pthreads,我试图保持简单。我刚刚用select完成了它,所以现在我将尝试修改它以使用pthreads。
    【解决方案2】:

    您是否阅读了 select(2) 和 select_tut(2) 手册页?

    您是否阅读过相关章节,例如Advanced Linux Programming 或 Advanced Unix Programming?

    实际上,由于c10k 问题和最大文件描述符限制为256(或1024),即__FD_SETSIZE),select 系统调用已过时,您应该使用@987654326改为@ syscall。

    您应该在for 循环中设置readfds,就在select 调用之前,使用显式FD_ZERO 和FD_SET(fd_set 类型可能是一个数组,因此您不能分配它整体上。)。 select 系统调用可以修改它。

    不要忘记使用gcc -Wall -g 进行编译并使用调试器。您还可以研究现有免费软件 HTTP 客户端库或代理的源代码。

    【讨论】:

      猜你喜欢
      • 2015-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 2010-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多