golang 簡單的實現內 網 穿 透,用戶訪問本地服務。


一、功能描述:

客戶端通過訪問外網服務器上指定端口,間接訪問自已本地的內網服務。

二、原理圖如下:

三、實現代碼如下:

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,代碼僅供參考,切勿用於生產環境。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM