一、功能描述:

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

二、原理图如下:

三、实现代码如下:

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

posted on 2017-07-21 17:05  怀素真  阅读(2914)  评论(0编辑  收藏  举报