一、功能描述:
客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务。
二、原理图如下:
三、实现代码如下:
server.go代码:
package main; import ( "net" "fmt" "flag" "os" ) type MidServer struct { //客户端监听 clientLis *net.TCPListener; //后端服务连接 transferLis *net.TCPListener; //所有通道 channels map[int]*Channel; //当前通道ID curChannelId int; } type Channel struct { //通道ID id int; //客户端连接 client net.Conn; //后端服务连接 transfer net.Conn; //客户端接收消息 clientRecvMsg chan []byte; //后端服务发送消息 transferSendMsg chan []byte; } //创建一个服务器 func New() *MidServer { return &MidServer{ channels: make(map[int]*Channel), curChannelId: 0, }; } //启动服务 func (m *MidServer) Start(clientPort int, transferPort int) error { addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", clientPort)); if err != nil { return err; } m.clientLis, err = net.ListenTCP("tcp", addr); if err != nil { return err; } addr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", transferPort)); if err != nil { return err; } m.transferLis, err = net.ListenTCP("tcp", addr); if err != nil { return err; } go m.AcceptLoop(); return nil; } //关闭服务 func (m *MidServer) Stop() { m.clientLis.Close(); m.transferLis.Close(); //循环关闭通道连接 for _, v := range m.channels { v.client.Close(); v.transfer.Close(); } } //删除通道 func (m *MidServer) DelChannel(id int) { chs := m.channels; delete(chs, id); m.channels = chs; } //处理连接 func (m *MidServer) AcceptLoop() { transfer, err := m.transferLis.Accept(); if err != nil { return; } for { //获取连接 client, err := m.clientLis.Accept(); if err != nil { continue; } //创建一个通道 ch := &Channel{ id: m.curChannelId, client: client, transfer: transfer, clientRecvMsg: make(chan []byte), transferSendMsg: make(chan []byte), }; m.curChannelId++; //把通道加入channels中 chs := m.channels; chs[ch.id] = ch; m.channels = chs; //启一个goroutine处理客户端消息 go m.ClientMsgLoop(ch); //启一个goroutine处理后端服务消息 go m.TransferMsgLoop(ch); go m.MsgLoop(ch); } } //处理客户端消息 func (m *MidServer) ClientMsgLoop(ch *Channel) { defer func() { fmt.Println("ClientMsgLoop exit"); }(); for { select { case data, isClose := <-ch.transferSendMsg: { //判断channel是否关闭,如果是则返回 if !isClose { return; } _, err := ch.client.Write(data); if err != nil { return; } } } } } //处理后端服务消息 func (m *MidServer) TransferMsgLoop(ch *Channel) { defer func() { fmt.Println("TransferMsgLoop exit"); }(); for { select { case data, isClose := <-ch.clientRecvMsg: { //判断channel是否关闭,如果是则返回 if !isClose { return; } _, err := ch.transfer.Write(data); if err != nil { return; } } } } } //客户端与后端服务消息处理 func (m *MidServer) MsgLoop(ch *Channel) { defer func() { //关闭channel,好让ClientMsgLoop与TransferMsgLoop退出 close(ch.clientRecvMsg); close(ch.transferSendMsg); m.DelChannel(ch.id); fmt.Println("MsgLoop exit"); }(); buf := make([]byte, 1024); for { n, err := ch.client.Read(buf); if err != nil { return; } ch.clientRecvMsg <- buf[:n]; n, err = ch.transfer.Read(buf); if err != nil { return; } ch.transferSendMsg <- buf[:n]; } } func main() { //参数解析 localPort := flag.Int("localPort", 8080, "客户端访问端口"); remotePort := flag.Int("remotePort", 8888, "服务访问端口"); flag.Parse(); if flag.NFlag() != 2 { flag.PrintDefaults(); os.Exit(1); } ms := New(); //启动服务 ms.Start(*localPort, *remotePort); //循环 select {}; }
client.go代码:
package main; import ( "net" "fmt" "flag" "os" ) func handler(r net.Conn, localPort int) { buf := make([]byte, 1024); for { //先从远程读数据 n, err := r.Read(buf); if err != nil { continue; } data := buf[:n]; //建立与本地80服务的连接 local, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort)); if err != nil { continue; } //向80服务写数据 n, err = local.Write(data); if err != nil { continue; } //读取80服务返回的数据 n, err = local.Read(buf); //关闭80服务,因为本地80服务是http服务,不是持久连接 //一个请求结束,就会自动断开。所以在for循环里我们要不断Dial,然后关闭。 local.Close(); if err != nil { continue; } data = buf[:n]; //向远程写数据 n, err = r.Write(data); if err != nil { continue; } } } func main() { //参数解析 host := flag.String("host", "127.0.0.1", "服务器地址"); remotePort := flag.Int("remotePort", 8888, "服务器端口"); localPort := flag.Int("localPort", 80, "本地端口"); flag.Parse(); if flag.NFlag() != 3 { flag.PrintDefaults(); os.Exit(1); } //建立与服务器的连接 remote, err := net.Dial("tcp", fmt.Sprintf("%s:%d", *host, *remotePort)); if err != nil { fmt.Println(err); } go handler(remote, *localPort); select {}; }
四、测试
1、先把server.go上传到外网服务器上,安装GO环境,并编译,然后运行server
> ./server -localPort 8080 -remotePort 8888
2、在本地编译client.go,运行client
> client.exe -host 外网服务器IP -localPort 80 -remotePort 8888
3、浏览器访问外网服务器8080端口
当我浏览器访问时,外网服务器的server会打印两次MsgLoop exit,这是因为谷歌浏览器会多一个favicon.ico请求,不知道其他浏览器会不会。
注意,上面的server.go和client.go代码不排除会有BUG,代码仅供参考,切勿用于生产环境。
版权声明:博主文章,可以不经博主允许随意转载,随意修改,知识是用来传播的。