├── .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 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------