Objective-C (iOS)实现TCP反向代理(Port forward隧道)

之前有一篇文章《华为AnyOffice eSDK建立TCP端口转发隧道》介绍了如何使用Java建立一条TCP端口隧道。实现一个将远程端口反向代理到本地的效果,类似于Nginx。比如你有一个远程Web服务器1.2.3.4:80,你建立起TCP转发隧道后,访问本地的127.0.0.1:8080端口就等于访问远程服务器的80端口。那么反向代理的轮子很多,比如Nginx,Haproxy,NodeJS,Caddy,Apache等等,为什么要自己写一个呢。我当初写这个主要目的还是为了在Android和iPhone手机端使用反向代理功能,并基于此兼容华为AnyOffice提供的eSDK,通过建立隧道来实现4层TCP协议的VPN通讯(注:华为的eSDK只提供了4层的HTTP协议的API,对4层协议没有很好的支持)。


所以这里,就介绍一下如何在IOS上实现一条TCP反向代理隧道。虽然说是用Objc开发的,但主要还是使用C语言实现的,好在IOS和MAC OS都可以在.m文件中混用C/C++和Objc代码,使用C语言的原因是华为的eSDK是用的C语言接口,他们重写了recv()和send(),变成了svn_recv()和svn_send()方法,所以为了实现最好的兼容,我们的隧道也采用C书写。Objc也有很大的任务,那就是使用GCD开启线程,使得整个代码与JAVA代码非常相似,简直如同JAVA代码一句一句翻译过来的一样,所有线程的调度也是一样的,所以在这里我就只贴代码,原理可以去我之前的博客查看。

下面就是在IOS和MAC OS中建立TCP隧道的代码了,下面代码使用的是原生socket,可以跑在任意一台iPhone和MAC主机上,对于MAC,直接在xcode中新建一个命令行应用然后执行main函数即可。对于iOS,需要把main函数放在一条子线程中异步执行。如果要和华为eSDK对接,只需把某些recv()/send()调用换成svn_recv(),svn_send()调用即可。并把下面的remote_socket初始化成svn_socket。并引入华为eSDK的头文件。具体需要更换的地方我都写在注释中了。


源代码中有两处IP地址的赋值,分别是0.0.0.0:8080 和 1.2.3.4:80,其中

0.0.0.0:8080是隧道的入口地址,你使用其他程序(比如浏览器)访问127.0.0.1:8080就等同与访问1.2.3.4:80

1.2.3.4:80是隧道出口地址,也是被反向代理的地址,使用过Nginx的攻城狮们应该不陌生。


#import 
#include 
#include 
#include 
#include 
#include 

//int startTunnel();

int main(int argc, const char * argv[]) {
    int result = -8;
    while(result == -8){//在断网的时候,accept函数会失效然后退出,然后不断的尝试重建隧道
        result = startTunnel();
        sleep(1);
    }
    
}

int startTunnel() {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    
    struct sockaddr_in server_addr;
    server_addr.sin_len = sizeof(struct sockaddr_in);
    server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    bzero(&(server_addr.sin_zero),8);
    
    //创建socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接
    int haha=1;
    setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &haha, sizeof(haha));//程序退出后可以解除端口占用
    if (server_socket == -1) {
        perror("socket error");
        return 1;
    }
    
    //绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信
    int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (bind_result == -1) {
        perror("bind error");
        return -9;
    }
    
    //listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满>了,且有新的连接的时候,对方可能会收到出错信息。
    if (listen(server_socket, 5) == -1) {
        perror("listen error");
        return 1;
    }
    
    while(1){
        struct sockaddr_in client_address;
        socklen_t address_len;
        memset(&client_address,0,sizeof(client_address));
        address_len = sizeof(client_address);
        int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len);
        printf("\n\n\nclient socket is %d ip address is %s:%d\n",client_socket,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));//打印客户端的请求地址

        if(client_socket == -1){
            printf("what the fuck\n");
            shutdown(client_socket, SHUT_RDWR);
            return -8;
        }
        
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //在这里应该新开一个线程执行下面的操作
            printf("接收到了新的连接\n");
            //返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。
            if (client_socket == -1) {
                perror("accept error");
                close(client_socket);
                return;
            }
            
            //-----------初始化发送到真实服务器的svn_socket--------
            struct sockaddr_in remote_addr;
            remote_addr.sin_len = sizeof(struct sockaddr_in);
            remote_addr.sin_family = AF_INET;
            remote_addr.sin_port = htons(80);//emessage服务器的端口
            remote_addr.sin_addr.s_addr = inet_addr("1.2.3.4");//emessage服务器的IP地址
            bzero(&(remote_addr.sin_zero),8);
            
            int remote_socket = socket(AF_INET, SOCK_STREAM, 0);//这个socket应该是华为的svn_socket,用于和真实服务器通信
            if (remote_socket == -1) {
                perror("socket error");
                close(client_socket);
                close(remote_socket);//此处应该为svn_close
                return;
            }
            
            if (connect(remote_socket, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr_in))==0)  {//此处应该是svn_connect
                printf("destination is %d ip address is %s:%d\n",remote_socket,inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port));
                //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。
                printf("远程服务器连接成功\n");
            } else {
                close(server_socket);
                return;
                //此处应终止,不要再往下走了
                
            }
            
            //----------下面是原生socket接收请求---------
            //下面代码是接收手机APP发出的请求,然后转发到VPN里面
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                //下面这个while循环应该单独跑在一个线程里,是把客户端转发到服务器
                long byte_num = 10000;
                char recv_msg[2048];//xmpp向外发送的消息
                while (byte_num>0) {
                    bzero(recv_msg, 2048);
                    byte_num=0;
                    byte_num = recv(client_socket,recv_msg,2048,0);//接收到了xmpp向外发送的数据
                    if(byte_num < 1)break;
                    printf("length==%ld\n",byte_num);
                    //把接收到的字节再发到真实服务器
                    if (send(remote_socket, recv_msg, byte_num, 0) == -1) {//此处应该是svn_send
                        perror("send error");
                        close(remote_socket);//此处应该换成svn_socket
                        close(client_socket);
                    }else{
                        printf("向远程服务器发送数据成功\n");
                    }
                    
                }
                close(client_socket);
                close(remote_socket);
                printf("全部请求都发送完毕\n");
            });
            
            //下面的代码是接收服务器发来的消息,然后再发给手机APP
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                //下面这个while也要单独跑在一个线程里,是把真实服务器转发给客户端
                long reply_num = 10000;
                char server_msg[2048];//服务器响应的消息
                while (reply_num>0) {
                    reply_num=0;
                    bzero(server_msg, 2048);
                    reply_num = recv(remote_socket, server_msg, sizeof(server_msg), 0);//接收到了真实服务器响应的消息,此处应为svn_recv
                    printf("length==%ld\n",reply_num);
                    if(reply_num < 1)break;
                    if (send(client_socket, server_msg, reply_num, 0) == -1) {//把服务器响应的信息发送给xmpp
                        perror("send error");
                        close(remote_socket);//此处应该换成svn_socket
                        close(client_socket);
                    } else {
                        //server_msg[reply_num]='\0';
                        //printf("向客户端发送数据成功%s\n",server_msg);
                    }
                }
                printf("全部数据都发送完毕\n");
                close(remote_socket);//此处应该换成svn_socket
                close(client_socket);
            });
            printf("全部的线程都退出了\n");
        });
     }
    }
    return 0;
}




你可能感兴趣的:(socket,objective-c,ios,tcp)