├── .gitignore ├── .travis.yml ├── README.md ├── bench ├── gostd_test.go └── nettyG_test.go ├── bootstrap.go ├── channel.go ├── codec.go ├── codec_line.go ├── codec_line_test.go ├── codec_string.go ├── context.go ├── handler.go ├── pipeline.go └── reactor.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | 6 | script: 7 | - go install -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nettyG 2 | [![Build Status](https://travis-ci.org/ejunjsh/nettyG.svg?branch=master)](https://travis-ci.org/ejunjsh/nettyG) 3 | 4 | a simple netty-like network framework. 5 | ````go 6 | NewBootstrap().Handler(func(channel *Channel) { 7 | channel.Pipeline(). 8 | AddLast(NewLineCodec("\r\n\r\n")). 9 | AddLast(NewStringCodec()). 10 | AddLast(ChannelActiveFunc(func(context *HandlerContext) error { 11 | context.WriteAndFlush("hello nettyG") 12 | return nil 13 | })).AddLast(ChannelReadFunc(func(context *HandlerContext, data interface{}) error { 14 | if d,ok:=data.(string);ok{ 15 | context.Write(d) 16 | } 17 | return nil 18 | })) 19 | }).RunServer("tcp",":8981") 20 | ```` 21 | 22 | benchmark 23 | nettyG vs go standard lib 24 | ````bash 25 | $ cd bench 26 | $ go test -bench . 27 | tcp listen on :8981 28 | BenchmarkGostd-8 10000 123746 ns/op 29 | BenchmarkNettyG-8 10000 134753 ns/op 30 | PASS 31 | ok github.com/ejunjsh/nettyG 2.623s 32 | 33 | ```` 34 | -------------------------------------------------------------------------------- /bench/gostd_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func init() { 11 | go func() { 12 | l,err:=net.Listen("tcp",":9988") 13 | 14 | if err!=nil{ 15 | return 16 | } 17 | 18 | defer l.Close() 19 | 20 | for { 21 | conn, err := l.Accept() 22 | if err != nil { 23 | continue 24 | } 25 | go func() { 26 | conn.Write([]byte("hello gostd")) 27 | b :=make([]byte,1024) 28 | conn.Read(b) 29 | conn.Write([]byte("Acknowledge")) 30 | conn.Close() 31 | }() 32 | } 33 | }() 34 | } 35 | 36 | 37 | 38 | func BenchmarkGostd(b *testing.B) { 39 | for i := 0; i < b.N; i++ { 40 | conn, err := net.Dial("tcp", ":9988") 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 43 | os.Exit(1) 44 | } 45 | 46 | 47 | b :=make([]byte,1024) 48 | conn.Read(b) 49 | conn.Write([]byte("hello")) 50 | conn.Read(b) 51 | conn.Close() 52 | } 53 | } -------------------------------------------------------------------------------- /bench/nettyG_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | "os" 7 | "net" 8 | "github.com/ejunjsh/nettyG" 9 | ) 10 | 11 | 12 | func init(){ 13 | go func() { 14 | nettyG.NewBootstrap().Handler(func(channel *nettyG.Channel) { 15 | channel.Pipeline(). 16 | AddLast(nettyG.NewStringCodec()). 17 | AddLast(nettyG.ChannelActiveFunc(func(context *nettyG.HandlerContext) error { 18 | context.WriteAndFlush("hello nettyG") 19 | return nil 20 | })).AddLast(nettyG.ChannelReadFunc(func(context *nettyG.HandlerContext, data interface{}) error { 21 | if _,ok:=data.(string);ok{ 22 | context.WriteAndFlush("Acknowledge") 23 | context.Close() 24 | } 25 | return nil 26 | })) 27 | }).RunServer("tcp",":8981") 28 | }() 29 | } 30 | 31 | 32 | 33 | func BenchmarkNettyG(b *testing.B) { 34 | for i := 0; i < b.N; i++ { 35 | conn, err := net.Dial("tcp", ":8981") 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 38 | os.Exit(1) 39 | } 40 | 41 | 42 | b :=make([]byte,1024) 43 | conn.Read(b) 44 | conn.Write([]byte("hello")) 45 | conn.Read(b) 46 | conn.Close() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bootstrap.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | import ( 4 | "net" 5 | "fmt" 6 | ) 7 | 8 | type ChannelInitializer func(*Channel) 9 | 10 | type Bootstrap struct{ 11 | initHandler ChannelInitializer 12 | } 13 | 14 | func NewBootstrap() *Bootstrap{ 15 | return &Bootstrap{} 16 | } 17 | 18 | func (b *Bootstrap) initChannel(conn net.Conn) *Channel { 19 | p:=newPipeline() 20 | c:=newChannel(conn,p) 21 | p.chl=c 22 | b.initHandler(c) 23 | p.fireNextChannelActive() 24 | return c 25 | } 26 | func (b *Bootstrap) Handler(handler ChannelInitializer) *Bootstrap{ 27 | b.initHandler=handler 28 | return b 29 | } 30 | 31 | func (b *Bootstrap) RunServer(proto string,addr string){ 32 | fmt.Printf("%s listen on %s\n",proto,addr) 33 | l,err:=net.Listen(proto,addr) 34 | defer l.Close() 35 | if err!=nil{ 36 | return 37 | } 38 | 39 | for{ 40 | conn,err:=l.Accept() 41 | if err!=nil{ 42 | return 43 | } 44 | 45 | 46 | go handle(b.initChannel(conn)) 47 | } 48 | } 49 | 50 | 51 | 52 | //func (b *Bootstrap) RunServer(proto string,addr string){ 53 | // conn,err:= net.Dial(proto,addr) 54 | // if err!=nil{ 55 | // return 56 | // } 57 | // 58 | // go handle(b.initChannel(conn)) 59 | //} -------------------------------------------------------------------------------- /channel.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | import ( 4 | "net" 5 | "bytes" 6 | //"time" 7 | //"io" 8 | "sync" 9 | //"fmt" 10 | ) 11 | 12 | type Channel struct { 13 | conn net.Conn 14 | pipeline *Pipeline 15 | writebuffer *bytes.Buffer 16 | readbuffer *bytes.Buffer 17 | flushC chan bool 18 | closeC chan struct{} 19 | writeLocker sync.Mutex 20 | } 21 | 22 | func newChannel(conn net.Conn,pipeline *Pipeline) *Channel { 23 | chl:= &Channel{conn,nil,bytes.NewBuffer(make([]byte,0,1024)), 24 | bytes.NewBuffer(make([]byte,0,1024)),make(chan bool,1),make(chan struct{}),sync.Mutex{}} 25 | chl.pipeline=pipeline 26 | return chl 27 | } 28 | 29 | func (c *Channel) Pipeline() *Pipeline{ 30 | return c.pipeline 31 | } 32 | 33 | func (c *Channel) runReadEventLoop(){ 34 | //go func() { 35 | for{ 36 | b:=make([]byte,1024) 37 | n,err:=c.conn.Read(b) 38 | if err!=nil{ 39 | //c.closeC<- struct{}{} 40 | return 41 | } 42 | c.readbuffer.Write(b[:n]) 43 | c.pipeline.fireNextChannelRead(c.readbuffer) 44 | } 45 | //}() 46 | } 47 | 48 | //func (c *Channel) runWriteEventLoop(){ 49 | // go func() { 50 | // t:=time.Tick(time.Millisecond) 51 | // for{ 52 | // select { 53 | // case <-c.flushC: 54 | // c.writeLocker.Lock() 55 | // io.Copy(c.conn,c.writebuffer) 56 | // c.writeLocker.Unlock() 57 | // case <-c.closeC: 58 | // return 59 | // case <-t: 60 | // c.writeLocker.Lock() 61 | // _,err:=io.Copy(c.conn,c.writebuffer) 62 | // if err!=nil{ 63 | // fmt.Println(err) 64 | // return 65 | // } 66 | // t=time.Tick(time.Millisecond) 67 | // c.writeLocker.Unlock() 68 | // } 69 | // } 70 | // }() 71 | //} 72 | 73 | //func (c *Channel) Write(b []byte){ 74 | // c.writeLocker.Lock() 75 | // defer c.writeLocker.Unlock() 76 | // c.writebuffer.Write(b) 77 | //} 78 | 79 | func (c *Channel) Write(b []byte){ 80 | c.conn.Write(b) 81 | } 82 | 83 | func (c *Channel) Close() error{ 84 | return c.conn.Close() 85 | } 86 | 87 | func (c *Channel) Flush() error{ 88 | //c.flushC<-true 89 | return nil 90 | } -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | type Codec struct { 4 | 5 | } 6 | 7 | func (c *Codec) ChannelActive(h *HandlerContext) error{ 8 | h.FireChannelActive() 9 | return nil 10 | } 11 | 12 | func (c *Codec) Close(h *HandlerContext) error{ 13 | h.Close() 14 | return nil 15 | } 16 | 17 | func (c *Codec) Flush(h *HandlerContext) error{ 18 | h.Flush() 19 | return nil 20 | } 21 | 22 | func (c *Codec) ErrorCaught(h *HandlerContext,err error){ 23 | 24 | } -------------------------------------------------------------------------------- /codec_line.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | type LineCodec struct { 9 | delimiter string 10 | Codec 11 | } 12 | 13 | func NewLineCodec(delimiter string) *LineCodec { 14 | return &LineCodec{delimiter:delimiter} 15 | } 16 | 17 | func (l *LineCodec) ChannelRead(c *HandlerContext,data interface{}) error{ 18 | if buffer,ok:=data.(*bytes.Buffer);ok{ 19 | for { 20 | s := buffer.String() 21 | r := []rune(s) 22 | index := strings.Index(s, l.delimiter) 23 | if index > 0 { 24 | r = r[0:index] 25 | b := []byte(string(r)) 26 | buffer.Next(len(b)) 27 | buffer.Next(len(l.delimiter)) 28 | c.FireChannelRead(b) 29 | } else { 30 | break 31 | } 32 | } 33 | 34 | } 35 | return nil 36 | } 37 | 38 | func (l *LineCodec) Write(c *HandlerContext,data interface{}) error{ 39 | if b,ok:=data.([]byte);ok{ 40 | c.Write(append(b, []byte(l.delimiter)...)) 41 | } 42 | return nil 43 | } 44 | 45 | -------------------------------------------------------------------------------- /codec_line_test.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | import ( 4 | "testing" 5 | "net" 6 | "fmt" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func TestNewLineCodec(t *testing.T) { 12 | go func() { 13 | NewBootstrap().Handler(func(channel *Channel) { 14 | channel.Pipeline(). 15 | AddLast(NewLineCodec("\r\n\r\n")). 16 | AddLast(NewStringCodec()). 17 | AddLast(ChannelActiveFunc(func(context *HandlerContext) error { 18 | context.WriteAndFlush("hello nettyG") 19 | return nil 20 | })).AddLast(ChannelReadFunc(func(context *HandlerContext, data interface{}) error { 21 | if d,ok:=data.(string);ok{ 22 | context.Write(d) 23 | } 24 | return nil 25 | })) 26 | }).RunServer("tcp",":8981") 27 | }() 28 | time.Sleep(2*time.Second) 29 | conn, err := net.Dial("tcp", ":8981") 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 32 | os.Exit(1) 33 | } 34 | 35 | 36 | b :=make([]byte,1024) 37 | n,_:=conn.Read(b) 38 | conn.Write([]byte("hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello\r\n\r\nworld\r\n\r\n")) 39 | n,_=conn.Read(b) 40 | fmt.Print(string(b[0:n])) 41 | n,_=conn.Read(b) 42 | fmt.Print(string(b[0:n])) 43 | conn.Close() 44 | time.Sleep(1000*time.Second) 45 | } 46 | -------------------------------------------------------------------------------- /codec_string.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | type StringCodec struct { 4 | Codec 5 | } 6 | 7 | 8 | func NewStringCodec() *StringCodec { 9 | return &StringCodec{} 10 | } 11 | 12 | func (l *StringCodec) ChannelRead(c *HandlerContext,data interface{}) error{ 13 | if b,ok:=data.([]byte);ok{ 14 | c.FireChannelRead(string(b)) 15 | } 16 | return nil 17 | } 18 | 19 | 20 | func (l *StringCodec) Write(c *HandlerContext,data interface{}) error{ 21 | if s,ok:=data.(string);ok{ 22 | c.Write([]byte(s)) 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | import "io/ioutil" 4 | 5 | type HandlerContext struct { 6 | p *Pipeline 7 | next *HandlerContext 8 | prev *HandlerContext 9 | handler Handler 10 | } 11 | 12 | func newHandlerContext(p *Pipeline,handler Handler) *HandlerContext { 13 | return &HandlerContext{p:p,handler:handler} 14 | } 15 | 16 | func (h *HandlerContext) Write(data interface{}){ 17 | hc:=h.findNextOutbound() 18 | if hc!=nil{ 19 | hc.handler.(OutboundHandler).Write(hc,data) 20 | } 21 | } 22 | 23 | func (h *HandlerContext) Flush(){ 24 | hc:=h.findNextOutbound() 25 | if hc!=nil{ 26 | hc.handler.(OutboundHandler).Flush(hc) 27 | } 28 | } 29 | 30 | func (h *HandlerContext) WriteAndFlush(data interface{}){ 31 | h.Write(data) 32 | h.Flush() 33 | } 34 | 35 | func (h *HandlerContext) Close(){ 36 | hc:=h.findNextOutbound() 37 | if hc!=nil{ 38 | hc.handler.(OutboundHandler).Close(hc) 39 | } 40 | } 41 | 42 | func (h *HandlerContext) FireChannelRead(data interface{}){ 43 | hc:=h.findNextInbound() 44 | if hc!=nil{ 45 | hc.handler.(InboundHandler).ChannelRead(hc,data) 46 | } 47 | } 48 | 49 | func (h *HandlerContext) FireChannelActive(){ 50 | hc:=h.findNextInbound() 51 | if hc!=nil{ 52 | hc.handler.(InboundHandler).ChannelActive(hc) 53 | } 54 | } 55 | 56 | func (h *HandlerContext) isInbound() bool{ 57 | _,ok:= h.handler.(InboundHandler) 58 | return ok 59 | } 60 | 61 | func (h *HandlerContext) isOutbound() bool{ 62 | _,ok:= h.handler.(OutboundHandler) 63 | return ok 64 | } 65 | 66 | func (h *HandlerContext) findNextInbound() *HandlerContext{ 67 | next:=h 68 | for{ 69 | next=next.next 70 | 71 | if next.isInbound(){ 72 | return next 73 | } 74 | } 75 | } 76 | 77 | func (h *HandlerContext) findNextOutbound() *HandlerContext{ 78 | prev:=h 79 | for{ 80 | 81 | prev=prev.prev 82 | 83 | if prev.isOutbound(){ 84 | return prev 85 | } 86 | } 87 | } 88 | 89 | 90 | func (h *HandlerContext) WriteToReadBuffer(b []byte){ 91 | h.p.chl.readbuffer.Write(b) 92 | } 93 | 94 | func (h *HandlerContext) ResetReadBuffer(){ 95 | h.p.chl.readbuffer.Reset() 96 | } 97 | 98 | func (h *HandlerContext) ReadAllReadBuffer() ([]byte,error){ 99 | return ioutil.ReadAll(h.p.chl.readbuffer) 100 | } 101 | 102 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | 4 | 5 | type InboundHandler interface { 6 | ChannelRead(c *HandlerContext,data interface{}) error 7 | ChannelActive(c *HandlerContext) error 8 | Handler 9 | } 10 | 11 | type OutboundHandler interface { 12 | Write(c *HandlerContext,data interface{}) error 13 | Close(c *HandlerContext) error 14 | Flush(c *HandlerContext) error 15 | Handler 16 | } 17 | 18 | type Handler interface { 19 | ErrorCaught(c *HandlerContext,err error) 20 | } 21 | 22 | 23 | type inbound struct { 24 | read func (*HandlerContext, interface{}) error 25 | active func(*HandlerContext) error 26 | } 27 | 28 | func (in *inbound) ChannelRead(c *HandlerContext,data interface{}) error{ 29 | if in.read==nil{ 30 | c.FireChannelRead(data) 31 | return nil 32 | } 33 | return in.read(c,data) 34 | } 35 | 36 | func (in *inbound) ChannelActive(c *HandlerContext) error{ 37 | if in.active==nil{ 38 | c.FireChannelActive() 39 | return nil 40 | } 41 | return in.active(c) 42 | } 43 | 44 | func (in *inbound) ErrorCaught(c *HandlerContext,err error){ 45 | 46 | } 47 | 48 | func ChannelReadFunc(read func (*HandlerContext, interface{}) error) Handler{ 49 | return &inbound{read:read} 50 | } 51 | 52 | func ChannelActiveFunc(active func (*HandlerContext) error) Handler{ 53 | return &inbound{active:active} 54 | } 55 | 56 | type outbound struct { 57 | write func(*HandlerContext, interface{}) error 58 | flush func(*HandlerContext) error 59 | close func(*HandlerContext) error 60 | } 61 | 62 | func (out *outbound) Write(c *HandlerContext,data interface{}) error{ 63 | if out.write==nil{ 64 | c.Write(data) 65 | return nil 66 | } 67 | return out.write(c,data) 68 | } 69 | 70 | func (out *outbound) Flush(c *HandlerContext) error{ 71 | if out.flush==nil{ 72 | c.Flush() 73 | return nil 74 | } 75 | return out.flush(c) 76 | } 77 | 78 | func (out *outbound) Close(c *HandlerContext) error{ 79 | if out.close==nil{ 80 | c.Close() 81 | return nil 82 | } 83 | return out.close(c) 84 | } 85 | 86 | func (out *outbound) ErrorCaught(c *HandlerContext,err error){ 87 | 88 | } 89 | 90 | func WriteFunc(write func (*HandlerContext,interface{}) error) Handler{ 91 | return &outbound{write:write} 92 | } 93 | 94 | func CloseFunc(close func (*HandlerContext) error) Handler{ 95 | return &outbound{close:close} 96 | } 97 | 98 | func FlushFunc(flush func (*HandlerContext) error) Handler{ 99 | return &outbound{flush:flush} 100 | } -------------------------------------------------------------------------------- /pipeline.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | type Pipeline struct { 4 | head *HandlerContext 5 | tail *HandlerContext 6 | chl *Channel 7 | } 8 | 9 | func (p *Pipeline) fireNextChannelRead(data interface{}){ 10 | p.head.FireChannelRead(data) 11 | } 12 | 13 | func (p *Pipeline) fireNextChannelActive(){ 14 | p.head.FireChannelActive() 15 | } 16 | 17 | 18 | 19 | func (p *Pipeline) AddLast(handler Handler) *Pipeline{ 20 | prev:=p.tail.prev 21 | newH:=newHandlerContext(p,handler) 22 | newH.prev=prev 23 | newH.next=p.tail 24 | prev.next=newH 25 | p.tail.prev=newH 26 | return p 27 | } 28 | 29 | func (p *Pipeline) AddFirst(handler Handler) *Pipeline{ 30 | next:=p.head.next 31 | newH:=newHandlerContext(p,handler) 32 | newH.prev=p.head 33 | newH.next=next 34 | p.head.next=newH 35 | next.prev=newH 36 | return p 37 | } 38 | 39 | type headHandler struct { 40 | 41 | } 42 | 43 | func (h *headHandler) ChannelRead(c *HandlerContext,data interface{}) error{ 44 | c.FireChannelRead(data) 45 | return nil 46 | } 47 | 48 | func (h *headHandler) ChannelActive(c *HandlerContext) error{ 49 | c.FireChannelActive() 50 | return nil 51 | } 52 | 53 | func (h *headHandler) ErrorCaught(c *HandlerContext,err error){ 54 | 55 | } 56 | 57 | func (h *headHandler) Write(c *HandlerContext,data interface{}) error{ 58 | b,ok:=data.([]byte) 59 | if ok{ 60 | c.p.chl.Write(b) 61 | } 62 | return nil 63 | } 64 | 65 | func (h *headHandler) Close(c *HandlerContext) error{ 66 | return c.p.chl.Close() 67 | } 68 | 69 | func (h *headHandler) Flush(c *HandlerContext) error{ 70 | return c.p.chl.Flush() 71 | } 72 | 73 | type tailHandler struct { 74 | 75 | } 76 | 77 | func (t *tailHandler) ChannelRead(c *HandlerContext,data interface{}) error{ 78 | return nil 79 | } 80 | 81 | func (t *tailHandler) ChannelActive(c *HandlerContext) error{ 82 | return nil 83 | } 84 | 85 | func (t *tailHandler) ErrorCaught(c *HandlerContext,err error){ 86 | 87 | } 88 | 89 | func (t *tailHandler) Write(c *HandlerContext,data interface{}) error{ 90 | c.Write(data) 91 | return nil 92 | } 93 | 94 | func (t *tailHandler) Close(c *HandlerContext) error{ 95 | c.Close() 96 | return nil 97 | } 98 | 99 | func (t *tailHandler) Flush(c *HandlerContext) error{ 100 | c.Flush() 101 | return nil 102 | } 103 | 104 | 105 | 106 | func newPipeline() *Pipeline{ 107 | p:=&Pipeline{} 108 | p.tail=&HandlerContext{p,nil,nil,&tailHandler{}} 109 | p.head=&HandlerContext{p,nil,nil,&headHandler{}} 110 | p.head.next=p.tail 111 | p.tail.prev=p.head 112 | return p 113 | } 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /reactor.go: -------------------------------------------------------------------------------- 1 | package nettyG 2 | 3 | 4 | 5 | func handle(chl *Channel){ 6 | chl.runReadEventLoop() 7 | //chl.runWriteEventLoop() 8 | } --------------------------------------------------------------------------------