golang 简单的实现内 网 穿 透,用户访问本地服务。
发布时间:2020-12-16 18:02:28 所属栏目:大数据 来源:网络整理
导读:一、功能描述: 客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务。 二、原理图如下: 三、实现代码如下: server.go代码: package main;import ("net""fmt""flag""os")type MidServer struct {//客户端监听clientLis *net.TCPListener;//
一、功能描述: 客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务。 二、原理图如下: 三、实现代码如下: 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,代码仅供参考,切勿用于生产环境。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |