├── .gitattributes ├── docs ├── 1.jpg ├── 2.jpg └── 3.jpg ├── src ├── main │ ├── java │ │ └── mx │ │ │ ├── wsFerryTCP │ │ │ ├── ws │ │ │ │ ├── IWriter.java │ │ │ │ ├── ListenPortToServerIpPortMap.java │ │ │ │ ├── HammalBase.java │ │ │ │ ├── WsClient.java │ │ │ │ └── Message.java │ │ │ ├── tcp │ │ │ │ ├── TcpHandler.java │ │ │ │ └── NettyClientHammal.java │ │ │ └── App.java │ │ │ ├── tcp │ │ │ ├── NettyServer.java │ │ │ ├── NettyClient.java │ │ │ └── NettyBase.java │ │ │ └── utils │ │ │ ├── ByteUtils.java │ │ │ └── Json.java │ └── resources │ │ ├── mx │ │ └── wsFerryTCP │ │ │ └── spring.xml │ │ └── log4j2.xml └── test │ ├── resources │ ├── log4j2.xml │ └── spring.xml │ └── java │ └── mx │ └── wsFerryTCP │ ├── TestWsClient.java │ ├── FerryApp.java │ └── TcpServerInLocalNetwork.java ├── wsFerryTCP ├── test │ ├── testWsServer.go │ ├── testChan.go │ ├── testCommon.go │ ├── testList.go │ ├── testQueue.go │ ├── testListenClose.go │ ├── testMessage.go │ ├── testTcpListen.go │ └── testWS.go └── src │ ├── tcpListen │ ├── main │ │ └── launcher.go │ ├── message.go │ ├── connectionHandler.go │ ├── tcpListen.go │ └── wsServer.go │ └── utils │ ├── queue.go │ ├── byteUtils │ └── byteUtils.go │ └── common.go ├── .gitignore ├── README.md └── pom.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.go linguist-vendored=false 3 | -------------------------------------------------------------------------------- /docs/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonenine/ferry/HEAD/docs/1.jpg -------------------------------------------------------------------------------- /docs/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonenine/ferry/HEAD/docs/2.jpg -------------------------------------------------------------------------------- /docs/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonenine/ferry/HEAD/docs/3.jpg -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/ws/IWriter.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.ws; 2 | 3 | public interface IWriter { 4 | 5 | void write(byte[] bs); 6 | } 7 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testWsServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "../src/tcpListen" 5 | ) 6 | 7 | func main() { 8 | tcpListen.StartWsServer(38080) 9 | } 10 | -------------------------------------------------------------------------------- /wsFerryTCP/src/tcpListen/main/launcher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | 4 | import ( 5 | "../../tcpListen" 6 | ) 7 | 8 | func main() { 9 | tcpListen.StartWsServer(38080) 10 | } 11 | 12 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testChan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | func main() { 6 | var ch = make(chan int,100) 7 | 8 | go func(){ 9 | for i:=0;i<10;i++{ 10 | ch<-i 11 | } 12 | close(ch) 13 | }() 14 | 15 | //关闭之后 for自动退出 16 | for ii := range ch{ 17 | println(ii) 18 | } 19 | 20 | println("退出循环") 21 | 22 | time.Sleep(10*time.Second) 23 | } 24 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testCommon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "../src/utils" 5 | 6 | func main() { 7 | testTry() 8 | 9 | fmt.Printf("%s\n","后面的工作继续进行中...") 10 | } 11 | 12 | 13 | func testTry(){ 14 | err:=utils.Try(func() { 15 | method() 16 | }) 17 | if err!=nil { 18 | fmt.Printf("%s\n",err.Error()) 19 | } 20 | 21 | } 22 | 23 | func method(){ 24 | fmt.Printf("%s\n","before panic") 25 | panic("12345") 26 | fmt.Printf("%s\n","after panic") 27 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | .mvn/ 34 | log/ 35 | logs/ 36 | mvnw 37 | *.cmd -------------------------------------------------------------------------------- /wsFerryTCP/test/testList.go: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | 测试golang的list 4 | 不支持queue接口,无法pop 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "container/list" 11 | "fmt" 12 | "reflect" 13 | ) 14 | 15 | func main() { 16 | li0:=list.New() 17 | 18 | for i:=1;i<100;i++{ 19 | li0.PushBack(i) 20 | } 21 | 22 | for li0.Len()>0{ 23 | e:= li0.Front() 24 | li0.Remove(e) 25 | 26 | 27 | //*list.Element 28 | fmt.Println(reflect.TypeOf(e)); 29 | fmt.Println(reflect.TypeOf(e.Value)); 30 | //interface类型强转 31 | fmt.Printf("%d\n",e.Value.(int)); 32 | } 33 | 34 | 35 | } 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/mx/tcp/NettyServer.java: -------------------------------------------------------------------------------- 1 | package mx.tcp; 2 | 3 | 4 | /** 5 | * 方便的创建一个以及基于netty的服务器程序 6 | */ 7 | public class NettyServer extends NettyBase{ 8 | 9 | public NettyServer() { 10 | super(true); 11 | } 12 | 13 | /** 14 | * 此方法为阻塞方法,直到所有端口都监听成功才返回 15 | * @param bossNum 16 | * @param workerNum 17 | * @param bindPorts 18 | * @param handlerCreater 19 | * @throws Throwable 20 | */ 21 | public void start(int bossNum,int workerNum,int[] bindPorts,final ChannelHandlerCreater handlerCreater) throws Throwable{ 22 | super.initServer(bossNum, workerNum, bindPorts, handlerCreater); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/mx/wsFerryTCP/spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testQueue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "../src/utils" 5 | "time" 6 | "os" 7 | ) 8 | 9 | func test0(){ 10 | q:= utils.NewQueue() 11 | q.Offer(1) 12 | q.Offer(2) 13 | q.Offer(3) 14 | 15 | //这个要自己转换 16 | println(q.Poll().(int)) 17 | println(q.Poll().(int)) 18 | println(q.Poll().(int)) 19 | //nil不能强转,需要提前判断是否为nil 20 | println(q.Poll()==nil) 21 | } 22 | 23 | /** 24 | 验证协程安全 25 | */ 26 | func test1(){ 27 | q:= utils.NewQueue() 28 | go func(){ 29 | var lastV = -1 30 | for{ 31 | v:=q.Poll() 32 | if v!=nil{ 33 | var v int = v.(int) 34 | if(v - lastV != 1){ 35 | os.Exit(1) 36 | }else{ 37 | println(v) 38 | } 39 | lastV = v 40 | }else{ 41 | time.Sleep(1*time.Millisecond) 42 | } 43 | } 44 | }() 45 | 46 | for i:=0;i<10000;i++{ 47 | q.Offer(i) 48 | } 49 | 50 | time.Sleep(10*time.Second) 51 | } 52 | 53 | func main() { 54 | test1() 55 | } -------------------------------------------------------------------------------- /wsFerryTCP/src/utils/queue.go: -------------------------------------------------------------------------------- 1 | /** 2 | 建立一个简单的,协程安全的queue 3 | */ 4 | package utils 5 | 6 | import ( 7 | "container/list" 8 | "errors" 9 | "sync" 10 | ) 11 | 12 | type Queue struct { 13 | list *list.List 14 | lock *sync.Mutex 15 | } 16 | 17 | func NewQueue() *Queue { 18 | return &Queue{ 19 | list:list.New(), 20 | lock:&sync.Mutex{}, 21 | } 22 | } 23 | 24 | var nilError = errors.New("不能向队列中offer空值"); 25 | 26 | 27 | /* 28 | 向队列尾部插入值,值不能为nil 29 | */ 30 | func (q *Queue) Offer(value interface{}) error{ 31 | if value==nil{ 32 | return nilError 33 | } 34 | q.lock.Lock() 35 | defer q.lock.Unlock() 36 | //从后端插入 37 | q.list.PushBack(value) 38 | 39 | return nil 40 | } 41 | 42 | /* 43 | 从队列头部取出值,如果队列为空,就返回nil 44 | */ 45 | func (q *Queue) Poll() interface{}{ 46 | q.lock.Lock() 47 | defer q.lock.Unlock() 48 | ele:= q.list.Front() 49 | if ele!=nil{ 50 | q.list.Remove(ele) 51 | return ele.Value 52 | } 53 | 54 | return nil 55 | } 56 | 57 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testListenClose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "fmt" 6 | "os" 7 | "time" 8 | "../src/utils" 9 | ) 10 | 11 | func main(){ 12 | listener, err := net.Listen("tcp4", ":19999") 13 | var tcpListener = listener.(*net.TCPListener) 14 | if err != nil { 15 | fmt.Printf("listen error:%s", err) 16 | os.Exit(1) 17 | } 18 | 19 | /* 20 | 定时从另一个协程关闭监听 21 | */ 22 | go func(){ 23 | time.Sleep(10*time.Second) 24 | utils.Try(func() { 25 | tcpListener.Close() 26 | }) 27 | }() 28 | 29 | flag0: 30 | for{ 31 | conn, err := tcpListener.AcceptTCP() 32 | if err != nil { 33 | //打个日志,然后忽略 34 | fmt.Printf("accept error:%s\n", err) 35 | break flag0 36 | }else{ 37 | //简单的阻塞读取一帧数据 38 | go func(){ 39 | for{ 40 | var data = make([]byte,10000) 41 | var n, err = conn.Read(data) 42 | if err != nil { 43 | fmt.Printf("accept error:%s", err) 44 | }else{ 45 | var str = string(data[0:n]) 46 | fmt.Printf("取得客户端数据:"+str) 47 | } 48 | } 49 | }() 50 | 51 | } 52 | } 53 | 54 | fmt.Printf("%s\n","退出监听") 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/ws/ListenPortToServerIpPortMap.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.ws; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | /** 8 | * 映射维护表,需要配置在配置文件中 9 | */ 10 | public class ListenPortToServerIpPortMap { 11 | 12 | Map map; 13 | 14 | public void setMap(Map map) { 15 | this.map = map; 16 | } 17 | 18 | /** 19 | * tcp listen端的监听端口,同"本地网络内"的服务器ip和端口的映射关系 20 | */ 21 | public static ListenPortToServerIpPortMap defaultMap; 22 | 23 | public ListenPortToServerIpPortMap(){ 24 | synchronized (ListenPortToServerIpPortMap.class){ 25 | if(defaultMap == null){ 26 | defaultMap = this; 27 | }else{ 28 | throw new RuntimeException("ListenPortToServerIpPortMap不能多次创建"); 29 | } 30 | } 31 | } 32 | 33 | public static String[] getIpPort(int listenPort){ 34 | String ipPort = defaultMap.map.get(listenPort); 35 | if(ipPort!=null){ 36 | return ipPort.split("\\:"); 37 | } 38 | 39 | return null; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testMessage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "../src/tcpListen" 5 | "../src/utils/byteUtils" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | //测试marshal 11 | lm := tcpListen.LoginMessage{27001,"cjadmin", "2Bqnynt!"}; 12 | var bs = lm.Marshal(); 13 | 14 | fmt.Printf("断连接消息") 15 | ci := tcpListen.ConnectInactiveMessage{9876543}; 16 | bs = ci.Marshal(); 17 | fmt.Printf("----%s\n", byteUtils.SliceToHex(bs)); 18 | 19 | fmt.Printf("登录响应消息") 20 | lr := tcpListen.LoginResponseMessage{ "已经有另外的账户登录了"}; 21 | bs = lr.Marshal(); 22 | fmt.Printf("----%s\n", byteUtils.SliceToHex(bs)); 23 | 24 | fmt.Printf("数据传输消息") 25 | dm:=tcpListen.DataMessage{12345,[]byte{0x11,0x22,0x33,0xff,0x80,0x7f,0x99}}; 26 | bs = dm.Marshal(); 27 | fmt.Printf("----%s\n", byteUtils.SliceToHex(bs)); 28 | 29 | //验证java端的结果 30 | testUnmarshal("00 00 00 4A 92 61 64 6D 69 6E 7C 7C 4F 6E 65 70 6C 75 73 31 3D 74 77 6F ") 31 | } 32 | 33 | func testUnmarshal(hex string){ 34 | var bs1,err =byteUtils.HexToSlice(hex); 35 | if err == nil{ 36 | var message,err = tcpListen.Unmarshal(bs1); 37 | if err == nil{ 38 | fmt.Printf("反序列化结果%+v\n",message) 39 | }else{ 40 | println(err) 41 | } 42 | }else{ 43 | println(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | %d{MM-dd HH:mm:ss.SSS} %16c{1} [%16M:%3L] %p %m%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | %d{MM-dd HH:mm:ss.SSS} %16c{1} [%16M:%3L] %p %m%n 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | %d{MM-dd HH:mm:ss.SSS} %16c{1} [%16M:%3L] %p %m%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | %d{MM-dd HH:mm:ss.SSS} %16c{1} [%16M:%3L] %p %m%n 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /wsFerryTCP/src/utils/byteUtils/byteUtils.go: -------------------------------------------------------------------------------- 1 | package byteUtils 2 | 3 | import ( 4 | "strings" 5 | "encoding/hex" 6 | "bufio" 7 | ) 8 | 9 | /** 10 | * 中间带空格的 11 | * 68 56 00 56 00 68 4B 00 12 51 BC 02 0D E9 40 04 01 14 22 11 17 19 16 02 04 23 00 5D 16 12 | */ 13 | func HexToSlice(hexStr string) ([]byte, error) { 14 | hexStr = strings.TrimSpace(hexStr); 15 | var slice = make([]byte, 0, 20) 16 | for _, byteHex := range strings.Split(hexStr, " ") { 17 | slice0, err := hex.DecodeString(byteHex); 18 | if err != nil { 19 | return nil, err 20 | } 21 | slice = append(slice, slice0[0]); 22 | } 23 | 24 | return slice, nil 25 | } 26 | 27 | /* 28 | 数组转成中间带空格的hex传值 29 | */ 30 | func SliceToHex(slice []byte) string { 31 | sb := make([]string, 0, 20) 32 | for _, by := range slice { 33 | str := hex.EncodeToString([]byte{by}) 34 | sb = append(sb, strings.ToUpper(str)); 35 | } 36 | return strings.Join(sb, " ") 37 | } 38 | 39 | 40 | /** 41 | * 直到从reader中读取n个字节才返回,否则tcp一直阻塞 42 | * 这个方法如果n大于reader中缓存的大小(即一次能返回的最大大小),会在方法中不停循环 43 | */ 44 | func ReadN(n int, reader *bufio.Reader) ([]byte, error) { 45 | for { 46 | buf, err := reader.Peek(n) 47 | 48 | if err == nil { 49 | reader.Discard(n) 50 | return buf, nil 51 | } 52 | 53 | if err != bufio.ErrBufferFull { 54 | return nil, err; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/resources/spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testTcpListen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "../src/tcpListen" 5 | "fmt" 6 | "time" 7 | "reflect" 8 | ) 9 | 10 | var tl *tcpListen.TcpListen 11 | 12 | func showHandlerList(){ 13 | var list = tl.GetHandlerList() 14 | 15 | if list!=nil { 16 | if e:=list.Front();e!=nil { 17 | forFlag: 18 | for{ 19 | fmt.Printf("#######%+v\n",e.Value) 20 | if e = e.Next();e == nil{ 21 | break forFlag; 22 | } 23 | } 24 | } 25 | } 26 | fmt.Printf("以上为handlerList内容") 27 | } 28 | 29 | func consume(msg tcpListen.Message){ 30 | fmt.Printf("%s,%+v\n",reflect.TypeOf(msg),msg) 31 | switch msg.(type){ 32 | case *tcpListen.ConnectInactiveMessage: 33 | println("连接断了") 34 | showHandlerList() 35 | 36 | case *tcpListen.DataMessage: 37 | var dm = msg.(*tcpListen.DataMessage) 38 | var l = len(dm.Data) 39 | var cmd = string(dm.Data[0:l-1]) 40 | if cmd == "close"{ 41 | //关闭连接 42 | tl.CloseConn(dm.ConnectId) 43 | }else if cmd == "destroy"{ 44 | //关闭监听 45 | tl.BreakAcceptAndCloseConnections() 46 | }else{ 47 | //形成echo 48 | tl.Write(dm.ConnectId,dm.Data) 49 | } 50 | 51 | default: 52 | showHandlerList() 53 | } 54 | } 55 | 56 | func main() { 57 | _tl,err:= tcpListen.GetOrCreateListen(38888,consume) 58 | if err!=nil { 59 | println(err) 60 | }else{ 61 | tl = _tl 62 | fmt.Printf("程序已经启动\n") 63 | time.Sleep(9999999*time.Second) 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/java/mx/wsFerryTCP/TestWsClient.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP; 2 | 3 | import mx.utils.ByteUtils; 4 | import mx.wsFerryTCP.ws.HammalBase; 5 | import mx.wsFerryTCP.ws.WsClient; 6 | 7 | import java.util.function.BiFunction; 8 | 9 | public class TestWsClient { 10 | 11 | static class Hammal extends HammalBase { 12 | 13 | @Override 14 | public void onListenConnActive(int connectId) { 15 | System.err.println(" tcp listen端的连接:"+connectId+"上线了"); 16 | try { 17 | sendDataToListen(connectId,"欢迎,欢迎,热烈欢迎".getBytes("GBK")); 18 | } catch (Exception e) { 19 | e.printStackTrace(); 20 | } 21 | } 22 | 23 | @Override 24 | public void onListenConnInactive(int connectId) { 25 | System.err.println("###tcp listen端的连接:"+connectId+"掉线了线了"); 26 | } 27 | 28 | @Override 29 | public void onReceiveDataFromListenConn(int connectId, byte[] bs) { 30 | System.err.println("从tcp listen端的连接:"+connectId+"接收到数据:"+ ByteUtils.toHexString(bs)); 31 | //把原数据返回,形成echo 32 | sendDataToListen(connectId,bs); 33 | } 34 | 35 | @Override 36 | public void doOnWsClose() { 37 | System.err.println("从webSocket端断掉连接"); 38 | } 39 | } 40 | 41 | 42 | public static void main(String[] args) { 43 | try { 44 | WsClient wsClient = new WsClient("127.0.0.1:38080",38888,new Hammal()); 45 | wsClient.connect(); 46 | // 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 公网反向摆渡(代理)到内网 2 | 3 | golang java proxy ferry 4 | 5 | --- 6 | 7 |       业务系统一般部署在内网,内网和公网之间一般是物理隔离或是防火墙隔离的,但有的时候也需要将内网网段的服务开放到公网上,用来进行调试、确认等临时工作。
8 |       公网访问内网,一般要具备两个条件(1)到运营商申请固定IP (2)通过路由器,代理服务器等将内网端口映射到固定IP上。 这种方式成本较高,不适合小公司,小团队操作。
9 |       有的时候公网资源有限。比如:还是因为成本限制,不可能在阿里云上租用大量的服务器;或者因为licence限制,也不可能将很多非开源中间件(比如oracle)部署在阿里云上。那么就需要出现一个工具,可以方便的将内网的资源开放到公网。(注意,这样做同样不要违反中间件版权涉及的法律约束)
10 |       工具的实质就是将客户端请求从公网代理(反向摆渡)给内网的服务中,再将内网服务的响应返回到外网的客户端。
11 | 12 |       …想到便要做到…
13 | 14 |       于是这样一款反向摆渡工具就诞生了。
15 |       (1) 内网摆渡程序需要登录到云端代理程序,保证安全性
16 |       (2) 云端代理程序按照内网请求动态监听端口
17 |       (3) 代理程序就一个可执行文件,拷贝到云端执行即可。内网摆渡是一个spring boot程序,配置极简。一共就两个进程,不依赖数据库及其他组件
18 |       (4) 一旦内网程序关闭web socket客户端,云端代理便会关闭socket监听。收放自如
19 |       (5) 云端程序采用golang编写,性能消耗极小(测试时性能消耗在1%左右),占内存也只占用几十M。租一个阿里的低配虚拟机可以同时将很多内网服务开放到公网。
20 |       (6) 这是个开源程序(而且是用java和go这两种低成本的语言编写),你可以改成任何你需要的样子,这才是最大的优点。
21 |       如图所示:
22 | ![此处输入图片的描述][1] 23 | 类图: 24 | ![此处输入图片的描述][2] 25 | 配置使用说明: 26 | ![此处输入图片的描述][3] 27 |       摆渡程序分两部分组成,在内网的部分做web socket客户端和tcp客户端,使用java编写。公网云端的程序做web socket服务端和tcp服务端,使用go编写 。
28 | 29 | 注:readme中图片不能显示的请翻墙查看 30 | 31 | [1]: https://github.com/jonenine/ferry/blob/master/docs/1.jpg 32 | [2]: https://github.com/jonenine/ferry/blob/master/docs/2.jpg 33 | [3]: https://github.com/jonenine/ferry/blob/master/docs/3.jpg 34 | -------------------------------------------------------------------------------- /src/test/java/mx/wsFerryTCP/FerryApp.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP; 2 | 3 | import mx.utils.Json; 4 | import mx.wsFerryTCP.ws.ListenPortToServerIpPortMap; 5 | import mx.wsFerryTCP.ws.WsClient; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.ImportResource; 13 | 14 | import java.util.Map; 15 | 16 | 17 | @Configuration 18 | @ImportResource(locations = { "classpath:/spring.xml" }) 19 | public class FerryApp extends App { 20 | 21 | public static void main(String[] args) { 22 | startUp(args); 23 | } 24 | 25 | /** 26 | * 不写这个,springContext中竟然不初始化,太过分了 27 | */ 28 | @Autowired 29 | ListenPortToServerIpPortMap map; 30 | 31 | 32 | @Override 33 | public void afterPropertiesSet() throws Exception { 34 | //这个不能Autowired,就这么几行就发现spring两问题 35 | Map projectConfig = (Map) applicationContext.getBean("projectConfig"); 36 | String wsServerAddress = projectConfig.get("wsServerAddress"); 37 | //这一句是留给业务编程的,可同时连接到多个wsServerAddress去,每个wsServer可以启动多个tcpListen监听端口 38 | //WsClient.establishFerryTransport(wsServerAddress,2404); 39 | //WsClient.establishFerryTransport(wsServerAddress,18888); 40 | 41 | WsClient.establishFerryTransport(wsServerAddress,9999); 42 | 43 | //没有上面那个Autowired,下面这个会报错 44 | //String[] localServerIpPort = ListenPortToServerIpPortMap.getIpPort(38888); 45 | //System.err.println(new Json(localServerIpPort)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/mx/tcp/NettyClient.java: -------------------------------------------------------------------------------- 1 | package mx.tcp; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.socket.SocketChannel; 9 | 10 | public class NettyClient extends NettyBase{ 11 | 12 | public NettyClient() { 13 | super(false); 14 | } 15 | 16 | public NettyClient init(int workerNum){ 17 | this.initClient(workerNum); 18 | return this; 19 | } 20 | 21 | public Bootstrap getBootstrap(){ 22 | return this.clientBootstrap; 23 | } 24 | 25 | private ChannelFuture _connect(String host,int port,final ChannelHandlerCreater handlerCreater){ 26 | ChannelFuture channelFuture = clientBootstrap.connect(host,port); 27 | channelFuture.addListener(new ChannelFutureListener() { 28 | public void operationComplete(ChannelFuture future) throws Exception { 29 | if(future.isSuccess()){ 30 | ChannelHandler[] handlers = handlerCreater.create((SocketChannel) future.channel()); 31 | future.channel().pipeline().addLast(handlers); 32 | } 33 | } 34 | }); 35 | 36 | return channelFuture; 37 | } 38 | 39 | /** 40 | * 同步的连接服务器端,返回的Channel对象可用于直接发送第一次对服务端的请求 41 | */ 42 | public Channel connect(String host,int port,ChannelHandlerCreater handlerCreater) throws Throwable{ 43 | ChannelFuture cf = _connect(host,port,handlerCreater).sync(); 44 | Throwable exp; 45 | if(!cf.isSuccess() && (exp = cf.cause())!=null){ 46 | throw exp; 47 | } 48 | return cf.channel(); 49 | } 50 | 51 | /** 52 | * 异步连接,请使用如下代码来处理连接不上的情况 53 | * channelFuture.channel().closeFuture().addListener(new ChannelFutureListener(){ 54 | public void operationComplete(ChannelFuture future) throws Exception { 55 | 56 | } 57 | }); 58 | */ 59 | public ChannelFuture connectAsync(String host,int port,final ChannelHandlerCreater handlerCreater){ 60 | return _connect(host,port,handlerCreater); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/ws/HammalBase.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.ws; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import java.util.Collections; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | /** 12 | * 通过一个类来解耦 13 | * listenPort对serverIP和Port 14 | * 每一个connectId对应一个channelHandler,而且要注入这个channelHandler 15 | */ 16 | public abstract class HammalBase { 17 | 18 | protected static final Logger logger = LogManager.getLogger(HammalBase.class); 19 | 20 | protected volatile IWriter writer; 21 | 22 | public void setWriter(IWriter writer) { 23 | this.writer = writer; 24 | } 25 | 26 | /** 27 | * 向tcp listen的ws服务端send数据 28 | */ 29 | public final void sendDataToListen(int connectId, byte[] bs){ 30 | Message.DataMessage dm = new Message.DataMessage(connectId,bs); 31 | writer.write(dm.marshal()); 32 | } 33 | 34 | /** 35 | * 向tcp listen的ws服务端send连接断掉消息 36 | */ 37 | public final void sendInactive(int connectId){ 38 | Message.ConnectInactiveMessage sim = new Message.ConnectInactiveMessage(connectId); 39 | writer.write(sim.marshal()); 40 | } 41 | 42 | 43 | /** 44 | * connectId标识的一个连接在tcpListen上线 45 | * 在ferry target端也要根据配置连接ip和端口,如果不能成功创建连接,需要向tcpListen端发送connectInactive事件 46 | */ 47 | public abstract void onListenConnActive(int connectId); 48 | /** 49 | * connectId标识的连接在tcpListen端断线 50 | * 在ferry target端也要断掉connectId标识的id 51 | */ 52 | public abstract void onListenConnInactive(int connectId); 53 | 54 | /** 55 | * connectId标识的连接接收数据 56 | */ 57 | public abstract void onReceiveDataFromListenConn(int connectId, byte[] bs); 58 | 59 | 60 | protected final AtomicBoolean destroy = new AtomicBoolean(false); 61 | /** 62 | * 生命周期结束,ws关闭 63 | * 需要关闭所有的 64 | */ 65 | public void onWsClose(){ 66 | if(destroy.compareAndSet(false,true)){ 67 | writer = null; 68 | doOnWsClose(); 69 | } 70 | } 71 | 72 | public abstract void doOnWsClose(); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/tcp/TcpHandler.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.tcp; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | public class TcpHandler extends ChannelInboundHandlerAdapter { 10 | 11 | final int connectId; 12 | final NettyClientHammal hammal; 13 | 14 | public TcpHandler(int connectId,NettyClientHammal hammal) { 15 | this.connectId = connectId; 16 | this.hammal = hammal; 17 | } 18 | 19 | volatile Channel channel; 20 | 21 | public void setChannel(Channel channel) { 22 | this.channel = channel; 23 | } 24 | 25 | volatile ChannelHandlerContext context; 26 | 27 | /** 28 | * 同服务端的连接建立 29 | */ 30 | @Override 31 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 32 | this.context = ctx; 33 | } 34 | 35 | /** 36 | * 当服务端发送过来数据 37 | */ 38 | @Override 39 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 40 | ByteBuf data = (ByteBuf) msg; 41 | byte[] bs = new byte[data.readableBytes()]; 42 | data.readBytes(bs); 43 | data.release(); 44 | hammal.sendDataToListen(connectId,bs); 45 | } 46 | 47 | /** 48 | * 同服务端的连接断掉 49 | */ 50 | @Override 51 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 52 | hammal.sendInactive(connectId); 53 | } 54 | 55 | /*----------------------------------------------------------*/ 56 | 57 | /** 58 | * 关闭tcp连接 59 | */ 60 | public void close(){ 61 | try { 62 | context.close(); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | 68 | /** 69 | * 向tcp写入数据 70 | */ 71 | public void write(byte[] bs){ 72 | ByteBuf buf = Unpooled.wrappedBuffer(bs); 73 | if(context!=null){ 74 | context.writeAndFlush(buf); 75 | }else{ 76 | channel.writeAndFlush(buf); 77 | 78 | } 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /wsFerryTCP/test/testWS.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 标准库的websocket 3 | */ 4 | 5 | package main 6 | 7 | 8 | import ( 9 | "fmt" 10 | "net/http" 11 | "os" 12 | "strings" 13 | 14 | "golang.org/x/net/websocket" 15 | ) 16 | 17 | /* 18 | * 每个连接是一个协程,连接工作完成前,协程是不能结束的 19 | * 文本方式处理webSocket 20 | */ 21 | func handlerWSText(ws *websocket.Conn) { 22 | var err error 23 | flag0: 24 | for { 25 | var reply string 26 | 27 | println("1-----"+ws.Request().RemoteAddr) 28 | 29 | if err = websocket.Message.Receive(ws, &reply); err != nil { 30 | fmt.Printf("接收字符串错误:%s\n", err) 31 | ws.Close(); 32 | break flag0 33 | } 34 | 35 | fmt.Printf("ws服务端接收到:%s\n", reply) 36 | 37 | /** 38 | 使用其他协程send,没有发现连接断掉的情况 39 | */ 40 | go func(){ 41 | if err = websocket.Message.Send(ws, strings.ToUpper(reply)); err != nil { 42 | fmt.Printf("发送字符串错误:%s\n", err) 43 | } 44 | fmt.Printf("ws服务端接发送2:%s\n", reply) 45 | }() 46 | 47 | //if err = websocket.Message.Send(ws, strings.ToUpper(reply)); err != nil { 48 | // fmt.Printf("发送字符串错误:%s\n", err) 49 | // ws.Close(); 50 | // break flag0 51 | //} 52 | //fmt.Printf("ws服务端接发送:%s\n", reply) 53 | } 54 | } 55 | 56 | func handlerWSBinary(ws *websocket.Conn) { 57 | var err error 58 | flag0: 59 | for { 60 | var reply []byte 61 | 62 | if err = websocket.Message.Receive(ws, &reply); err != nil { 63 | fmt.Printf("接收二进制错误:%s\n", err) 64 | ws.Close(); 65 | break flag0 66 | } 67 | 68 | fmt.Printf("ws服务端接收到:%v+\n", reply) 69 | 70 | 71 | if err = websocket.Message.Send(ws, reply); err != nil { 72 | fmt.Printf("发送二进制错误:%s\n", err) 73 | ws.Close(); 74 | break flag0 75 | } 76 | fmt.Printf("ws服务端接发送:%v+\n", reply) 77 | } 78 | } 79 | 80 | func initHandler(w http.ResponseWriter, r *http.Request) { 81 | s := websocket.Server{ 82 | Handler: websocket.Handler(handlerWSText), 83 | 84 | //解决跨域的问题,要不还得使用第三方库 85 | Handshake: func(config *websocket.Config, request *http.Request) error { 86 | return nil 87 | }, 88 | } 89 | 90 | s.ServeHTTP(w, r); 91 | } 92 | 93 | func main() { 94 | http.HandleFunc("/upper", initHandler) 95 | 96 | //下面这个方法是阻塞的 97 | if err := http.ListenAndServe(":38888", nil); err != nil { 98 | fmt.Println(err) 99 | os.Exit(1) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /wsFerryTCP/src/utils/common.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pkg/errors" 6 | "runtime/debug" 7 | "sync" 8 | ) 9 | 10 | /** 11 | 打印错误及线程栈 12 | */ 13 | func PrintErrorStackWithPrefix(prefix string,err error){ 14 | fmt.Printf(prefix+":%s\n", err.Error()) 15 | debug.PrintStack() 16 | } 17 | 18 | /** 19 | 打印错误及线程栈 20 | */ 21 | func PrintErrorStack(err error){ 22 | PrintErrorStackWithPrefix("出现了错误:",err) 23 | } 24 | 25 | /** 26 | 在某个"锁"上,同步的做一件事情 27 | */ 28 | func Sync(lock *sync.Mutex ,do func()){ 29 | lock.Lock() 30 | defer lock.Unlock() 31 | do() 32 | } 33 | 34 | func SyncRead(lock *sync.RWMutex ,do func()){ 35 | lock.RLock() 36 | defer lock.RUnlock() 37 | do() 38 | } 39 | 40 | func SyncWrite(lock *sync.RWMutex ,do func()){ 41 | lock.Lock() 42 | defer lock.Unlock() 43 | do() 44 | } 45 | 46 | func tryWithError(pErr *interface{},do func() error) error{ 47 | defer func(){ 48 | err := recover() 49 | if err!=nil { 50 | *pErr = err 51 | } 52 | }() 53 | return do() 54 | } 55 | 56 | /** 57 | 捕获panic,并以error的形式返回 58 | */ 59 | func Try(do func() error) error{ 60 | var panicErr interface{} 61 | var err = tryWithError(&panicErr,do) 62 | 63 | if panicErr !=nil{ 64 | //应该做的更细致些,把Try方法之前的错误栈信息去掉 65 | debug.PrintStack() 66 | switch panicErr.(type){ 67 | case error: 68 | println(panicErr.(error).Error()) 69 | return panicErr.(error) 70 | case string: 71 | println(panicErr.(string)) 72 | return errors.New(panicErr.(string)); 73 | default: 74 | println("出现了panic") 75 | return errors.New("出现了panic"); 76 | } 77 | }else if err!=nil{ 78 | debug.PrintStack() 79 | println(err.Error()) 80 | return err 81 | } 82 | 83 | return nil 84 | } 85 | 86 | /** 87 | 在锁参与的情况下,进行同步的情况 88 | */ 89 | func TrySync(lock *sync.Mutex ,do func() error) error{ 90 | lock.Lock() 91 | err:=Try(do) 92 | lock.Unlock() 93 | return err 94 | } 95 | 96 | /** 97 | 相对于读写锁的读的情况 98 | */ 99 | func TrySyncRead(lock *sync.RWMutex ,do func() error) error{ 100 | lock.RLock() 101 | err:=Try(do) 102 | lock.RUnlock() 103 | return err 104 | } 105 | 106 | /** 107 | 相对于读写锁的读的情况 108 | */ 109 | func TrySyncWrite(lock *sync.RWMutex ,do func() error) error{ 110 | lock.Lock() 111 | err:=Try(do) 112 | lock.Unlock() 113 | return err 114 | } 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/test/java/mx/wsFerryTCP/TcpServerInLocalNetwork.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.channel.socket.SocketChannel; 9 | import mx.tcp.NettyBase; 10 | import mx.tcp.NettyServer; 11 | import mx.utils.ByteUtils; 12 | 13 | /** 14 | * 模拟本地网络的tcp服务端 15 | */ 16 | public class TcpServerInLocalNetwork { 17 | 18 | public static void main(String[] args) { 19 | try { 20 | NettyServer echoServer = new NettyServer(); 21 | echoServer.start(1, 1, new int[]{28888}, new NettyBase.ChannelHandlerCreater() { 22 | @Override 23 | public ChannelHandler[] create(SocketChannel ch) { 24 | return new ChannelHandler[]{ 25 | new ChannelInboundHandlerAdapter() { 26 | @Override 27 | public void channelActive(ChannelHandlerContext ctx)throws Exception { 28 | super.channelActive(ctx); 29 | System.err.println("和本地网络28888端口服务建立连接"); 30 | //响应 31 | ByteBuf bbf = Unpooled.buffer(); 32 | bbf.writeBytes("来自内网服务器28888的问候".getBytes("GBK")); 33 | ctx.writeAndFlush(bbf); 34 | } 35 | 36 | @Override 37 | public void channelInactive(ChannelHandlerContext ctx)throws Exception { 38 | System.err.println("和本地网络28888端口连接断了"); 39 | } 40 | 41 | @Override 42 | public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception { 43 | ByteBuf data = (ByteBuf) msg; 44 | byte[] bs = ByteUtils.getBytes(data); 45 | if(new String(bs).startsWith("close")){ 46 | System.err.println("正在关闭连接"); 47 | ctx.close(); 48 | }else{ 49 | System.err.println("本地网络28888端口接到\"摆渡\"过来额数据"+ ByteUtils.toHexString(data)); 50 | ByteBuf bbf = Unpooled.buffer(); 51 | bbf.writeBytes(data); 52 | //形成echo 53 | ctx.writeAndFlush(bbf); 54 | } 55 | } 56 | } 57 | }; 58 | } 59 | }); 60 | } catch (Throwable throwable) { 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/tcp/NettyClientHammal.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.tcp; 2 | 3 | 4 | import com.google.common.base.Throwables; 5 | import io.netty.channel.*; 6 | import mx.tcp.NettyBase; 7 | import mx.tcp.NettyClient; 8 | import mx.wsFerryTCP.ws.HammalBase; 9 | import mx.wsFerryTCP.ws.IWriter; 10 | import mx.wsFerryTCP.ws.ListenPortToServerIpPortMap; 11 | import mx.wsFerryTCP.ws.WsClient; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import java.util.Iterator; 16 | import java.util.Map; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | 19 | /** 20 | * 和ws客户端WsClient对象是一对一的关系 21 | * 和tcp listen是一对一关系 22 | * 和"本地网络"中服务端ip和port是一对一关系 23 | * 维护对服务端ip和port的所有连接 24 | */ 25 | public class NettyClientHammal extends HammalBase { 26 | 27 | protected static final Logger logger = LogManager.getLogger(NettyClientHammal.class); 28 | 29 | private volatile NettyClient nc; 30 | 31 | @Override 32 | public void setWriter(IWriter writer) { 33 | super.setWriter(writer); 34 | nc = new NettyClient(); 35 | //仅仅是转发数据,1个线程足以,5秒钟连接不上closeFuture的监听器就会接收到错误 36 | nc.init(1).getBootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); 37 | } 38 | 39 | final Map handlerMap = new ConcurrentHashMap<>(); 40 | 41 | @Override 42 | public void onListenConnActive(int connectId) { 43 | // 44 | try { 45 | TcpHandler handler = new TcpHandler(connectId,this); 46 | //查找listenPort对应的"本地网络"中的服务端ip和端口 47 | String[] ipPort = ListenPortToServerIpPortMap.getIpPort(((WsClient)writer).listenPort); 48 | //向tcp服务端发起请求 49 | Channel channel = nc.connect(ipPort[0], Integer.parseInt(ipPort[1]), new NettyBase.ChannelHandlerCreater() { 50 | @Override 51 | public ChannelHandler[] create(io.netty.channel.socket.SocketChannel ch) { 52 | return new ChannelHandler[]{handler}; 53 | } 54 | }); 55 | handler.setChannel(channel); 56 | 57 | //一旦连接失败,要从handlerMap去掉 58 | channel.closeFuture().addListener(new ChannelFutureListener() { 59 | @Override 60 | public void operationComplete(ChannelFuture future) throws Exception { 61 | handlerMap.remove(connectId); 62 | } 63 | }); 64 | 65 | //将connectId和handler建立对应关系 66 | handlerMap.put(connectId,handler); 67 | } catch (Throwable e) { 68 | logger.error(Throwables.getStackTraceAsString(e)); 69 | 70 | //向服务端发起连接失败,发断连接消息 71 | sendInactive(connectId); 72 | } 73 | } 74 | 75 | @Override 76 | public void onListenConnInactive(int connectId) { 77 | TcpHandler handler = handlerMap.get(connectId); 78 | if(handler != null){ 79 | handler.close(); 80 | } 81 | } 82 | 83 | @Override 84 | public void onReceiveDataFromListenConn(int connectId, byte[] bs) { 85 | TcpHandler handler = handlerMap.get(connectId); 86 | if(handler!=null){ 87 | handler.write(bs); 88 | } 89 | } 90 | 91 | 92 | @Override 93 | public void doOnWsClose() { 94 | Iterator it = handlerMap.values().iterator(); 95 | while(it.hasNext()){ 96 | it.next().close(); 97 | } 98 | handlerMap.clear(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /wsFerryTCP/src/tcpListen/message.go: -------------------------------------------------------------------------------- 1 | package tcpListen 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | 12 | type Message interface { 13 | Marshal() []byte 14 | } 15 | 16 | /* 17 | * 由ferry发起的登录消息 18 | */ 19 | type LoginMessage struct { 20 | ListenPort int 21 | UserName string 22 | Password string 23 | } 24 | 25 | func (message *LoginMessage) Marshal() []byte { 26 | //没有这种需要 27 | return nil; 28 | } 29 | 30 | /** 31 | * tcp listen accept连接的消息 32 | */ 33 | type ConnectActiveMessage struct { 34 | //连接id 35 | ConnectId uint32 36 | } 37 | 38 | func (message *ConnectActiveMessage) Marshal() []byte { 39 | buf := bytes.NewBuffer([]byte{}) 40 | //以大端序写入buf 41 | binary.Write(buf, binary.BigEndian, &message.ConnectId) 42 | 43 | return append([]byte{connectActiveMessageType}, buf.Bytes()...) 44 | } 45 | 46 | /* 47 | * 两边传递给对方的连接断掉消息 48 | */ 49 | type ConnectInactiveMessage struct { 50 | //连接id 51 | ConnectId uint32 52 | } 53 | 54 | func (message *ConnectInactiveMessage) Marshal() []byte { 55 | buf := bytes.NewBuffer([]byte{}) 56 | //以大端序写入buf 57 | binary.Write(buf, binary.BigEndian, &message.ConnectId) 58 | 59 | return append([]byte{connectInactiveMessageType}, buf.Bytes()...) 60 | } 61 | 62 | /* 63 | * 登录响应消息 64 | */ 65 | type LoginResponseMessage struct { 66 | //无异常表示登录成功,否则会返回失败原因 67 | Error string 68 | } 69 | 70 | func (message *LoginResponseMessage) Marshal() []byte { 71 | bs := []byte{loginResponseMessageType} 72 | 73 | if message.Error!=""{ 74 | //将error转成byte数组,再写入 75 | var buf = bytes.NewBuffer([]byte{}) 76 | //字符串直接转byte数组,就是utf-8编码的 77 | strBs := []byte(message.Error) 78 | binary.Write(buf, binary.BigEndian, &strBs) 79 | bs = append(bs, buf.Bytes()...) 80 | } 81 | 82 | 83 | return bs 84 | } 85 | 86 | /* 87 | * 数据消息 88 | */ 89 | type DataMessage struct { 90 | //连接id 91 | ConnectId uint32 92 | Data []byte 93 | } 94 | 95 | func (message *DataMessage) Marshal() []byte { 96 | bs := []byte{dataMessageType} 97 | 98 | //以大端序写入ConnectId 99 | var buf = bytes.NewBuffer([]byte{}) 100 | binary.Write(buf, binary.BigEndian, &message.ConnectId) 101 | bs = append(bs, buf.Bytes()...) 102 | 103 | //写入data 104 | bs = append(bs,message.Data...) 105 | 106 | return bs 107 | } 108 | 109 | const ( 110 | loginMessageType = 0 111 | connectActiveMessageType = 31 112 | connectInactiveMessageType = 61 113 | loginResponseMessageType = 01 114 | dataMessageType = 30 115 | ) 116 | 117 | /** 118 | 返回的都是message的子类型 119 | */ 120 | func Unmarshal(bs []byte) (interface{}, error) { 121 | typeFlag := int(bs[0]) 122 | switch typeFlag { 123 | case loginMessageType: 124 | //读监听端口 125 | buf := bytes.NewBuffer(bs[1:5]) 126 | //binary.Read方法源码中是忽略int类型的 127 | var listenPort uint32 128 | binary.Read(buf, binary.BigEndian, &listenPort) 129 | //读应户名密码 130 | str := string(bs[5:]) 131 | uap := strings.Split(str, "||") 132 | return &LoginMessage{ListenPort:int(listenPort),UserName: uap[0], Password: uap[1]}, nil 133 | 134 | case connectInactiveMessageType: 135 | buf := bytes.NewBuffer(bs[1:]) 136 | var connectId uint32 137 | binary.Read(buf, binary.BigEndian, &connectId) 138 | return &ConnectInactiveMessage{connectId}, nil 139 | 140 | case loginResponseMessageType: 141 | //没有这种需求 142 | return nil, nil 143 | case dataMessageType: 144 | var buf = bytes.NewBuffer(bs[1:]) 145 | var connectId uint32 146 | binary.Read(buf, binary.BigEndian, &connectId) 147 | 148 | return &DataMessage{connectId, bs[5:]}, nil 149 | 150 | default: 151 | return nil, errors.New("不支持的类型" + strconv.Itoa(typeFlag)); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | mx.wsFerryTCP 8 | ferry 9 | 1.0-SNAPSHOT 10 | 11 | ferry 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 1.5.18.RELEASE 17 | 18 | 19 | 20 | UTF-8 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-logging 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-log4j2 40 | 41 | 42 | 43 | com.squareup.okhttp3 44 | okhttp 45 | 3.14.2 46 | 47 | 48 | 49 | io.netty 50 | netty-all 51 | [4.1.42,) 52 | 53 | 54 | 55 | commons-lang 56 | commons-lang 57 | 2.5 58 | 59 | 60 | 61 | junit 62 | junit 63 | 4.11 64 | test 65 | 66 | 67 | 68 | net.sf.json-lib 69 | json-lib 70 | 2.3 71 | jdk15 72 | 73 | 74 | com.google.guava 75 | guava 76 | 27.0.1-jre 77 | 78 | 79 | 80 | 81 | 82 | 83 | ferry-${project.version} 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.7.0 90 | 91 | 1.8 92 | 1.8 93 | 1.8 94 | 95 | 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-maven-plugin 100 | 101 | ferry 102 | mx.wsFerryTCP.App 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /wsFerryTCP/src/tcpListen/connectionHandler.go: -------------------------------------------------------------------------------- 1 | package tcpListen 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "strconv" 9 | "sync/atomic" 10 | 11 | "runtime/debug" 12 | ) 13 | 14 | /* 15 | 1.非什么在上行处理时使用worker线程 16 | 如果不使用worker线程处理upstream消息,在压力大的时候,也就是所有连接 17 | 都有数据上来的时候会造成过多goroutine同时是活动的,会卡死进程,触发golang内部线程过多的bug 18 | upstream goroutine在接收到数据时自己不处理,交给worker处理,实际是一种异步的行为. 19 | 因为upstream goroutine是阻塞的,所以其活动的时间很短,将耗时的计算工作交给worker完成 20 | worker数量是有限的,当等于系统cpu核心数时,同样会压满cpu,不存在性能问题 21 | 而且,这种方式压力越大,越多的连接里有数据上来,就有越多的connectionHandler在worker中进行处理 22 | 在worker中就意味着不在upstream goroutine中,可以压制upstream goroutine数量过多的问题 23 | 24 | 下面这个程序没有这样做 25 | 26 | 2.upstream处理和write和close处理分开 27 | 通常upstream是在一个循环中,都是内部调用 28 | 而write和close通常都是从外部进行调用 29 | 这两个通常都不在同一个线程或协程,但彼此访问的是不同的状态,应该互不干扰,transport层并不存在同步的问题(有同步问题也是处理业务的状态层) 30 | 唯有close时,需要在connectionHandler中记录一个"原子的"状态,当状态改变时(表示已经close了) 31 | upstream goroutine退出,write操作无效 32 | 这一点在netty写的程序中同样有所体现 33 | */ 34 | 35 | /* 36 | 管理连接 37 | */ 38 | type connectionHandler struct { 39 | //connection id,每个监听端口的connection id是独立的 40 | connectId uint32 41 | 42 | //connection的关闭也是异步的,在一个独立的routine中进行 43 | closeChan chan *connectionHandler 44 | 45 | //当前连接 46 | conn *net.TCPConn 47 | 48 | err error 49 | 50 | //0表示没有关闭,1表示关闭,主要给write使用 51 | isClosing int32 52 | 53 | element *list.Element 54 | } 55 | 56 | 57 | func newConnectionHandler(conn *net.TCPConn, connectId uint32,closeChan chan *connectionHandler) *connectionHandler { 58 | return &connectionHandler{ 59 | conn: conn, 60 | connectId: connectId, 61 | closeChan: closeChan, 62 | isClosing: 0, 63 | } 64 | } 65 | 66 | /** 67 | 关闭connection,内部调用,不要使用 68 | */ 69 | func (connHandler *connectionHandler) doCloseConn() { 70 | connHandler.closeChan = nil 71 | //关闭socket 72 | defer connHandler.doRecover(false) 73 | //在closer goroutine中执行,不用担心同步问题 74 | if connHandler.conn!=nil{ 75 | connHandler.conn.Close() 76 | connHandler.conn = nil 77 | } 78 | } 79 | 80 | /* 81 | 同步发送数据 82 | */ 83 | func (connHandler *connectionHandler) write(bs []byte) error { 84 | if atomic.LoadInt32(&connHandler.isClosing) != 0 { 85 | return errors.New("connectionId:" + strconv.Itoa(int(connHandler.connectId)) + "连接已经关闭") 86 | } 87 | //直接发送 88 | defer connHandler.doRecover(true) 89 | _, err := connHandler.conn.Write(bs); 90 | if err != nil { 91 | return errors.New("connectionId:" + strconv.Itoa(int(connHandler.connectId)) + "连接已经关闭") 92 | } 93 | 94 | return nil 95 | } 96 | 97 | 98 | /** 99 | 阻塞读取的go routine 100 | */ 101 | func (connHandler *connectionHandler) receiveRoutine(consumer func(msg Message)) error{ 102 | if atomic.LoadInt32(&connHandler.isClosing) != 0 { 103 | return errors.New(strconv.Itoa(int(connHandler.connectId))+"连接已经关闭!") 104 | } 105 | //一旦读取出现异常,就关闭socket 106 | defer connHandler.doRecover(true) 107 | 108 | //阻塞读取 109 | var data = make([]byte,2000) 110 | var n, err = connHandler.conn.Read(data) 111 | 112 | if n>0 { 113 | /** 114 | 数据传输事件 115 | */ 116 | consumer(&DataMessage{ 117 | ConnectId: connHandler.connectId, 118 | Data: data[0:n], 119 | }) 120 | }else if err != nil{//没读到数据而且出现了异常,就关闭连接 121 | //设置已关闭 122 | atomic.StoreInt32(&connHandler.isClosing, 1) 123 | connHandler.err = err 124 | //送入close routine处理 125 | connHandler.closeChan <- connHandler 126 | return err 127 | } 128 | 129 | return nil 130 | } 131 | 132 | 133 | func (connHandler *connectionHandler) doRecover(isClose bool) { 134 | if err := recover(); err != nil { 135 | fmt.Println("截获到panic:", err) 136 | debug.PrintStack() 137 | 138 | if isClose { 139 | //设置已关闭 140 | atomic.StoreInt32(&connHandler.isClosing, 1) 141 | connHandler.closeChan <- connHandler 142 | } 143 | } 144 | } 145 | 146 | /** 147 | 从外部调用,关闭connection,这是一个异步操作 148 | */ 149 | func (connHandler *connectionHandler) closeFromOuter(listenPort int) { 150 | //设置已关闭 151 | atomic.StoreInt32(&connHandler.isClosing, 1) 152 | connHandler.err = errors.New("监听" + strconv.Itoa(listenPort) + "的连接" + strconv.Itoa(int(connHandler.connectId)) + "将从外部强制关闭连接") 153 | //送入close routine处理 154 | connHandler.closeChan <- connHandler 155 | } 156 | 157 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/App.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP; 2 | 3 | import okhttp3.OkHttpClient; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.boot.Banner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.context.annotation.ImportResource; 14 | 15 | import java.lang.management.ManagementFactory; 16 | import java.net.InetAddress; 17 | import java.net.UnknownHostException; 18 | import java.util.UUID; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | @Configuration 22 | @ImportResource(locations = { "classpath:/mx/wsFerryTCP/spring.xml" }) 23 | public class App implements ApplicationContextAware,InitializingBean { 24 | 25 | 26 | protected final Logger logger = LoggerFactory.getLogger(this.getClass()); 27 | 28 | /** 29 | * 实例的唯一id 30 | */ 31 | public static final String globalID = generateUUID();; 32 | 33 | public static String generateUUID(){ 34 | return UUID.randomUUID().toString(); 35 | } 36 | 37 | /** 38 | * 主机名称 39 | */ 40 | public static final String hostName = getHostName(); 41 | 42 | /** 43 | * 进程id,注意这时一个字符串 44 | */ 45 | public static final String processId = getProcessId(); 46 | 47 | /** 48 | * 如果配置了多个网卡,这个值只是其中之一 49 | * -Djava.net.preferIPv4Stack=true 50 | */ 51 | public static final String localIp = getLocalIP(); 52 | 53 | /** 54 | * 只支持linux和windows这两个平台 55 | */ 56 | public static final boolean isLinux = !isWindows(); 57 | 58 | 59 | private static String getProcessId(){ 60 | String runtimeName = ManagementFactory.getRuntimeMXBean().getName(); 61 | int i = runtimeName.indexOf("@"); 62 | return runtimeName.substring(0, i); 63 | } 64 | 65 | private static String getHostName(){ 66 | String runtimeName = ManagementFactory.getRuntimeMXBean().getName(); 67 | int i = runtimeName.indexOf("@"); 68 | return runtimeName.substring(i+1); 69 | } 70 | 71 | private static String getLocalIP(){ 72 | InetAddress addr; 73 | try { 74 | addr = InetAddress.getLocalHost(); 75 | return addr.getHostAddress().toString(); 76 | } catch (UnknownHostException e) { 77 | } 78 | 79 | return null; 80 | } 81 | 82 | 83 | private static boolean isWindows() { 84 | return System.getProperties().getProperty("os.name").toUpperCase().indexOf("WINDOWS") != -1; 85 | } 86 | 87 | /** 88 | * cpu核心数 89 | */ 90 | public static final int numOfCores = Runtime.getRuntime().availableProcessors(); 91 | 92 | 93 | private static volatile OkHttpClient client; 94 | 95 | /** 96 | * 用的时候再分配资源,里面有缓冲和线程池,估计消耗也少不了 97 | */ 98 | public static OkHttpClient getHTTPClient(){ 99 | if(client == null){ 100 | synchronized (App.class){ 101 | if(client == null){ 102 | client = new OkHttpClient.Builder() 103 | //有的服务就是很慢,像是访问数据库的服务,一般30秒到2分分钟之内都是正常的 104 | //okhttp的默认值是10秒 105 | .readTimeout(30, TimeUnit.SECONDS) 106 | .build(); 107 | 108 | client.dispatcher().setMaxRequestsPerHost(8000); 109 | //client.dispatcher().setMaxRequests(20000); 110 | } 111 | } 112 | } 113 | 114 | return client; 115 | } 116 | 117 | @Override 118 | public void afterPropertiesSet() throws Exception { 119 | 120 | } 121 | 122 | private static Class applicationClazz; 123 | 124 | 125 | protected static void startUp(String[] args){ 126 | try { 127 | //得到启动类,找到第一个BaseApplication的子类为止 128 | Thread mainThread = Thread.currentThread(); 129 | StackTraceElement[] stackTrace = mainThread.getStackTrace(); 130 | 131 | for(int i=stackTrace.length - 1;i>=0;i--){ 132 | String mainClassName = stackTrace[i].getClassName(); 133 | applicationClazz = mainThread.getContextClassLoader().loadClass(mainClassName); 134 | if(App.class.isAssignableFrom(applicationClazz)){ 135 | break; 136 | }else{ 137 | applicationClazz = null; 138 | } 139 | } 140 | 141 | System.out.println("启动类:"+ applicationClazz); 142 | 143 | //找不到启动类,下面报错 144 | SpringApplication app = new SpringApplication(applicationClazz); 145 | app.setBannerMode(Banner.Mode.OFF); 146 | app.run(args); 147 | } catch (Exception e) { 148 | e.printStackTrace(); 149 | System.err.println("进程退出"); 150 | System.exit(1); 151 | } 152 | } 153 | 154 | 155 | protected static ApplicationContext applicationContext; 156 | @Override 157 | public void setApplicationContext(ApplicationContext _applicationContext) throws BeansException { 158 | applicationContext = _applicationContext; 159 | } 160 | 161 | public static void main(String[] args) { 162 | startUp(args); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/mx/wsFerryTCP/ws/WsClient.java: -------------------------------------------------------------------------------- 1 | package mx.wsFerryTCP.ws; 2 | 3 | import com.google.common.base.Throwables; 4 | import mx.wsFerryTCP.tcp.NettyClientHammal; 5 | import okhttp3.*; 6 | import okio.ByteString; 7 | 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | public class WsClient extends WebSocketListener implements IWriter{ 12 | 13 | /** 14 | * 创建摆渡通道 15 | * @param wsServerAddress ws服务端地址 包括端口 16 | * @param remoteListenPort 在tcp listen端要监听的端口,而不是ws服务端的端口 17 | */ 18 | public static void establishFerryTransport(String wsServerAddress, int remoteListenPort){ 19 | WsClient client = new WsClient(wsServerAddress,remoteListenPort,new NettyClientHammal()); 20 | client.connect(); 21 | } 22 | 23 | protected static final Logger logger = LogManager.getLogger(WsClient.class); 24 | 25 | public final String wsServerAddress; 26 | public final int listenPort; 27 | private final HammalBase hammal; 28 | 29 | /** 30 | * @param wsServerIpPort webSocket服务端ip和端口 31 | * @param remoteListenPort tcp listen的端口 32 | * @param hammal 数据搬运工 33 | */ 34 | public WsClient(String wsServerIpPort, int remoteListenPort, HammalBase hammal) { 35 | if(hammal==null){ 36 | throw new RuntimeException("hammal不能为null"); 37 | } 38 | this.wsServerAddress = wsServerIpPort.trim(); 39 | this.listenPort = remoteListenPort; 40 | this.hammal = hammal; 41 | this.hammal.setWriter(this); 42 | } 43 | 44 | public void connect(){ 45 | Request request = new Request.Builder() 46 | .url("ws://"+wsServerAddress+"/ws/ferry/tcp") 47 | .build(); 48 | 49 | OkHttpClient client = new OkHttpClient(); 50 | //这个连接是异步的,失败也不会报错 51 | client.newWebSocket(request,this); 52 | } 53 | 54 | volatile WebSocket webSocket; 55 | 56 | @Override 57 | public void write(byte[] bs){ 58 | //这个方法本身是同步的 59 | webSocket.send(ByteString.of(bs)); 60 | //修改了okHttp3内部缓冲区导致的bug 61 | while(webSocket.queueSize()>10276683){ 62 | try { 63 | Thread.sleep(100); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | 70 | 71 | /** 72 | * 当连接打开时登录 73 | */ 74 | @Override 75 | public void onOpen(WebSocket webSocket, Response response) { 76 | this.webSocket = webSocket; 77 | //发送登陆消息 78 | Message.LoginMessage loginMessage = new Message.LoginMessage(listenPort,"Admin","密码牢不可破"); 79 | write(loginMessage.marshal()); 80 | } 81 | 82 | 83 | @Override 84 | public void onMessage(WebSocket webSocket, ByteString bytes) { 85 | try { 86 | byte[] bs = bytes.toByteArray(); 87 | Message message = Message.unmarshal(bs); 88 | //登录响应 89 | if (message instanceof Message.LoginResponseMessage){ 90 | Message.LoginResponseMessage loginResponseMessage = (Message.LoginResponseMessage) message; 91 | String error; 92 | if((error = loginResponseMessage.getError())!=null){ 93 | logger.error("登录失败"+error); 94 | hammal.onWsClose(); 95 | webSocket.close(1001,"webSocket登录失败,客户端主动关闭连接"); 96 | }else{ 97 | //登录成功 98 | logger.info("登录成功"); 99 | } 100 | } 101 | //tcp listen连接建立消息 102 | else if(message instanceof Message.ConnectActiveMessage){ 103 | Message.ConnectActiveMessage ca = (Message.ConnectActiveMessage) message; 104 | hammal.onListenConnActive(ca.connectId); 105 | } 106 | //tcp listen连接断掉消息 107 | else if(message instanceof Message.ConnectInactiveMessage){ 108 | Message.ConnectInactiveMessage cim = (Message.ConnectInactiveMessage) message; 109 | hammal.onListenConnInactive(cim.connectId); 110 | } 111 | //从tcp listen摆渡数据消息 112 | else if(message instanceof Message.DataMessage){ 113 | Message.DataMessage dm = (Message.DataMessage) message; 114 | hammal.onReceiveDataFromListenConn(dm.connectId,dm.data); 115 | }else{ 116 | //忽略 117 | } 118 | } catch (Exception e) { 119 | logger.error(Throwables.getStackTraceAsString(e)); 120 | } 121 | } 122 | 123 | @Override 124 | public void onClosing(WebSocket webSocket, int code, String reason) { 125 | hammal.onWsClose(); 126 | webSocket.close(1001,"webSocket登录失败,客户端主动关闭连接"); 127 | } 128 | 129 | /** 130 | * 连接和写入出现错误 131 | */ 132 | @Override 133 | public void onFailure(WebSocket webSocket, Throwable t, Response response) { 134 | System.err.println("wsServerAddress:"+wsServerAddress+",listenPort:"+listenPort+ 135 | ",连接或写入出现错误:"+Throwables.getStackTraceAsString(t)); 136 | hammal.onWsClose(); 137 | webSocket.close(1001,"webSocket登录失败,客户端主动关闭连接"); 138 | } 139 | 140 | 141 | /** 142 | * 从外部手动关闭这个ws客户端 143 | */ 144 | public void close(String reason){ 145 | if(webSocket!=null){ 146 | webSocket.close(1000,reason+"客户端主动关闭"); 147 | } 148 | } 149 | 150 | } 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/main/java/mx/tcp/NettyBase.java: -------------------------------------------------------------------------------- 1 | package mx.tcp; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import com.google.common.base.Throwables; 7 | 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.bootstrap.ServerBootstrap; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelHandler; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.ChannelOption; 14 | import io.netty.channel.EventLoopGroup; 15 | import io.netty.channel.epoll.EpollEventLoopGroup; 16 | import io.netty.channel.epoll.EpollServerSocketChannel; 17 | import io.netty.channel.epoll.EpollSocketChannel; 18 | import io.netty.channel.nio.NioEventLoopGroup; 19 | import io.netty.channel.socket.SocketChannel; 20 | import io.netty.channel.socket.nio.NioServerSocketChannel; 21 | import io.netty.channel.socket.nio.NioSocketChannel; 22 | 23 | /** 24 | * 可作为tcp客户端,也可以作为服务器端 25 | */ 26 | public abstract class NettyBase { 27 | 28 | protected static final Logger logger = LogManager.getLogger(NettyBase.class); 29 | 30 | /** 31 | * 是否作为tcp服务端,否则就是客户端 32 | */ 33 | final boolean isTCPServer; 34 | 35 | public NettyBase(boolean isServer) { 36 | this.isTCPServer = isServer; 37 | } 38 | 39 | public static interface ChannelHandlerCreater{ 40 | ChannelHandler[] create(SocketChannel ch); 41 | } 42 | 43 | ServerBootstrap serverBootstrap; 44 | EventLoopGroup serverBossGroup; 45 | EventLoopGroup serverrWorkerGroup; 46 | ChannelFuture[] serverFutures; 47 | 48 | /** 49 | * 此方法为阻塞方法,直到所有端口都监听成功才返回 50 | * @param bossNum 51 | * @param workerNum 52 | * @param bindPorts 53 | * @param handlerCreater 54 | * @throws Throwable 55 | */ 56 | protected synchronized void initServer(int bossNum,int workerNum,int[] bindPorts,final ChannelHandlerCreater handlerCreater) throws Throwable{ 57 | if(serverBossGroup!=null) return; 58 | 59 | if(isLinux){ 60 | serverBossGroup = new EpollEventLoopGroup(bossNum); 61 | serverrWorkerGroup = new EpollEventLoopGroup(workerNum); 62 | }else{ 63 | serverBossGroup = new NioEventLoopGroup(bossNum); 64 | //netty在windows上线程轮询有问题,不要超过核心数的两倍,低了到核心数1/2又出现同样的问题 65 | serverrWorkerGroup = new NioEventLoopGroup(workerNum); 66 | } 67 | 68 | serverBootstrap = new ServerBootstrap(); 69 | 70 | serverBootstrap.group(serverBossGroup, serverrWorkerGroup) 71 | .channel(isLinux?EpollServerSocketChannel.class:NioServerSocketChannel.class) 72 | .childHandler(new ChannelInitializer() { 73 | public void initChannel(SocketChannel ch) throws Exception { 74 | ChannelHandler[] handers; 75 | try { 76 | handers = handlerCreater.create(ch); 77 | ch.pipeline().addLast(handers); 78 | } catch (Exception e) { 79 | logger.error(Throwables.getStackTraceAsString(e)); 80 | ch.close(); 81 | } 82 | 83 | } 84 | }) 85 | .childOption(ChannelOption.SO_KEEPALIVE, true) 86 | .childOption(ChannelOption.TCP_NODELAY, true) 87 | .childOption(ChannelOption.SO_SNDBUF, 1024*256) 88 | .childOption(ChannelOption.SO_RCVBUF, 1024*1024*64); 89 | 90 | serverFutures = new ChannelFuture[bindPorts.length]; 91 | //绑定多个端口 92 | for(int i=0;i(){ 117 | protected void initChannel(SocketChannel ch) throws Exception { 118 | 119 | } 120 | }); 121 | } 122 | 123 | /** 124 | * 楞关,没有验证过,也没有这样的需求 125 | * 实际都是关进程 126 | */ 127 | public synchronized void destory(){ 128 | if(serverFutures!=null){ 129 | for(int i=0;i 0 { 95 | var clientAddr = tcpListenClientMap[tcpListenPort] 96 | if clientAddr == wsConn.Request().RemoteAddr { 97 | delete(tcpListenClientMap, tcpListenPort) 98 | } 99 | } 100 | return nil 101 | }) 102 | 103 | } 104 | 105 | /* 106 | 标准库的connection的upstream处理模式就是一个循环 107 | */ 108 | func handlerWSBinary(conn *websocket.Conn) { 109 | //1.ws客户端的连接上来 110 | var listenPort = -1 111 | var tl *TcpListen 112 | 113 | var clientAddr = conn.Request().RemoteAddr 114 | 115 | flag0: 116 | for { 117 | var upStreamData []byte 118 | if err := websocket.Message.Receive(conn, &upStreamData); err != nil { 119 | println(clientAddr + "对应的webSocket服务端接收二进制错误:" + err.Error()) 120 | closeWSConnection(listenPort, conn) 121 | break flag0 122 | } else { 123 | //2.ws客户端的数据上来 124 | var message, err = Unmarshal(upStreamData) 125 | if err != nil { 126 | //反序列化失败 127 | utils.PrintErrorStack(err) 128 | closeWSConnection(listenPort, conn) 129 | //不给其他不友好的客户端连接的机会 130 | break flag0 131 | } else { 132 | /* 133 | A.登录消息 134 | B.ferry target端的连接断掉消息 135 | C.从ferry target到ferry source端的数据传输消息 136 | */ 137 | switch message.(type) { 138 | /* 139 | A.登录消息 140 | */ 141 | case *LoginMessage: 142 | var errorMessage string 143 | 144 | var lm = message.(*LoginMessage) 145 | if lm.UserName == "Admin" && lm.Password == "密码牢不可破" { //暂时采取固定的用户名 146 | if tl == nil { 147 | listenPort = lm.ListenPort 148 | //如果登录成功,建立tcpListen 149 | var isPut, _err = computeIfTcpListenClientAbsent(listenPort, func() (string, error) { 150 | /* 151 | 创建这个端口tcpListen,并向这个tcpListen注册消费tcp事件消息的方法 152 | */ 153 | tl, err = GetOrCreateTCPListen(listenPort, func(msg Message) { 154 | bs := msg.Marshal() 155 | send(conn, bs) 156 | }) 157 | //如果tcpListen创建成功,返回成功 158 | if err == nil { 159 | //发送登录成功的效应消息 160 | send(conn, (&LoginResponseMessage{}).Marshal()) 161 | 162 | fmt.Printf("来自" + clientAddr + "的客户端登录成功!并成功打开监听端口" + strconv.Itoa(listenPort)+"\n") 163 | 164 | return conn.Request().RemoteAddr, nil 165 | } else { 166 | return "", err 167 | } 168 | }) 169 | 170 | if _err != nil { 171 | errorMessage = _err.Error() 172 | } else { 173 | if isPut { 174 | continue flag0 175 | } else { 176 | errorMessage = "已经有其他客户端创建了连接,请等待" 177 | } 178 | } 179 | } else { 180 | //忽略同一个客户端重复的登录 181 | continue flag0 182 | } 183 | } else { 184 | errorMessage = "用户名密码错误" 185 | } 186 | 187 | println("来自" + clientAddr + "的客户端登录失败!原因为," + errorMessage) 188 | 189 | var lrm = LoginResponseMessage{ 190 | errorMessage, 191 | } 192 | 193 | //发送失败的登录响应消息 194 | send(conn, lrm.Marshal()) 195 | 196 | //登录遇到失败就关闭这个ws连接 197 | closeWSConnection(listenPort, conn); 198 | break flag0 199 | 200 | /* 201 | B.ferry端的某个TCP连接断了,对应连接id的tcpListen端的TCP连接也断 202 | */ 203 | case *ConnectInactiveMessage: 204 | var clm = message.(*ConnectInactiveMessage) 205 | if tl != nil { 206 | tl.CloseConn(clm.ConnectId) 207 | continue flag0 208 | } 209 | 210 | /* 211 | C.数据传输的消息 212 | */ 213 | case *DataMessage: 214 | var dm = message.(*DataMessage) 215 | if tl != nil { 216 | tl.Write(dm.ConnectId, dm.Data) 217 | continue flag0 218 | } 219 | 220 | default: 221 | //不应该到这里 222 | } //~switch 223 | } //~处理个类型的message 224 | } //~else 225 | } //~for 226 | 227 | /* 228 | 退出服务端消息循环 229 | */ 230 | var warnMessage = "客户端:" + clientAddr + "对应的webSocket服务端轮询退出"; 231 | if listenPort > 0 { 232 | warnMessage = "TCP监听端口:" + strconv.Itoa(listenPort) + "对应的,而且是" + warnMessage 233 | } 234 | println(warnMessage); 235 | 236 | /* 237 | ws断掉,其对应的tcp listen也同样断掉 238 | */ 239 | if tl != nil { 240 | tl.BreakAcceptAndCloseConnections() 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/mx/utils/ByteUtils.java: -------------------------------------------------------------------------------- 1 | package mx.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.util.Calendar; 6 | import java.util.GregorianCalendar; 7 | 8 | import org.apache.commons.lang.StringUtils; 9 | 10 | public class ByteUtils { 11 | 12 | /** 13 | * 此方法不改变buf的writeIndex和readIndex 14 | */ 15 | public static byte[] getBytes(ByteBuf buf){ 16 | int size = buf.writerIndex(); 17 | byte[] bytes = new byte[size]; 18 | buf.getBytes(0, bytes); 19 | 20 | return bytes; 21 | } 22 | 23 | /** 24 | * 紧凑型 25 | */ 26 | public static String toCompactHexString(byte[] bytes){ 27 | StringBuffer sb = new StringBuffer(); 28 | for(int i= 0;i1?h:("0"+h); 31 | sb.append(h); 32 | } 33 | return sb.toString(); 34 | } 35 | 36 | public static String toHexString(Object obj){ 37 | if(obj == null) return "null"; 38 | 39 | if(obj.getClass().isArray()){ 40 | byte[] bytes = (byte[]) obj; 41 | StringBuffer sb = new StringBuffer(); 42 | for(int i= 0;i1?h:("0"+h); 45 | sb.append(h+" "); 46 | } 47 | return sb.toString(); 48 | }else if(obj instanceof Byte){ 49 | return toHexString(new byte[]{(Byte)obj}); 50 | }else if(obj instanceof ByteBuf){ 51 | return toHexString(getBytes((ByteBuf)obj)); 52 | } 53 | 54 | throw new RuntimeException("can't get hex for class "+obj.getClass()); 55 | } 56 | 57 | public static byte[] toBytes(String hexs){ 58 | String[] hexes = hexs.split("\\s+"); 59 | 60 | byte[] re = new byte[hexes.length]; 61 | for(int i=0;i=0;i--){ 124 | sb.append((bytes[i]&0xff)>>>4); 125 | int low = bytes[i]&0xf; 126 | if(low<10) sb.append(low); 127 | } 128 | 129 | return sb.toString(); 130 | } 131 | 132 | public static String bytesToBCD(byte[] bytes){ 133 | StringBuilder sb = new StringBuilder(); 134 | for(int i=0;i>>4); 136 | int low = bytes[i]&0xf; 137 | if(low<10) sb.append(low); 138 | } 139 | 140 | return sb.toString(); 141 | } 142 | 143 | /** 144 | * bytesToBCD_LTE方法的反方法 145 | * 地址域是小端字节序 146 | */ 147 | public static byte[] bCD_LTEToBytes(String bcd){ 148 | char[] chs = bcd.toCharArray(); 149 | 150 | byte[] re = new byte[chs.length/2+chs.length%2]; 151 | int j=0; 152 | for(int i=chs.length-1;i>=0;i--){ 153 | int low = chs[i]-48; 154 | int high; 155 | if(--i<0){ 156 | high = low; 157 | low = 15; 158 | }else{ 159 | high = chs[i]-48; 160 | } 161 | 162 | re[j++] = (byte) ((high<<4)+low); 163 | } 164 | return re; 165 | } 166 | 167 | public static byte[] bCDToBytes(String bcd){ 168 | char[] chs = bcd.toCharArray(); 169 | 170 | byte[] re = new byte[chs.length/2+chs.length%2]; 171 | int j=0; 172 | for(int i=0;i0){ 155 | buf.writeBytes(error.getBytes("UTF-8")); 156 | } 157 | return ByteUtils.getBytes(buf); 158 | } catch (UnsupportedEncodingException e) { 159 | } 160 | 161 | return null; 162 | } 163 | 164 | static LoginResponseMessage _unmarshal(byte[] bs){ 165 | try { 166 | ByteBuf buf = Unpooled.wrappedBuffer(bs); 167 | buf.readByte(); 168 | 169 | String error = null; 170 | 171 | int l; 172 | if((l = bs.length-1)>0){ 173 | byte[] strBs = new byte[l]; 174 | buf.readBytes(strBs); 175 | error = new String(strBs,"UTF-8"); 176 | } 177 | 178 | return new LoginResponseMessage(error); 179 | } catch (UnsupportedEncodingException e) { 180 | } 181 | 182 | return null; 183 | } 184 | } 185 | 186 | 187 | /** 188 | * 数据传输消息 189 | * type 30 190 | */ 191 | public static final class DataMessage extends Message{ 192 | public final int connectId; 193 | public final byte[] data; 194 | 195 | public DataMessage(int connectId,byte[] data) { 196 | this.connectId = connectId; 197 | this.data = data; 198 | } 199 | 200 | public long getConnectId() { 201 | return connectId; 202 | } 203 | 204 | public byte[] getData() { 205 | return data; 206 | } 207 | 208 | @Override 209 | public byte[] marshal() { 210 | ByteBuf buf = Unpooled.buffer(); 211 | buf.writeByte(dataMessageType); 212 | buf.writeInt(connectId); 213 | buf.writeBytes(data); 214 | return ByteUtils.getBytes(buf); 215 | } 216 | 217 | static DataMessage _unmarshal(byte[] bs){ 218 | ByteBuf buf = Unpooled.wrappedBuffer(bs); 219 | buf.readByte(); 220 | int connectId = buf.readInt(); 221 | byte[] data = new byte[bs.length-5]; 222 | buf.readBytes(data); 223 | 224 | return new DataMessage(connectId,data); 225 | } 226 | } 227 | 228 | /** 229 | * 序列化 230 | */ 231 | public abstract byte[] marshal(); 232 | 233 | public static final byte loginMessageType = 0; 234 | public static final byte connectActiveMessageType = 31; 235 | public static final byte connectInactiveMessageType = 61; 236 | public static final byte loginResponseMessageType = 01; 237 | public static final byte dataMessageType = 30; 238 | 239 | 240 | /** 241 | * 反序列化 242 | */ 243 | public static Message unmarshal(byte[] bs){ 244 | int type = (int)(bs[0]); 245 | switch (type){ 246 | case loginMessageType: 247 | throw new RuntimeException("在ferry中没有反序列化登录消息的可能性"); 248 | case connectActiveMessageType: 249 | return ConnectActiveMessage._unmarshal(bs); 250 | case connectInactiveMessageType: 251 | return ConnectInactiveMessage._unmarshal(bs); 252 | case loginResponseMessageType: 253 | return LoginResponseMessage._unmarshal(bs); 254 | case dataMessageType: 255 | return DataMessage._unmarshal(bs); 256 | 257 | default: 258 | throw new RuntimeException("反序列化的时候遇到未知的类型"+type); 259 | } 260 | } 261 | 262 | static String marshalToHex(Message message){ 263 | byte[] bs = message.marshal(); 264 | String hex = ByteUtils.toHexString(bs); 265 | System.out.println(hex); 266 | return hex; 267 | } 268 | 269 | static void unMarshalTest(String hex){ 270 | byte[] bs = ByteUtils.toBytes(hex); 271 | Message message = unmarshal(bs); 272 | System.out.println(message.getClass()+"/"+new Json(message)); 273 | } 274 | 275 | public static void main(String[] args) { 276 | LoginMessage loginMessage = new LoginMessage(19090,"admin","Oneplus1=two"); 277 | String hex = marshalToHex(loginMessage); 278 | System.out.println("---登录消息:"+hex); 279 | 280 | System.out.println("---断连接消息:"); 281 | ConnectInactiveMessage connectInactiveMessage = new ConnectInactiveMessage(11345); 282 | hex = marshalToHex(connectInactiveMessage); 283 | unMarshalTest(hex); 284 | 285 | System.out.println("---登录响应消息:"); 286 | LoginResponseMessage loginResponseMessage = new LoginResponseMessage("已经有另外的账户登录了"); 287 | hex = marshalToHex(loginResponseMessage); 288 | unMarshalTest(hex); 289 | 290 | System.out.println("---数据传输消息:"); 291 | DataMessage dataMessage = new DataMessage(919183,new byte[]{0x00,(byte)0x99,(byte)0xff,(byte)0x80,0x7f}); 292 | hex = marshalToHex(dataMessage); 293 | unMarshalTest(hex); 294 | 295 | //-------------------------------------------------------- 296 | System.out.println("##########验证golang的结果##########"); 297 | unMarshalTest("01 E5 B7 B2 E7 BB 8F E6 9C 89 E5 8F A6 E5 A4 96 E7 9A 84 E8 B4 A6 E6 88 B7 E7 99 BB E5 BD 95 E4 BA 86"); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/main/java/mx/utils/Json.java: -------------------------------------------------------------------------------- 1 | package mx.utils; 2 | 3 | import java.io.File; 4 | import java.sql.Timestamp; 5 | import java.text.SimpleDateFormat; 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Random; 12 | 13 | import net.sf.json.JSON; 14 | import net.sf.json.JSONArray; 15 | import net.sf.json.JSONNull; 16 | import net.sf.json.JSONObject; 17 | import net.sf.json.JsonConfig; 18 | import net.sf.json.processors.JsonValueProcessor; 19 | import net.sf.json.util.JSONUtils; 20 | 21 | /** 22 | * 改进的jso类,有空还是看一下jsonlib 23 | * 以此类形成一种比较灵活的动态数据模型 24 | */ 25 | public class Json { 26 | 27 | public static enum TYPE{ 28 | //对象类型 29 | object, 30 | //数组类型 31 | array, 32 | //其他基本类型都为字符串类型,由业务层去打理 33 | string, 34 | //null 35 | nvl 36 | } 37 | 38 | //内部封装值 39 | protected JSON json; 40 | /** 41 | * 值---这个值实际上是和json互补的,当表达一个普通的值时json为null,当可以按照json解析时,strValue为null 42 | */ 43 | protected String strValue; 44 | 45 | /** 46 | * 类型 47 | */ 48 | private TYPE type; 49 | public TYPE getType() { 50 | return type; 51 | } 52 | 53 | //当是对象或数组类型时,可判断是否empty 54 | public Boolean isEmpty(){ 55 | if(this.type.equals(TYPE.object) || this.type.equals(TYPE.array)) return json.isEmpty(); 56 | //返回null,表示不支持这种类型 57 | return null; 58 | } 59 | 60 | private static final SimpleDateFormat dateFm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 61 | { 62 | //要么将时区设置为标准格林威治时区,并调整时间,要么在下面修改为东8区,麻烦 63 | //dateFm.setTimeZone(TimeZone.getTimeZone("GMT+8")); 64 | } 65 | private static final JsonConfig defaultJC=new JsonConfig(); 66 | private static final JsonValueProcessor defaultDateValueProcessor=new JsonValueProcessor(){ 67 | /** 68 | * 当作为数组或集合的元素时,应如何解析 69 | */ 70 | public Object processArrayValue(Object arg0, JsonConfig arg1) { 71 | Timestamp timeStamp = new Timestamp(((java.util.Date)arg0).getTime()); 72 | return dateFm.format(timeStamp); 73 | } 74 | /** 75 | * 当作为对象的属性时应该如何解析 76 | */ 77 | public Object processObjectValue(String arg0, Object arg1, JsonConfig arg2) { 78 | if(arg1==null) return ""; 79 | Timestamp timeStamp = new Timestamp(((java.util.Date)arg1).getTime()); 80 | return dateFm.format(timeStamp); 81 | } 82 | }; 83 | 84 | private static JsonValueProcessor jsonProcessor=new JsonValueProcessor(){ 85 | public Object processArrayValue(Object arg0, JsonConfig arg1) { 86 | return ((Json)arg0).toString(); 87 | } 88 | public Object processObjectValue(String arg0, Object arg1, JsonConfig arg2) { 89 | if(arg1==null) return ""; 90 | return ((Json)arg1).toString(); 91 | } 92 | }; 93 | 94 | static { 95 | /** 96 | * 修改时间类型的序列化方式 97 | */ 98 | defaultJC.registerJsonValueProcessor(Timestamp.class,defaultDateValueProcessor); 99 | defaultJC.registerJsonValueProcessor(java.sql.Time.class, defaultDateValueProcessor); 100 | defaultJC.registerJsonValueProcessor(java.sql.Date.class, defaultDateValueProcessor); 101 | defaultJC.registerJsonValueProcessor(java.util.Date.class, defaultDateValueProcessor); 102 | defaultJC.registerJsonValueProcessor(Json.class, jsonProcessor); 103 | } 104 | 105 | public class NoUse{ 106 | Object object; 107 | public Object getObject() {return object;} 108 | public void setObject(Object object) {this.object = object;} 109 | } 110 | 111 | /** 112 | * 注意这个构造器返回类型为nul的json 113 | */ 114 | public Json(){ 115 | this(null); 116 | } 117 | 118 | private static final Object object = new Object(); 119 | public static Json createJson(){ 120 | return new Json(object); 121 | } 122 | /** 123 | * 构造参数要不是json元素,要不是一个java对象.注意同反序列化方法的区别 124 | * 约定:字符串"null","undefined" 表达的意义是nvl 125 | */ 126 | public Json(Object target){ 127 | // 128 | if(target==null){ 129 | this.strValue="null"; 130 | this.type=TYPE.nvl; 131 | this.json=JSONNull.getInstance(); 132 | }else if(JSONObject.class.isAssignableFrom(target.getClass())) { 133 | this.json =(JSON)target; 134 | this.type=TYPE.object; 135 | }else if(JSONArray.class.isAssignableFrom(target.getClass())) { 136 | this.json =(JSON)target; 137 | this.type=TYPE.array; 138 | }else if(JSONNull.class.isAssignableFrom(target.getClass())) { 139 | this.json =(JSON)target; 140 | this.type=TYPE.nvl; 141 | }else if(JSONUtils.isArray(target)){//大概凡是可以被认同为"数组"而被json化的比如set,list,emuration等 142 | json=JSONArray.fromObject(target,defaultJC); 143 | this.type=TYPE.array; 144 | }else if(JSONUtils.isObject(target)){//可以被认为是对象的 145 | NoUse nu=new NoUse(); 146 | nu.setObject(target); 147 | Object object=JSONObject.fromObject(nu,defaultJC).get("object"); 148 | if(object instanceof JSONObject){ 149 | json=(JSONObject)object; 150 | this.type=TYPE.object; 151 | }else if(object instanceof JSONNull){//字符串"null"属于这里 152 | this.strValue="null"; 153 | this.type=TYPE.nvl; 154 | this.json=JSONNull.getInstance(); 155 | }else if(object instanceof String){//时间类型会在这里 156 | this.strValue=(String)object; 157 | this.type=TYPE.string; 158 | } 159 | }else{ 160 | //其他均按字符串处理 161 | this.strValue=target.toString(); 162 | this.type=TYPE.string; 163 | } 164 | } 165 | 166 | /** 167 | * 反序列化接口 168 | */ 169 | public static Json desierialize(String jsString){ 170 | String _temp="{OO:"+jsString+"}"; 171 | Object target=JSONObject.fromObject(_temp,defaultJC).get("OO"); 172 | return new Json(target); 173 | } 174 | 175 | /** 176 | * 如果是数组类型,得到长度 177 | * @return 178 | */ 179 | public int length(){ 180 | if(this.type.equals(TYPE.array)){ 181 | return ((JSONArray)json).size(); 182 | } 183 | //返回-1表示不是数组 184 | return -1; 185 | } 186 | 187 | public List asList(){ 188 | if(this.type.equals(TYPE.nvl)) return new ArrayList(0); 189 | List re=new LinkedList(); 190 | if(this.type.equals(TYPE.array)){ 191 | for(int i=0;i)this.json).keySet()){ 219 | Json value=new Json(((Map)this.json).get(id)); 220 | tCallBack.process(id, value ,this ,layer); 221 | //子一级继续遍历 222 | if(limitLayer<0 || limitLayer>layer) value.travel(tCallBack,layer+1,limitLayer); 223 | } 224 | }else if(this.type.equals(TYPE.array)){ 225 | Iterator it = ((Iterable)this.json).iterator(); 226 | int i=0; 227 | while(it.hasNext()){ 228 | Json value=new Json(it.next()); 229 | tCallBack.process(""+(i++), value,this,layer); 230 | //子一级继续遍历 231 | if(limitLayer<0 || limitLayer>layer) value.travel(tCallBack,layer+1,limitLayer); 232 | } 233 | }else if(this.type.equals(TYPE.string) || this.type.equals(TYPE.nvl)){ 234 | return; 235 | } 236 | } 237 | 238 | /** 239 | * 调试用方法 240 | */ 241 | public synchronized void debug(){ 242 | this.travel(new TravelCallback(){ 243 | public void process(String id, Json jso, Json parent, int layer) { 244 | for(int i=0;i0?",":"")+id); 268 | try { 269 | if(current.type.equals(TYPE.object)) current=new Json(((JSONObject)current.json).get(id)); 270 | else if(current.type.equals(TYPE.array)) current=new Json(((JSONArray)current.json).get(Integer.parseInt(id))); 271 | else throw new RuntimeException("can't get value from object that not belong to Object or Array type!"); 272 | } catch (Exception e) { 273 | e.printStackTrace(); 274 | throw new RuntimeException("can't find property "+id+" in path:"+alreadyIdPath+"\n current:"+current+"\n this: "+this); 275 | } 276 | } 277 | 278 | if(current.getType().equals(TYPE.nvl)) return null; 279 | return current; 280 | } 281 | 282 | /** 283 | * 删除属性,注意不会将作为空集合的父删掉,如果删除属性不存在,将不会抛出异常,但数组索引不能越界 284 | * @param idPath 285 | * @param propId 要删除的属性索引或名称 286 | */ 287 | public synchronized Json remove(String idPath,String propId){ 288 | Json target=find(idPath); 289 | if(target.type.equals(TYPE.object)) return new Json(((JSONObject)target.json).remove(propId)); 290 | else if(target.type.equals(TYPE.array)) return new Json(((JSONArray)target.json).remove(Integer.parseInt(propId))); 291 | else throw new RuntimeException("can't find property "+propId+" in path:"+idPath+"\n current:"+target+"\n this: "+this); 292 | 293 | } 294 | 295 | 296 | public synchronized void addOrUpadte(String idPath,String propId,Json value){ 297 | Json target=find(idPath); 298 | if(target.type.equals(TYPE.object)){ 299 | //当是一个可以json化的值时 300 | if(value.json!=null) ((JSONObject)target.json).element(propId,value.json); 301 | //当是一个普通值时 302 | else ((JSONObject)target.json).element(propId,value.strValue); 303 | 304 | }else if(target.type.equals(TYPE.array)){ 305 | if(value.json!=null) ((JSONArray)target.json).element(Integer.parseInt(propId),value.json); 306 | else ((JSONArray)target.json).element(Integer.parseInt(propId),value.strValue); 307 | }else throw new RuntimeException("can't find property "+propId+" in path:"+idPath+"\n current:"+target+"\n this: "+this); 308 | } 309 | /** 310 | * value可以判断是否为null 311 | */ 312 | public String value(){ 313 | if(type.equals(TYPE.nvl)) return null; 314 | if(strValue!=null && !strValue.equals("undefined")) return strValue; 315 | else if(json!=null) return json.toString(); 316 | //当strValue为undefined时 317 | return null; 318 | } 319 | 320 | public String toString(){ 321 | String value=value(); 322 | return value==null?"null":value; 323 | } 324 | 325 | 326 | public static void main(String[] args){ 327 | /* 328 | Json aaa=new Json().desierialize("[1,2,3,4]"); 329 | aaa.addOrUpadte(".","4", new Json("bbbbb")); 330 | Json bbb=new Json(aaa.value()); 331 | Json ccc=new Json().desierialize(bbb.value()); 332 | ccc.addOrUpadte(".","5", new Json("fff")); 333 | System.out.println(ccc.value()); 334 | System.out.println(new Json(new java.util.Date()).value()); 335 | String[] aa= new String[]{"上海\"大白兔\"食品有限公司"}; 336 | System.out.println(new Json(aa).value()); 337 | */ 338 | Json jj = Json.desierialize("{aaa_ff:111,'bbb':{s:1,b:2}}"); 339 | jj.travelAllLayer(new TravelCallback(){ 340 | public void process(String id, Json jso, Json parent, int layer) { 341 | System.out.println("属性名:"+id+":属性值"+jso.toString()+",层次:"+layer); 342 | } 343 | }); 344 | try { 345 | Random r = new Random(); 346 | File tempDir = null; 347 | do{ 348 | String random = (r.nextDouble()+"_"+System.currentTimeMillis()).substring(2); 349 | String temp = System.getProperty("java.io.tmpdir")+"hadoop_piece\\inpurFormatParams\\"+random; 350 | tempDir = new File(temp); 351 | }while(tempDir.exists()); 352 | tempDir.mkdirs(); 353 | 354 | File tempFile = new File(tempDir.getPath()+"\\conf"); 355 | tempFile.createNewFile(); 356 | System.out.println(tempFile.getPath()); 357 | 358 | 359 | } catch (Exception e) { 360 | e.printStackTrace(); 361 | } 362 | } 363 | 364 | } 365 | 366 | 367 | --------------------------------------------------------------------------------