【问题标题】:How to act as proxy between client and server?如何充当客户端和服务器之间的代理?
【发布时间】:2020-07-17 12:32:07
【问题描述】:

我正在编写一个程序,它应该充当 Web 服务器和浏览器之间的简单代理。浏览器连接到我的代理程序,我的程序连接到 Web 服务器。我的代理程序应该简单地将它从浏览器接收到的所有数据转发到网络服务器,反之亦然,无需以任何方式修改数据,也无需执行任何缓存。

我已经设法从网络服务器获得回复,但我如何将该回复定向到我的浏览器?还有什么方法可以将它放入某种无限循环中,我可以随意recvsend

编辑:

我已经差不多了。我只需要知道如何连续读取套接字。收到 Http 重定向后程序意外关闭。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>

#define SERVER_PORT  8080
#define SA struct sockaddr
#define MAX 80

pthread_t ptid, ptidd;

#define TRUE             1
#define FALSE            0

struct sockets_struct {
    int server_sd;
    int client_sd;
    int new_sd;
}socks;

// Function designed to act as client. 
void *client_func(void *sockets)
{ 
    char   buffer[MAX];
    struct sockaddr_in servaddrr;
    struct sockets_struct *socks = (struct sockets_struct*)sockets;
    int    i, len, rc, on = 1;
    //bzero(&servaddrr, sizeof(servaddrr)); 
        
    // assign IP, PORT 
    servaddrr.sin_family = AF_INET; 
    servaddrr.sin_addr.s_addr = inet_addr("192.168.0.1"); 
    servaddrr.sin_port = htons(80);

    // connect the client socket to server socket 
    if (connect(socks->client_sd, (SA*)&servaddrr, sizeof(servaddrr)) != 0) { 
        printf(" client: connection with the server failed...\n"); 
        exit(0); 
    } 
    else
        printf(" client: connected to the remote server..\n");
        

    do {
        rc = recv(socks->client_sd, buffer, sizeof(buffer), 0);
        
        if (rc < 0) {
            if (errno != EWOULDBLOCK) {
                perror(" client: recv() failed\n");
            }
            break;
        }

        if (rc == 0) {
            printf(" client: Connection closed\n");
            break;
        }

        len = rc;
        printf(" client: %d bytes received\n", len);
        rc = send(socks->new_sd, buffer, len, 0);
        
        if (rc < 0) {
            perror(" client: send() failed");
            break;  
        }

    } while(TRUE);
}

