一、功能描述:
客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务。
二、原理图如下:
三、实现代码如下:
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",transferPort)); if err != nil { return err; } m.transferlis,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",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","服务器端口"); localPort := flag.Int("localPort",80,"本地端口"); flag.Parse(); if flag.NFlag() != 3 { flag.PrintDefaults(); os.Exit(1); } //建立与服务器的连接 remote,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 简单的实现内 网 穿 透,用户访问本地服务。全部内容,希望文章能够帮你解决golang 简单的实现内 网 穿 透,用户访问本地服务。所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)