博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
golang 简单的实现内 网 穿 透,用户访问本地服务。
阅读量:6484 次
发布时间:2019-06-23

本文共 4666 字,大约阅读时间需要 15 分钟。

一、功能描述:

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

二、原理图如下:

三、实现代码如下:

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

转载地址:http://bliuo.baihongyu.com/

你可能感兴趣的文章
实参传递不当导致的运行时错误
查看>>
sqlserver 批量删除存储过程(转)
查看>>
自建型呼叫中心
查看>>
Inno setup中定制安装路径
查看>>
要懂得对你的老板好一点!
查看>>
visio如何让动态连接线的单箭头变成双箭头?
查看>>
poj 1273 Drainage Ditches 网络流最大流基础
查看>>
Bash: how to check if a process id (PID) exists
查看>>
Mirantis Fuel fundations
查看>>
启动Tomcat一闪而过——分析及解决过程
查看>>
Android intent action大全
查看>>
使用 Flash Builder 的 Apple iOS 开发过程
查看>>
RabbitMq_05_Topics
查看>>
redis.conf
查看>>
SCALA中的函数式编程
查看>>
将List<int> 转换为用逗号连接为字符串
查看>>
C/C++中extern关键字详解
查看>>
Eclipse 最有用的快捷键
查看>>
K & DN 的前世今生(微软开源命名变革)
查看>>
--@angularJS--angular与BootStrap3的应用
查看>>