代码很简单,这里的应用场景是可以通过端口转发,将目的IP指向动态IP上,从而实现直接访问通过电信等家庭网络中的非标准服务器。
如果外网IP较难获取到,那估计只能等IPV6了。
至于如何获取到目的IP(动态变化),只需要花百元购一个云服务器/云空间(它的IP是固定的)转发一下即可。
当然,现在的云服务器也不贵。这都是穷闹的。不过毕竟云服务器没家里的带宽和自由。
通过以下转发,即可浏览器输入localhost看到指定的内容,目的端口就任意了(家庭宽带是封禁了80端口的)。
如果通过修改本机hosts,是不是可以实现自定义域名访问?
package main
import (
"errors"
"fmt"
"io"
"net"
)
func main() {
// 要监听的本地端口
localPort := "80"
// 要转发到的远程端口
remoteHost := "192.168.1.40:9999"
go func() {
// 监听本地端口
listener, err := net.Listen("tcp", ":"+localPort)
if err != nil {
fmt.Println("Error listening:", err)
return
}
defer listener.Close()
fmt.Println("Listening on :" + localPort)
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting:", err)
continue
}
// 处理连接
go handleConnection(conn, remoteHost)
}
}()
// 阻塞主线程
select {}
}
func handleConnection(local net.Conn, remoteAddr string) {
defer local.Close()
remote, err := net.Dial("tcp", remoteAddr)
if err != nil {
fmt.Println("Error dialing remote:", err)
return
}
defer remote.Close()
// 转发数据
go copyData(local, remote)
copyData(remote, local)
}
func copyData(source, dest net.Conn) {
defer source.Close()
buf := make([]byte, 1024)
for {
n, err := source.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
} else {
fmt.Println("Error copying data:", err)
break
}
}
dest.Write(buf[:n])
}
}
解决动态外网IP的问题,而无需中转(云服务器),前文提到的百元也可以节约了。
通过ntfy.sh这个公共消息服务,发送和接收消息,可以中转IP地址信息。觉得不安全也可以自建服务。
顺便实现了消息显示,以及IP更换服务。
这样一来,家里的千兆宽带也至少有30MB的上行可作为峰值宽带使用。
服务器可以很简单的POST一个消息:curl -H “Title:ip” -d “18.112.8.11:88” https://ntfy.sh/SCCD001
编译并UPX后,程序4.13MB,占用内存11MB,可以说是非常小巧了。绿色跨平台自是不必说了。
package main
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
)
const DEVICECODE = "SCCD001"
var remoteHost string // 要转发到的远程端口
var localPort = "80" // 要监听的本地端口
var ResetServer bool // 重置服务
func main() {
if len(os.Args) == 2 && os.Args[1] == "help" {
fmt.Printf("%s 远端IP:端口 [本地端口]", os.Args[0])
return
}
if len(os.Args) >= 2 {
remoteHost = os.Args[1]
if len(os.Args) == 3 {
localPort = os.Args[2]
}
}
go ListenServerIP()
go func() {
fmt.Print("等待获取服务器地址")
for {
if remoteHost != "" {
PortServer()
ResetServer = false
} else {
fmt.Print(".")
time.Sleep(3 * time.Second)
}
}
}()
select {} // 阻塞主线程
}
// 转发服务
func PortServer() {
listener, err := net.Listen("tcp", ":"+localPort) // 监听本地端口
if err != nil {
fmt.Println("\n监听本地服务出错:", err)
return
}
defer listener.Close()
fmt.Printf("\n服务监听中 %s -> %s\n", localPort, remoteHost)
for {
if !ResetServer { // 重置服务(用于IP变更)
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting:", err)
continue
}
go handleConnection(conn, remoteHost) // 处理连接
} else {
break
}
}
}
func handleConnection(local net.Conn, remoteAddr string) {
defer local.Close()
remote, err := net.Dial("tcp", remoteAddr)
if err != nil {
fmt.Println("Error dialing remote:", err)
return
}
defer remote.Close()
// 转发数据
go copyData(local, remote)
copyData(remote, local)
}
func copyData(source, dest net.Conn) {
defer source.Close()
buf := make([]byte, 1024)
for {
if !ResetServer {
n, err := source.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
} else {
fmt.Println("Error copying data:", err)
break
}
}
dest.Write(buf[:n])
} else {
break
}
}
}
// 监听ntfy.sh获取服务器IP及端口
// TODO: 通过加密增加安全
func ListenServerIP() {
// 获取最后一次更新的IP
resp, err := http.Get(fmt.Sprintf("https://ntfy.sh/ease_%s/json?poll=1&sched=1&title=ip", DEVICECODE))
if err != nil {
fmt.Println("发送http请求出错:", err)
}
defer resp.Body.Close()
var keyVal map[string]interface{}
body, _ := ioutil.ReadAll(resp.Body)
allMessage := strings.Split(string(body), "\n")
if len(allMessage) > 1 {
latMessage := allMessage[len(allMessage)-2]
json.Unmarshal([]byte(latMessage), &keyVal)
remoteHost = keyVal["message"].(string)
}
// 循环监听消息
resp, err = http.Get(fmt.Sprintf("https://ntfy.sh/ease_%s/json", DEVICECODE))
if err != nil {
fmt.Println("监听ntfy错误:", err)
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
var keyVal map[string]interface{}
info := scanner.Text()
json.Unmarshal([]byte(info), &keyVal)
if keyVal["title"] == "ip" {
// 变更IP
if keyVal["message"].(string) != remoteHost {
fmt.Println("IP变更,重置服务")
ResetServer = true
remoteHost = keyVal["message"].(string)
}
}
if keyVal["title"] == "message" {
msgbox(keyVal["message"].(string))
}
}
}
// 简单的消息弹出
func msgbox(message string) {
title := "消息"
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
// Windows下使用msg命令
cmd = exec.Command("msg", "*", title, message)
case "linux":
// Linux下使用zenity(如果安装了的话)
cmd = exec.Command("zenity", "--info", "--text", message, "--title", title)
default:
return
}
cmd.Run()
}
将轮循消息部份取消,改为自已定时循环获取。内存占用3M-6M,有访问时变大一点(10M),偶尔CPU占用较高(<40%)不知何为。观察。