// Function designed to act as server. 
void *server_func(void *sockets)
{
    int    len, rc, on = 1;
    int    desc_ready, end_server = FALSE, compress_array = FALSE;
    int    close_conn;
    char   buffer[80];
    struct sockaddr_in6   addr;
    int    timeout;
    struct pollfd fds[200];
    int    nfds = 1, current_size = 0, i, j;
    struct sockets_struct *socks = (struct sockets_struct*)sockets;
    
    rc = setsockopt(socks->server_sd, SOL_SOCKET, SO_REUSEADDR, 
                                            (char *)&on, sizeof(on));
    if (rc < 0) {
        perror(" server: setsockopt() failed\n");
        close(socks->server_sd);
        exit(-1);
    }

    rc = ioctl(socks->server_sd, FIONBIO, (char *)&on);
    
    if (rc < 0) {
        perror(" server: ioctl() failed\n");
        close(socks->server_sd);
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin6_family      = AF_INET;
    memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));
    addr.sin6_port        = htons(SERVER_PORT);
    
    rc = bind(socks->server_sd, (struct sockaddr *)&addr, sizeof(addr));
    
    if (rc < 0) {
        perror(" server: bind() failed");
        close(socks->server_sd);
        exit(-1);
    }

    rc = listen(socks->server_sd, 32);
    
    if (rc < 0) {
        perror(" server: listen() failed");
        close(socks->server_sd);
        exit(-1);
    }

    memset(fds, 0 , sizeof(fds));
    fds[0].fd = socks->server_sd;
    fds[0].events = POLLIN;
    timeout = (3 * 60 * 1000);

    do {
        printf(" server: waiting on poll()...\n");
        
        rc = poll(fds, nfds, timeout);
        if (rc < 0) {
            perror(" server: poll() failed\n");
            break;
        }

        if (rc == 0) {
            printf(" server: poll() timed out.  End program.\n");
            break;
        }

        current_size = nfds;
        for (i = 0; i < current_size; i++) {
            if (fds[i].revents == 0)
                continue;

            if (fds[i].revents != POLLIN) {
                printf(" server: Error! revents = %d\n", fds[i].revents);
                end_server = TRUE;
                break;

            }
        
            if (fds[i].fd == socks->server_sd) {
                printf("  server: Listening socket is readable\n");
                socks->new_sd = accept(socks->server_sd, NULL, NULL);
                if (socks->new_sd < 0) {
                    if (errno != EWOULDBLOCK) {
                        perror("  server: accept() failed\n");
                        end_server = TRUE;
                    }
                    break;
                }

                printf("  server: new incoming connection - %d\n", socks->new_sd);
                fds[nfds].fd = socks->new_sd;
                fds[nfds].events = POLLIN;
                nfds++;
            }
            else {
                printf("  server: Descriptor %d is readable\n", fds[i].fd);
                close_conn = FALSE;

                do {
                    rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);
                    if (rc < 0) {
                        if (errno != EWOULDBLOCK) {
                            perror("  recv() failed");
                            close_conn = TRUE;
                        }
                        break;
                    }

                    if (rc == 0) {
                        printf(" server: Connection closed\n");
                        close_conn = TRUE;
                        break;
                    }

                    len = rc;
                    printf(" server: %d bytes received \n", len);
                    rc = send(socks->client_sd, buffer, len, 0);
                    
                    if (rc < 0) {
                        perror(" server: send() failed\n");
                        close_conn = TRUE;
                        break;
                    }

                } while(TRUE);

                if (close_conn) {
                    close(fds[i].fd);
                    fds[i].fd = -1;
                    compress_array = TRUE;
                }

            }  /* End of existing connection is readable */
        } /* End of loop through pollable descriptors */

    } while (end_server == FALSE); /* End of serving running. */

}   

int main (int argc, char *argv[])
{
    socks.server_sd = socket(AF_INET, SOCK_STREAM, 0);
    socks.client_sd = socket(AF_INET, SOCK_STREAM, 0);

    if (socks.server_sd == -1) { 
        printf("socket \"server_sd\" creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket \"server_sd\" successfully created..\n");
        
    if (socks.client_sd == -1) { 
        printf("socket \"client_sd\" creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket \"client_sd\" successfully created..\n");
    
    pthread_create(&ptidd, NULL, &client_func, &socks);
    pthread_create(&ptid, NULL, &server_func, &socks);
    pthread_join(ptidd, NULL);
    
    return 0;

}

【问题讨论】:

  • 我是否正确理解您的问题,即您的程序充当 Web 客户端(浏览器)和 Web 服务器之间的中间人,以便 Web 客户端连接到您的程序,而您的程序连接到 Web 服务器.建立这些连接后,您的程序应该将从 Web 服务器接收到的所有数据转发到 Web 客户端,反之亦然?
  • 你需要密切关注你收到了多少字节。你要求 8080 字节,但如果客户端只发送 200,那么你只需要发送 200 到服务器! (不是 200 字节,然后是 7880 额外的 0 字节)
  • 您使用recvwrite的功能组合有什么特别的原因吗?通常,要么使用套接字函数recvsend,要么使用更通用的函数readwrite,但使用recvwrite 对我来说似乎是一个奇怪的组合。不过,我对 UNIX 编程有点陌生。
  • 您是否正在尝试实现一个不知道它所代理的数据的协议的代理?做这两件事的过程是完全不同的,你的代码在中间的某个地方。那肯定行不通。
  • @Madao:我现在已经编辑了您的问题以使其更清楚。如果您不喜欢我的更改,请随时恢复它们。

标签: c sockets server


【解决方案1】:

您可以编写一个能够理解它所代理的数据的代理,也可以编写一个不理解它的代理。你的问题表明你想写一个不写的。这绝对是更简单的方法。

因此,一旦所有连接都设置完毕,您有两件事要做。您需要从一个连接读取数据并将其发送到另一个连接。您还需要从另一个连接读取数据并将其发送到第一个连接。

使用两个线程是一种简单的方法。你也可以fork每个方向一个进程。但每个人学习的第一种方式是selectpoll 循环。您可以将“选择循环代理”打入您喜欢的搜索引擎以查找大量示例。

【讨论】:

    【解决方案2】:

    注意:此答案是在 OP 编辑​​问题并将线程添加到问题中的代码之前编写的。

    我在您的算法中看到的主要问题是,您似乎假设您将始终在一次recvread 调用中接收来自客户端和服务器的所有数据。即使 Web 客户端(浏览器)只发送一个 HTTP 请求(即使只加载一个网页,这种情况也不太可能)。

    我建议你改用以下算法:

    1. 等待网络客户端(浏览器)与您的程序建立连接。
    2. 创建一个连接到 Web 服务器的新套接字。
    3. 等待建立 Web 服务器连接。您的程序不需要此步骤,因为您使用的是阻塞式connect 调用。只有在使用非阻塞或异步套接字时才需要。
    4. 等待可在两个套接字中的任何一个上读取新数据,例如使用函数select。当此函数返回时,它将指示在哪些套接字上可以对recv 进行非阻塞调用。
    5. select 报告为有数据可供读取的套接字读取,然后使用send 函数将此数据写入另一个套接字。
    6. 转到第 4 步。

    然而,这个算法有一个可能的问题:它假设send 总是能成功地立即写入所有字节,而不会阻塞。根据具体情况(例如操作系统对套接字的缓冲),情况可能并非总是如此。它可能一次只能部分发送缓冲区的内容。 documentation of the function send 没有指定如果 send 函数的缓冲区太大而无法立即发送会发生什么,即它是否会阻塞直到 所有 数据发送或是否会在能够执行部分发送时立即返回。

    因此,您的算法应该能够处理仅部分发送数据的情况,例如,通过在第 4 步中检查是否可以写入更多数据,如果不是所有数据都在之前的调用中写入send.

    另外,请注意,当您的程序阻塞 send 调用时,它不会处理其他方向的任何通信。例如,当您的程序在将数据从客户端转发到服务器时阻塞send 调用时,它将无法将任何数据从服务器转发到客户端。我认为这不会对 HTTP 协议造成问题,因为客户端和服务器从不同时发送数据,因为服务器总是等待客户端完成其请求,然后客户端等待服务器完成其回复,在它发送另一个请求之前。但是,这可能是其他协议的问题。特别是,如果您完全阻止一个方向的通信,这可能会导致客户端或服务器也卡在阻塞的sendrecv 调用上。这可能会导致所有三个程序都出现死锁。

    因此,您可能需要考虑改用non-blocking sockets or asynchronous sockets,这样您就可以随时继续双向转发网络流量。

    或者,您可以继续使用阻塞套接字调用并创建两个线程,一个用于将数据从客户端转发到服务器,另一个用于将数据从服务器转发到客户端。这样,任何方向的交流都不会被阻挡。但我建议使用非阻塞套接字或异步套接字,因为线程可能会变得混乱。

    您的算法还应该做的一件事是处理有序的套接字关闭(由 recv 返回 0 表示)和错误情况。如何执行此操作取决于您使用的套接字类型,即它们是阻塞式、非阻塞式还是异步的。

    【讨论】:

    • 非常感谢。
    • HTTP 协议不禁止客户端和服务器同时发送数据。许多建立在 HTTP 之上的协议(例如 websockets)可以同时双向传输数据。归结为 OP 的程序是打算成为玩具还是打算成为可靠的工具。写后者要复杂得多。 (HTTP 协议规范有一个代理要求列表。请参阅 HTTP/1.1 规范的第 8.1.3 节。)
    猜你喜欢
    • 2013-08-10
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2022-11-16
    • 2011-02-10
    • 1970-01-01
    • 2011-01-20
    • 2013-10-30
    相关资源
    最近更新 更多