一、功能描述:

客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务。

二、原理图如下:

三、实现代码如下:

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,代码仅供参考,切勿用于生产环境。

golang 简单的实现内 网 穿 透,用户访问本地服务。的更多相关文章

  1. Pandorabox固件路由器上申请Let's Encrypt证书,为内网里的多个web服务提供SSL支持

    对于家中宽带有公网IP的用户,有时我们需要将路由器内部网络的某些web服务通过端口转发暴露到外网(例如NAS远程访问),但HTTP是明文传输,有被监听的风险:如果在NAS上使用自签名证书,再端口转发, ...

  2. [转]Zen Cart官网屏蔽中国用户访问的真正原因

    近需要到 zen cart 的官方网站查询一些资料,却发现无法访问!在网上搜索一番以后,原来如此. Zen Cart官网屏蔽中国用户访问的真正原因 作者:[鹏程万里] 日期:2011-03-26 准备 ...

  3. 内网穿透神器(ngrok)服务端部署【分享一台自己的ngrok服务器】【多平台】

    Ngrok为何物 “ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道.ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放.”这是百度百科上给Ng ...

  4. 内网穿透神器ngrok——将本地项目驾到外网

    相信做Web开发的同学们,经常会遇到需要将本地部署的Web应用能够让公网环境直接访问到的情况,例如微信应用调试.支付宝接口调试等.这个时候,一个叫ngrok的神器可能会帮到你,它提供了一个能够在公网安 ...

  5. zookeeper两台内网服务器彼此调不到服务的问题。

    Start NettyClient /172.20.11.52 connect to the server /172.20.11.52:20881, dubbo version: 2.5.3, cur ...

  6. 内网中让其他人访问我电脑上的asp.net应用程序

    打开防火墙,高级配置,新建入站规则,选择“端口”,下一步,填写特定本地端口,然后都是点击下一步,命名规则,到此,内网中的其他人可以访问你的程序了

  7. 本地Linux虚拟机内网穿透,服务器文件下载到本地磁盘

    本地Linux虚拟内网穿透 把服务器文件下载到本地磁盘 https://natapp.cn/ 1.注册账户点击免费隧道  

  8. 使用内网映射工具Holer将本地的Web应用映射到公网上访问

    Holer exposes local servers behind NATs and firewalls to the public internet over secure tunnels. Su ...

  9. linux 能访问内网,但不能访问外网?解决方案

    用iptables就可以了 iptables -F iptables -t nat -F iptables -A INPUT -s -d -j ACCEPT iptables -A INPUT -d ...

随机推荐

  1. 在新安装的Linux系统中,防火墙默认是被禁掉的,一般也没有配置过任何防火墙的策略,所有不存在/etc/sysconfig/iptables文件。

    原因:在新安装的Linux系统中,防火墙默认是被禁掉的,一般也没有配置过任何防火墙的策略,所有不存在/etc/sysconfig/iptables文件. 解决办法: .随便写一条iptables命令配 ...

  2. mysql 忘记密码解决方案

    Mysql 忘记root密码的完美解决方法 转载  2016-12-23   作者:MR.QiGao    我要评论 通常在使用Mysql数据库时,如果长时间没有登陆,或者由于工作交接完成度不高,会导 ...

  3. Python模块和类.md

    模块的定义 代码的层次结构 对于python的层次结构一般为包->模块 包也就是文件夹,但是文件夹下必须有文件"init.py"那么此文件夹才可以被识别为包."in ...

  4. 推荐一款好用并且免费的markdown软件 Typora

    Typora 的linux 安装步骤 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA300B7755AFCFAE su ...

  5. matlab 画二维图与三维图

    二维图 ezplot('sin(x)');%默认范围 ezplot('sin(x)',[-4 4]);%自己设定范围 三维图 ezmesh('x*x+y*y');%默认范围

  6. redis 存储java对象 两种方式

    根据redis的存储原理,Redis的key和value都支持二进制安全的字符串 1.利用序列化和反序列化的方式存储java对象我们可以通过对象的序列化与反序列化完成存储于取出,这样就可以使用redi ...

  7. Spring 手动 提交事务

    1.配置文件 applicationContext.xml: <bean id="txManager"class="org.springframework.jdbc ...

  8. nginx反向代理:两个域名指向不同web服务端口

    一台服务器上安装了zabbix服务和jumpserver服务,两个域名zabbix.xxxx.xxxx和jumserver.xxx.xxxx 一.编辑/etc/nginx/conf.d目录下nginx ...

  9. javascript中所有函数的参数都是按值传递的

    [javascript中所有函数的参数都是按值传递的] 参考:http://www.jb51.net/article/89297.htm

  10. fiddler常用操作

    fiddler常用操作 标签(空格分隔): fiddler fidrdler抓取https请求: fiddler是一个很好的抓包工具,但是默认的是抓取HTTP的,对于pc的https的会提示网页不安全 ...