From 48deaa5f57568f779a65ea54d0ea4f8dd8467b31 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 20 May 2019 08:59:16 +0800 Subject: [PATCH 01/24] improving package feature for gtcp --- g/crypto/gcrc32/gcrc32.go | 12 +++- g/net/gtcp/gtcp_conn.go | 2 +- g/net/gtcp/gtcp_conn_pkg.go | 132 ++++++++++++++++++++++++------------ g/net/gtcp/gtcp_func.go | 4 +- g/net/gtcp/gtcp_server.go | 111 ++++++++++++++++++++---------- 5 files changed, 179 insertions(+), 82 deletions(-) diff --git a/g/crypto/gcrc32/gcrc32.go b/g/crypto/gcrc32/gcrc32.go index 8ee9f1a99..60ea5f7ff 100644 --- a/g/crypto/gcrc32/gcrc32.go +++ b/g/crypto/gcrc32/gcrc32.go @@ -4,17 +4,25 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gcrc32 provides useful API for CRC32 encryption/decryption algorithms. +// Package gcrc32 provides API for CRC32 encryption/decryption algorithm. package gcrc32 import ( "hash/crc32" ) +// Encrypt encrypts bytes using CRC32 algorithm. +func Encrypt(v []byte) uint32 { + return crc32.ChecksumIEEE(v) +} + +// EncryptString encrypts string using CRC32 algorithm. func EncryptString(v string) uint32 { return crc32.ChecksumIEEE([]byte(v)) } +// Alias of Encrypt. +// Deprecated. func EncryptBytes(v []byte) uint32 { - return crc32.ChecksumIEEE(v) + return Encrypt(v) } diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index a608c848c..e77b4554d 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -87,7 +87,7 @@ func (c *Conn) Send(data []byte, retry...Retry) error { // 获取数据,指定读取的数据长度(length < 1表示获取所有可读数据),以及重试策略(retry) // 需要注意: // 1、往往在socket通信中需要指定固定的数据结构,并在设定对应的长度字段,并在读取数据时便于区分包大小; -// 2、当length < 1时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现非完整的包情况),因此需要解析端注意解析策略; +// 2、当length < 1时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现断包情况),因此需要解析端注意解析策略; func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { var err error // 读取错误 var size int // 读取长度 diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index cd0a8ee53..2d88d2d1f 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -10,84 +10,130 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/gogf/gf/g/crypto/gcrc32" "time" ) const ( - // 允许最大的简单协议包大小(byte), 15MB - PKG_MAX_SIZE = 0xFFFFFF - // 消息包头大小: "总长度"3字节+"校验码"4字节 - PKG_HEADER_SIZE = 7 + // 默认允许最大的简单协议包大小(byte), 65535 byte + gPKG_MAX_SIZE = 65535 ) -// 根据简单协议发送数据包。 -// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。 -// 注意: -// 1. "总长度"包含自身3字节及"校验码"4字节。 -// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。 -func (c *Conn) SendPkg(data []byte, retry...Retry) error { - length := uint32(len(data)) - if length > PKG_MAX_SIZE - PKG_HEADER_SIZE { - return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE)) +// 数据读取选项 +type Option struct { + MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte + Secret []byte // (可选)安全通信密钥 + Retry Retry // 失败重试 +} + +// getPkgOption wraps and returns the option. +// If no option given, it returns a new option with default value. +func getPkgOption(option...Option) (*Option, error) { + pkgOption := Option{} + if len(option) > 0 { + pkgOption = option[0] } - buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data)) - copy(buffer[PKG_HEADER_SIZE + 1 : ], data) - binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length) - binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data)) - //fmt.Println("SendPkg:", buffer[1:]) - return c.Send(buffer[1:], retry...) + if pkgOption.MaxSize == 0 { + pkgOption.MaxSize = gPKG_MAX_SIZE + } else if pkgOption.MaxSize > 0xFFFFFF { + return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF) + } + return &pkgOption, nil +} + +// 根据简单协议发送数据包。 +// 简单协议数据格式:总长度(24bit)|校验位(32bit,可选)|数据(变长)。 +// 注意: +// 1. "总长度"包含自身3字节及"校验位"4字节(可选)。 +// 2. 当Secret有提供时,"校验位"才会存在,否则该字段为空。 +// 3. "校验位"提供简单的数据完整性及防篡改校验,默认没有开启。 +// 4. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此这里最后返回的buffer使用了buffer[1:]。 +func (c *Conn) SendPkg(data []byte, option...Option) error { + pkgOption, err := getPkgOption(option...) + if err != nil { + return err + } + headerSize := 3 + if len(pkgOption.Secret) > 0 { + headerSize = 7 + } + length := len(data) + if length > pkgOption.MaxSize - headerSize { + return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_SIZE - headerSize)) + } + + buffer := make([]byte, headerSize + 1 + len(data)) + copy(buffer[headerSize + 1 : ], data) + binary.BigEndian.PutUint32(buffer[0 : ], uint32(headerSize + length)) + if len(pkgOption.Secret) > 0 { + binary.BigEndian.PutUint32(buffer[4 : ], gcrc32.Encrypt(append(data, pkgOption.Secret...))) + } + if pkgOption.Retry.Count > 0 { + c.Send(buffer[1:], pkgOption.Retry) + } + return c.Send(buffer[1:]) } // 简单协议: 带超时时间的数据发送 -func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { +func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...Option) error { c.SetSendDeadline(time.Now().Add(timeout)) defer c.SetSendDeadline(time.Time{}) - return c.SendPkg(data, retry...) + return c.SendPkg(data, option...) } // 简单协议: 发送数据并等待接收返回数据 -func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkg(retry...) +func (c *Conn) SendRecvPkg(data []byte, option...Option) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkg(option...) } else { return nil, err } } // 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间) -func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkgWithTimeout(timeout, retry...) +func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...Option) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkgWithTimeout(timeout, option...) } else { return nil, err } } // 简单协议: 获取一个数据包。 -func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { +func (c *Conn) RecvPkg(option...Option) (result []byte, err error) { var temp []byte - var length uint32 + var length int + pkgOption, err := getPkgOption(option...) + if err != nil { + return nil, err + } + headerSize := 3 + if len(pkgOption.Secret) > 0 { + headerSize = 7 + } for { // 先根据对象的缓冲区数据进行计算 for { - if len(c.buffer) >= PKG_HEADER_SIZE { + if len(c.buffer) >= headerSize { // 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 - length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}) - // 解析的大小是否符合规范 - if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE { - c.buffer = c.buffer[1:] - continue + length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})) + // 解析的大小是否符合规范,清空从该连接接收到的所有数据包 + if length <= 0 || length + headerSize > pkgOption.MaxSize { + c.buffer = c.buffer[:0] + return nil, fmt.Errorf(`invalid package size %d`, length) } // 不满足包大小,需要继续读取 - if uint32(len(c.buffer)) < length { + if len(c.buffer) < length { break } - // 数据校验 - if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) { - c.buffer = c.buffer[1:] - continue + // 数据校验,如果失败,丢弃该数据包 + receivedCrc32 := binary.BigEndian.Uint32(c.buffer[3 : headerSize]) + calculatedCrc32 := gcrc32.Encrypt(c.buffer[headerSize : length]) + if receivedCrc32 != calculatedCrc32 { + c.buffer = c.buffer[length: ] + return nil, fmt.Errorf(`data CRC32 validates failed, received %d, caculated %d`, receivedCrc32, calculatedCrc32) } - result = c.buffer[PKG_HEADER_SIZE : length] + result = c.buffer[headerSize : length] c.buffer = c.buffer[length: ] return } else { @@ -95,7 +141,7 @@ func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { } } // 读取系统socket缓冲区的完整数据 - temp, err = c.Recv(-1, retry...) + temp, err = c.Recv(-1, option...) if err != nil { break } @@ -108,7 +154,7 @@ func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { } // 简单协议: 带超时时间的消息包获取 -func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { +func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...Option) ([]byte, error) { c.SetRecvDeadline(time.Now().Add(timeout)) defer c.SetRecvDeadline(time.Time{}) return c.RecvPkg(retry...) diff --git a/g/net/gtcp/gtcp_func.go b/g/net/gtcp/gtcp_func.go index e4aacd6ef..cbc82106a 100644 --- a/g/net/gtcp/gtcp_func.go +++ b/g/net/gtcp/gtcp_func.go @@ -12,8 +12,8 @@ import ( ) const ( - gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔 - gDEFAULT_READ_BUFFER_SIZE = 1024 // 默认数据读取缓冲区大小 + gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔 + gDEFAULT_READ_BUFFER_SIZE = 128 // (byte)默认数据读取缓冲区大小 ) type Retry struct { diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index 7962d9240..c6e3df5be 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -8,48 +8,62 @@ package gtcp import ( - "errors" - "github.com/gogf/gf/g/os/glog" - "net" - "github.com/gogf/gf/g/container/gmap" - "github.com/gogf/gf/g/util/gconv" + "crypto/rand" + "crypto/tls" + "errors" + "github.com/gogf/gf/g/container/gmap" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "net" + "time" ) const ( gDEFAULT_SERVER = "default" ) -// tcp server结构体 +// TCP Server. type Server struct { address string handler func (*Conn) + tlsConfig *tls.Config } -// Server表,用以存储和检索名称与Server对象之间的关联关系 +// Map for name to server, for singleton purpose. var serverMapping = gmap.NewStrAnyMap() -// 获取/创建一个空配置的TCP Server -// 单例模式,请保证name的唯一性 +// GetServer returns the TCP server with specified , +// or it returns a new normal TCP server named if it does not exist. +// The parameter is used to specify the TCP server func GetServer(name...interface{}) *Server { serverName := gDEFAULT_SERVER if len(name) > 0 { serverName = gconv.String(name[0]) } - if s := serverMapping.Get(serverName); s != nil { - return s.(*Server) + return serverMapping.GetOrSetFuncLock(serverName, func() interface{} { + return NewServer("", nil) + }).(*Server) +} + +// NewServer creates and returns a new normal TCP server. +// The param is optional, which is used to specify the instance name of the server. +func NewServer(address string, handler func (*Conn), name...string) *Server { + s := &Server{ + address : address, + handler : handler, + } + if len(name) > 0 { + serverMapping.Set(name[0], s) } - s := NewServer("", nil) - serverMapping.Set(serverName, s) return s } -// 创建一个tcp server对象,并且可以选择指定一个单例名字 -func NewServer(address string, handler func (*Conn), names...string) *Server { - s := &Server{address, handler} - if len(names) > 0 { - serverMapping.Set(names[0], s) - } - return s +// NewTlsServer creates and returns a new TCP server with TLS support. +// The param is optional, which is used to specify the instance name of the server. +func NewTLSServer(address, crtFile, keyFile string, handler func (*Conn), name...string) *Server { + s := NewServer(address, handler, name...) + s.SetTLSKeyCrt(crtFile, keyFile) + return s } // 设置参数 - address @@ -62,24 +76,53 @@ func (s *Server) SetHandler (handler func (*Conn)) { s.handler = handler } -// 执行监听 -func (s *Server) Run() error { +// SetTlsKeyCrt sets the certificate and key file for TLS configuration of server. +func (s *Server) SetTLSKeyCrt (crtFile, keyFile string) error { + crt, err := tls.LoadX509KeyPair(crtFile,keyFile) + if err != nil { + return err + } + s.tlsConfig = &tls.Config{} + s.tlsConfig.Certificates = []tls.Certificate{crt} + s.tlsConfig.Time = time.Now + s.tlsConfig.Rand = rand.Reader + return nil +} + +// SetTlsConfig sets the TLS configuration of server. +func (s *Server) SetTLSConfig(tlsConfig *tls.Config) { + s.tlsConfig = tlsConfig +} + +// Run starts running the TCP Server. +func (s *Server) Run() (err error) { if s.handler == nil { - err := errors.New("start running failed: socket handler not defined") + err = errors.New("start running failed: socket handler not defined") glog.Error(err) - return err + return } - addr, err := net.ResolveTCPAddr("tcp", s.address) - if err != nil { - glog.Error(err) - return err + listen := net.Listener(nil) + if s.tlsConfig != nil { + // TLS Server + listen, err = tls.Listen("tcp", s.address, s.tlsConfig) + if err != nil { + glog.Error(err) + return + } + } else { + // Normal Server + addr, err := net.ResolveTCPAddr("tcp", s.address) + if err != nil { + glog.Error(err) + return err + } + listen, err = net.ListenTCP("tcp", addr) + if err != nil { + glog.Error(err) + return err + } } - listen, err := net.ListenTCP("tcp", addr) - if err != nil { - glog.Error(err) - return err - } - for { + for { if conn, err := listen.Accept(); err != nil { glog.Error(err) return err From e4f9e1000dcf6e50ae51cf33ecd27af92c6538c7 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 3 Jun 2019 16:39:45 +0800 Subject: [PATCH 02/24] fix issue in status handler for ghttp.Server --- g/net/ghttp/ghttp_response.go | 4 +++- geg/other/test.go | 30 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/g/net/ghttp/ghttp_response.go b/g/net/ghttp/ghttp_response.go index b756df942..2956b5769 100644 --- a/g/net/ghttp/ghttp_response.go +++ b/g/net/ghttp/ghttp_response.go @@ -134,7 +134,9 @@ func (r *Response) WriteStatus(status int, content...string) { // 状态码注册回调函数处理 if status != http.StatusOK { if f := r.request.Server.getStatusHandler(status, r.request); f != nil { - f(r.request) + r.Server.niceCallFunc(func() { + f(r.request) + }) // 防止多次设置(http: multiple response.WriteHeader calls) if r.Status == 0 { r.WriteHeader(status) diff --git a/geg/other/test.go b/geg/other/test.go index 93993139e..41ec4db51 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,20 +1,28 @@ package main import ( - "encoding/json" "fmt" - "github.com/gogf/gf/g/encoding/gparser" + + "github.com/gogf/gf" + "github.com/gogf/gf/g/net/ghttp" + + "github.com/gogf/gf/g" ) -type User struct { - Id int `json:"id" gconv:"i_d"` -} - func main() { - user := User{100} - jsonBytes, _ := json.Marshal(user) - fmt.Println(string(jsonBytes)) + // fmt.Print(g.) + fmt.Println(gf.VERSION) + s := g.Server() - b, _ := gparser.VarToJson(user) - fmt.Println(string(b)) + s.BindHandler("/status/:status", func(r *ghttp.Request) { + r.Response.Write("woops, status ", r.Get("status"), " found") + }) + s.BindStatusHandler(404, func(r *ghttp.Request) { + r.Response.RedirectTo("/status/404") + }) + + s.SetErrorLogEnabled(true) + s.SetAccessLogEnabled(true) + s.SetPort(8890) + s.Run() } \ No newline at end of file From 85b104bafab9540f9a5f9403c0f2a18be85f4ff5 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 3 Jun 2019 16:59:33 +0800 Subject: [PATCH 03/24] update benchmark test cases for gregex --- g/text/gregex/gregex_z_bench_test.go | 58 +++++++++------------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/g/text/gregex/gregex_z_bench_test.go b/g/text/gregex/gregex_z_bench_test.go index db72f9d28..cd83dc1db 100644 --- a/g/text/gregex/gregex_z_bench_test.go +++ b/g/text/gregex/gregex_z_bench_test.go @@ -10,51 +10,29 @@ package gregex_test import ( "github.com/gogf/gf/g/text/gregex" - "testing" + "regexp" + "testing" ) -var pattern = `(.+):(\d+)` -var src = "johng.cn:80" -var replace = "johng.cn" +var pattern = `(\w+).+\-\-\s*(.+)` +var src = `GF is best! -- John` -func BenchmarkValidate(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.Validate(pattern) - } +func Benchmark_GF(b *testing.B) { + for i := 0; i < b.N; i++ { + gregex.IsMatchString(pattern, src) + } } -func BenchmarkIsMatch(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.IsMatch(pattern, []byte(src)) - } +func Benchmark_Compile(b *testing.B) { + var wcdRegexp = regexp.MustCompile(pattern) + for i := 0; i < b.N; i++ { + wcdRegexp.MatchString(src) + } } -func BenchmarkIsMatchString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.IsMatchString(pattern, src) - } -} - -func BenchmarkMatchString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.MatchString(pattern, src) - } -} - -func BenchmarkMatchAllString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.MatchAllString(pattern, src) - } -} - -func BenchmarkReplace(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.Replace(pattern, []byte(replace), []byte(src)) - } -} - -func BenchmarkReplaceString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.ReplaceString(pattern, replace, src) - } +func Benchmark_Compile_Actual(b *testing.B) { + for i := 0; i < b.N; i++ { + wcdRegexp := regexp.MustCompile(pattern) + wcdRegexp.MatchString(src) + } } From 4c5d2839bd4023d1d941aa3b425a4210fe26a13d Mon Sep 17 00:00:00 2001 From: John Date: Mon, 3 Jun 2019 20:34:19 +0800 Subject: [PATCH 04/24] donators updates --- DONATOR.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/DONATOR.MD b/DONATOR.MD index 64b845e96..892de6304 100644 --- a/DONATOR.MD +++ b/DONATOR.MD @@ -11,6 +11,7 @@ |[wxkj](https://gitee.com/wxkj)|wechat|¥10.00 |[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 +|[arden](https://github.com/arden)|alipay|¥10.00 |潘兄|wechat|¥100.00 |土豆相公|alipay|¥66.60 |上海金保证网络科技|bank|¥2000.00 From 3a99c6e5f50f8924fe6b665c7adb5d9b1282ede9 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 3 Jun 2019 20:58:30 +0800 Subject: [PATCH 05/24] improve TryCatch function for gutil --- g/util/gutil/gutil.go | 6 ++++++ geg/other/try-catch.go | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go index b664df319..625214bac 100644 --- a/g/util/gutil/gutil.go +++ b/g/util/gutil/gutil.go @@ -69,11 +69,17 @@ func Throw(exception interface{}) { // TryCatch implements try...catch... logistics. func TryCatch(try func(), catch ... func(exception interface{})) { if len(catch) > 0 { + // If is given, it's used to handle the exception. defer func() { if e := recover(); e != nil { catch[0](e) } }() + } else { + // If no function passed, it filters the exception. + defer func() { + recover() + }() } try() } diff --git a/geg/other/try-catch.go b/geg/other/try-catch.go index 085b77bb0..a0bb032c7 100644 --- a/geg/other/try-catch.go +++ b/geg/other/try-catch.go @@ -7,9 +7,15 @@ import ( func main() { g.TryCatch(func() { - glog.Printfln("hello") + glog.Println("hello") g.Throw("exception") - glog.Printfln("world") + glog.Println("world") + }) + + g.TryCatch(func() { + glog.Println("hello") + g.Throw("exception") + glog.Println("world") }, func(exception interface{}) { glog.Error(exception) }) From 00a8ef63b6a1dfd3f66390c6efaa6fc81dca5a48 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 3 Jun 2019 23:53:59 +0800 Subject: [PATCH 06/24] improve gtcp --- g/crypto/gaes/gaes.go | 10 +-- g/net/gtcp/gtcp_conn.go | 12 ++- g/net/gtcp/gtcp_conn_pkg.go | 88 ++++++++----------- g/net/gtcp/gtcp_func_pkg.go | 16 ++-- g/net/gtcp/gtcp_pool_pkg.go | 30 +++---- g/net/gudp/gudp_conn.go | 13 ++- g/util/gconv/gconv.go | 9 ++ .../common/gtcp_common_client.go | 8 +- .../common/gtcp_common_server.go | 4 +- .../{gtcp_server_client1.go => gtcp_basic.go} | 2 +- ...p_server_client2.go => gtcp_empty_data.go} | 0 .../gtcp/pkg_operations/gtcp_pkg_option.go | 39 ++++++++ .../monitor/gtcp_monitor_server.go | 2 +- 13 files changed, 138 insertions(+), 95 deletions(-) rename geg/net/gtcp/pkg_operations/{gtcp_server_client1.go => gtcp_basic.go} (96%) rename geg/net/gtcp/pkg_operations/{gtcp_server_client2.go => gtcp_empty_data.go} (100%) create mode 100644 geg/net/gtcp/pkg_operations/gtcp_pkg_option.go diff --git a/g/crypto/gaes/gaes.go b/g/crypto/gaes/gaes.go index 746deba74..14f34f44b 100644 --- a/g/crypto/gaes/gaes.go +++ b/g/crypto/gaes/gaes.go @@ -25,18 +25,18 @@ func Encrypt(plainText []byte, key []byte, iv...[]byte) ([]byte, error) { return nil, err } blockSize := block.BlockSize() - plainText = PKCS5Padding(plainText, blockSize) - ivValue := ([]byte)(nil) + plainText = PKCS5Padding(plainText, blockSize) + ivValue := ([]byte)(nil) if len(iv) > 0 { ivValue = iv[0] } else { ivValue = []byte(ivDefValue) } blockMode := cipher.NewCBCEncrypter(block, ivValue) - ciphertext := make([]byte, len(plainText)) - blockMode.CryptBlocks(ciphertext, plainText) + cipherText := make([]byte, len(plainText)) + blockMode.CryptBlocks(cipherText, plainText) - return ciphertext, nil + return cipherText, nil } // AES解密, 使用CBC模式,注意key必须为16/24/32位长度,iv初始化向量为非必需参数 diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index e77b4554d..a3778f11c 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -84,10 +84,12 @@ func (c *Conn) Send(data []byte, retry...Retry) error { } } -// 获取数据,指定读取的数据长度(length < 1表示获取所有可读数据),以及重试策略(retry) +// 阻塞等待获取指定读取的数据长度,并给定重试策略。 +// // 需要注意: // 1、往往在socket通信中需要指定固定的数据结构,并在设定对应的长度字段,并在读取数据时便于区分包大小; -// 2、当length < 1时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现断包情况),因此需要解析端注意解析策略; +// 2、当length < 0时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现粘包/断包情况),因此需要解析端注意解析策略; +// 3、当length = 0时表示获取一次缓冲区的数据后立即返回; func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { var err error // 读取错误 var size int // 读取长度 @@ -106,7 +108,7 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { // 如果已经读取到数据(这点很关键,表明缓冲区已经有数据,剩下的操作就是将所有数据读取完毕), // 那么可以设置读取全部缓冲数据的超时时间;如果没有接收到任何数据,那么将会进入读取阻塞(或者自定义的超时阻塞); // 仅对读取全部缓冲区数据操作有效 - if length <= 0 && index > 0 { + if length < 0 && index > 0 { bufferWait = true c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) } @@ -155,6 +157,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } break } + // 只获取一次数据 + if length == 0 { + break + } } return buffer[:index], err } diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index 2d88d2d1f..f9d00cda9 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -10,31 +10,31 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/gogf/gf/g/crypto/gcrc32" "time" ) const ( // 默认允许最大的简单协议包大小(byte), 65535 byte - gPKG_MAX_SIZE = 65535 + gPKG_MAX_DATA_SIZE = 65535 + // 简单协议包头大小 + gPKG_HEADER_SIZE = 3 ) // 数据读取选项 -type Option struct { - MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte - Secret []byte // (可选)安全通信密钥 - Retry Retry // 失败重试 +type PkgOption struct { + MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte + Retry Retry // 失败重试 } -// getPkgOption wraps and returns the option. +// getPkgOption wraps and returns the PkgOption. // If no option given, it returns a new option with default value. -func getPkgOption(option...Option) (*Option, error) { - pkgOption := Option{} +func getPkgOption(option...PkgOption) (*PkgOption, error) { + pkgOption := PkgOption{} if len(option) > 0 { pkgOption = option[0] } if pkgOption.MaxSize == 0 { - pkgOption.MaxSize = gPKG_MAX_SIZE + pkgOption.MaxSize = gPKG_MAX_DATA_SIZE } else if pkgOption.MaxSize > 0xFFFFFF { return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF) } @@ -42,47 +42,40 @@ func getPkgOption(option...Option) (*Option, error) { } // 根据简单协议发送数据包。 -// 简单协议数据格式:总长度(24bit)|校验位(32bit,可选)|数据(变长)。 +// +// 简单协议数据格式:数据长度(24bit)|数据字段(变长)。 +// // 注意: -// 1. "总长度"包含自身3字节及"校验位"4字节(可选)。 -// 2. 当Secret有提供时,"校验位"才会存在,否则该字段为空。 -// 3. "校验位"提供简单的数据完整性及防篡改校验,默认没有开启。 -// 4. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此这里最后返回的buffer使用了buffer[1:]。 -func (c *Conn) SendPkg(data []byte, option...Option) error { +// 1. "数据长度"仅为"数据字段"的长度,不包含头信息的长度字段3字节。 +// 2. 由于"数据长度"为3字节,并且使用的BigEndian字节序,因此这里最后返回的buffer使用了buffer[1:]。 +func (c *Conn) SendPkg(data []byte, option...PkgOption) error { pkgOption, err := getPkgOption(option...) if err != nil { return err } - headerSize := 3 - if len(pkgOption.Secret) > 0 { - headerSize = 7 - } length := len(data) - if length > pkgOption.MaxSize - headerSize { - return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_SIZE - headerSize)) - } - - buffer := make([]byte, headerSize + 1 + len(data)) - copy(buffer[headerSize + 1 : ], data) - binary.BigEndian.PutUint32(buffer[0 : ], uint32(headerSize + length)) - if len(pkgOption.Secret) > 0 { - binary.BigEndian.PutUint32(buffer[4 : ], gcrc32.Encrypt(append(data, pkgOption.Secret...))) + if length > pkgOption.MaxSize { + return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_DATA_SIZE)) } + buffer := make([]byte, gPKG_HEADER_SIZE + 1 + len(data)) + binary.BigEndian.PutUint32(buffer[0 : ], uint32(length)) + copy(buffer[gPKG_HEADER_SIZE + 1 : ], data) if pkgOption.Retry.Count > 0 { c.Send(buffer[1:], pkgOption.Retry) } + //fmt.Println("SendPkg:", buffer[1:]) return c.Send(buffer[1:]) } // 简单协议: 带超时时间的数据发送 -func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...Option) error { +func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { c.SetSendDeadline(time.Now().Add(timeout)) defer c.SetSendDeadline(time.Time{}) return c.SendPkg(data, option...) } // 简单协议: 发送数据并等待接收返回数据 -func (c *Conn) SendRecvPkg(data []byte, option...Option) ([]byte, error) { +func (c *Conn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) { if err := c.SendPkg(data, option...); err == nil { return c.RecvPkg(option...) } else { @@ -91,7 +84,7 @@ func (c *Conn) SendRecvPkg(data []byte, option...Option) ([]byte, error) { } // 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间) -func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...Option) ([]byte, error) { +func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { if err := c.SendPkg(data, option...); err == nil { return c.RecvPkgWithTimeout(timeout, option...) } else { @@ -100,48 +93,37 @@ func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option } // 简单协议: 获取一个数据包。 -func (c *Conn) RecvPkg(option...Option) (result []byte, err error) { +func (c *Conn) RecvPkg(option...PkgOption) (result []byte, err error) { var temp []byte var length int pkgOption, err := getPkgOption(option...) if err != nil { return nil, err } - headerSize := 3 - if len(pkgOption.Secret) > 0 { - headerSize = 7 - } for { // 先根据对象的缓冲区数据进行计算 for { - if len(c.buffer) >= headerSize { - // 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 + if len(c.buffer) >= gPKG_HEADER_SIZE { + // 注意"数据长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})) // 解析的大小是否符合规范,清空从该连接接收到的所有数据包 - if length <= 0 || length + headerSize > pkgOption.MaxSize { + if length < 0 || length > pkgOption.MaxSize { c.buffer = c.buffer[:0] return nil, fmt.Errorf(`invalid package size %d`, length) } // 不满足包大小,需要继续读取 - if len(c.buffer) < length { + if len(c.buffer) < length + gPKG_HEADER_SIZE { break } - // 数据校验,如果失败,丢弃该数据包 - receivedCrc32 := binary.BigEndian.Uint32(c.buffer[3 : headerSize]) - calculatedCrc32 := gcrc32.Encrypt(c.buffer[headerSize : length]) - if receivedCrc32 != calculatedCrc32 { - c.buffer = c.buffer[length: ] - return nil, fmt.Errorf(`data CRC32 validates failed, received %d, caculated %d`, receivedCrc32, calculatedCrc32) - } - result = c.buffer[headerSize : length] - c.buffer = c.buffer[length: ] + result = c.buffer[gPKG_HEADER_SIZE : gPKG_HEADER_SIZE + length] + c.buffer = c.buffer[gPKG_HEADER_SIZE + length: ] return } else { break } } // 读取系统socket缓冲区的完整数据 - temp, err = c.Recv(-1, option...) + temp, err = c.Recv(0, pkgOption.Retry) if err != nil { break } @@ -154,8 +136,8 @@ func (c *Conn) RecvPkg(option...Option) (result []byte, err error) { } // 简单协议: 带超时时间的消息包获取 -func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...Option) ([]byte, error) { +func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { c.SetRecvDeadline(time.Now().Add(timeout)) defer c.SetRecvDeadline(time.Time{}) - return c.RecvPkg(retry...) + return c.RecvPkg(option...) } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_func_pkg.go b/g/net/gtcp/gtcp_func_pkg.go index 164d513cb..a5d50f234 100644 --- a/g/net/gtcp/gtcp_func_pkg.go +++ b/g/net/gtcp/gtcp_func_pkg.go @@ -9,41 +9,41 @@ package gtcp import "time" // 简单协议: (面向短链接)发送消息包 -func SendPkg(addr string, data []byte, retry...Retry) error { +func SendPkg(addr string, data []byte, option...PkgOption) error { conn, err := NewConn(addr) if err != nil { return err } defer conn.Close() - return conn.SendPkg(data, retry...) + return conn.SendPkg(data, option...) } // 简单协议: (面向短链接)发送数据并等待接收返回数据 -func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) { +func SendRecvPkg(addr string, data []byte, option...PkgOption) ([]byte, error) { conn, err := NewConn(addr) if err != nil { return nil, err } defer conn.Close() - return conn.SendRecvPkg(data, retry...) + return conn.SendRecvPkg(data, option...) } // 简单协议: (面向短链接)带超时时间的数据发送 -func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error { +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) error { conn, err := NewConn(addr) if err != nil { return err } defer conn.Close() - return conn.SendPkgWithTimeout(data, timeout, retry...) + return conn.SendPkgWithTimeout(data, timeout, option...) } // 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间) -func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { conn, err := NewConn(addr) if err != nil { return nil, err } defer conn.Close() - return conn.SendRecvPkgWithTimeout(data, timeout, retry...) + return conn.SendRecvPkgWithTimeout(data, timeout, option...) } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_pool_pkg.go b/g/net/gtcp/gtcp_pool_pkg.go index f22b60da6..08eb566d4 100644 --- a/g/net/gtcp/gtcp_pool_pkg.go +++ b/g/net/gtcp/gtcp_pool_pkg.go @@ -11,11 +11,11 @@ import ( ) // 简单协议: (方法覆盖)发送数据 -func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) { - if err = c.Conn.SendPkg(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN { +func (c *PoolConn) SendPkg(data []byte, option...PkgOption) (err error) { + if err = c.Conn.SendPkg(data, option...); err != nil && c.status == gCONN_STATUS_UNKNOWN { if v, e := c.pool.NewFunc(); e == nil { c.Conn = v.(*PoolConn).Conn - err = c.Conn.SendPkg(data, retry...) + err = c.Conn.SendPkg(data, option...) } else { err = e } @@ -29,8 +29,8 @@ func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) { } // 简单协议: (方法覆盖)接收数据 -func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) { - data, err := c.Conn.RecvPkg(retry...) +func (c *PoolConn) RecvPkg(option...PkgOption) ([]byte, error) { + data, err := c.Conn.RecvPkg(option...) if err != nil { c.status = gCONN_STATUS_ERROR } else { @@ -40,32 +40,32 @@ func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) { } // 简单协议: (方法覆盖)带超时时间的数据获取 -func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { +func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { c.SetRecvDeadline(time.Now().Add(timeout)) defer c.SetRecvDeadline(time.Time{}) - return c.RecvPkg(retry...) + return c.RecvPkg(option...) } // 简单协议: (方法覆盖)带超时时间的数据发送 -func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { +func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { c.SetSendDeadline(time.Now().Add(timeout)) defer c.SetSendDeadline(time.Time{}) - return c.SendPkg(data, retry...) + return c.SendPkg(data, option...) } // 简单协议: (方法覆盖)发送数据并等待接收返回数据 -func (c *PoolConn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkg(retry...) +func (c *PoolConn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkg(option...) } else { return nil, err } } // 简单协议: (方法覆盖)发送数据并等待接收返回数据(带返回超时等待时间) -func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkgWithTimeout(timeout, retry...) +func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkgWithTimeout(timeout, option...) } else { return nil, err } diff --git a/g/net/gudp/gudp_conn.go b/g/net/gudp/gudp_conn.go index 0d924734f..32bb952d7 100644 --- a/g/net/gudp/gudp_conn.go +++ b/g/net/gudp/gudp_conn.go @@ -88,8 +88,11 @@ func (c *Conn) Send(data []byte, retry...Retry) error { } } -// 接收数据. -// 注意:UDP协议存在消息边界,因此使用 length<=0 可以获取缓冲区所有消息包数据,即一个完整包。 +// 接收UDP协议数据. +// +// 注意事项: +// 1、UDP协议存在消息边界,因此使用 length < 0 可以获取缓冲区所有消息包数据,即一个完整包; +// 2、当length = 0时,表示获取当前的缓冲区数据,获取一次后立即返回; func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { var err error // 读取错误 var size int // 读取长度 @@ -105,7 +108,7 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } for { - if length <= 0 && index > 0 { + if length < 0 && index > 0 { bufferWait = true c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) } @@ -157,6 +160,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } break } + // 只获取一次数据 + if length == 0 { + break + } } return buffer[:index], err } diff --git a/g/util/gconv/gconv.go b/g/util/gconv/gconv.go index 6b517ca37..635645a6d 100644 --- a/g/util/gconv/gconv.go +++ b/g/util/gconv/gconv.go @@ -20,6 +20,11 @@ type apiString interface { String() string } +// Type assert api for Error(). +type apiError interface { + Error() string +} + var ( // Empty strings. emptyStringMap = map[string]struct{}{ @@ -143,6 +148,10 @@ func String(i interface{}) string { // If the variable implements the String() interface, // then use that interface to perform the conversion return f.String() + } else if f, ok := value.(apiError); ok { + // If the variable implements the Error() interface, + // then use that interface to perform the conversion + return f.Error() } else { // Finally we use json.Marshal to convert. jsonContent, _ := json.Marshal(value) diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go index e4d9ed49c..1669d8784 100644 --- a/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go @@ -40,21 +40,21 @@ func main() { case "doexit": onServerDoExit(conn, msg) case "heartbeat": onServerHeartBeat(conn, msg) default: - glog.Errorfln("invalid message: %v", msg) + glog.Errorf("invalid message: %v", msg) break } } } func onServerHello(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) } func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) } func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("exit command from [%s]", conn.RemoteAddr().String()) + glog.Printf("exit command from [%s]", conn.RemoteAddr().String()) conn.Close() } \ No newline at end of file diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go index 51bffe544..375c66adb 100644 --- a/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go @@ -36,10 +36,10 @@ func main() { } func onClientHello(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + glog.Printf("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) funcs.SendPkg(conn, msg.Act, "Nice to meet you!") } func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) } diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client1.go b/geg/net/gtcp/pkg_operations/gtcp_basic.go similarity index 96% rename from geg/net/gtcp/pkg_operations/gtcp_server_client1.go rename to geg/net/gtcp/pkg_operations/gtcp_basic.go index 867a7e404..f390e1224 100644 --- a/geg/net/gtcp/pkg_operations/gtcp_server_client1.go +++ b/geg/net/gtcp/pkg_operations/gtcp_basic.go @@ -32,7 +32,7 @@ func main() { defer conn.Close() for i := 0; i < 10000; i++ { if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { - glog.Error(err) + glog.Error(err.Error()) } time.Sleep(1*time.Second) } diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client2.go b/geg/net/gtcp/pkg_operations/gtcp_empty_data.go similarity index 100% rename from geg/net/gtcp/pkg_operations/gtcp_server_client2.go rename to geg/net/gtcp/pkg_operations/gtcp_empty_data.go diff --git a/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go b/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go new file mode 100644 index 000000000..31f603ae5 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg(gtcp.PkgOption{MaxSize : 1}) + if err != nil { + fmt.Println(err) + break + } + fmt.Println("RecvPkg:", string(data)) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} diff --git a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go index e91f1b34b..d8b2e2979 100644 --- a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go +++ b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go @@ -21,7 +21,7 @@ func main() { } info := &types.NodeInfo{} if err := json.Unmarshal(data, info); err != nil { - glog.Errorfln("invalid package structure: %s", err.Error()) + glog.Errorf("invalid package structure: %s", err.Error()) } else { glog.Println(info) conn.SendPkg([]byte("ok")) From 5d3c154b455a842a6c47362e86fccb18b2422cbb Mon Sep 17 00:00:00 2001 From: John Date: Tue, 4 Jun 2019 18:26:32 +0800 Subject: [PATCH 07/24] improve gtcp --- g/net/gtcp/gtcp_conn.go | 29 ++++++++++++++++------------- g/net/gtcp/gtcp_server.go | 4 ++-- g/net/gudp/gudp_conn.go | 32 +++++++++++++++++--------------- g/net/gudp/gudp_func.go | 8 ++++---- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index a3778f11c..a4729da0b 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -50,16 +50,14 @@ func NewConnByNetConn(conn net.Conn) *Conn { } // 关闭连接 -func (c *Conn) Close() { - c.conn.Close() +func (c *Conn) Close() error { + return c.conn.Close() } // 发送数据 func (c *Conn) Send(data []byte, retry...Retry) error { - length := 0 for { - n, err := c.conn.Write(data) - if err != nil { + if _, err := c.conn.Write(data); err != nil { // 链接已关闭 if err == io.EOF { return err @@ -76,10 +74,7 @@ func (c *Conn) Send(data []byte, retry...Retry) error { time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) } } else { - length += n - if length == len(data) { - return nil - } + return nil } } } @@ -110,7 +105,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { // 仅对读取全部缓冲区数据操作有效 if length < 0 && index > 0 { bufferWait = true - c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) + if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil { + return nil, err + } } size, err = c.reader.Read(buffer[index:]) if size > 0 { @@ -139,7 +136,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } // 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠) if bufferWait && isTimeout(err) { - c.conn.SetReadDeadline(c.recvDeadline) + if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil { + return nil, err + } err = nil break } @@ -190,14 +189,18 @@ func (c *Conn) RecvLine(retry...Retry) ([]byte, error) { // 带超时时间的数据获取 func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // 带超时时间的数据发送 func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index c6e3df5be..591fd8c89 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -66,12 +66,12 @@ func NewTLSServer(address, crtFile, keyFile string, handler func (*Conn), name.. return s } -// 设置参数 - address +// SetAddress sets the listening address for server. func (s *Server) SetAddress (address string) { s.address = address } -// 设置参数 - handler +// SetHandler sets the connection handler for server. func (s *Server) SetHandler (handler func (*Conn)) { s.handler = handler } diff --git a/g/net/gudp/gudp_conn.go b/g/net/gudp/gudp_conn.go index 32bb952d7..8c69d28dc 100644 --- a/g/net/gudp/gudp_conn.go +++ b/g/net/gudp/gudp_conn.go @@ -12,7 +12,7 @@ import ( "io" ) -// 封装的链接对象 +// 封装的UDP链接对象 type Conn struct { conn *net.UDPConn // 底层链接对象 raddr *net.UDPAddr // 远程地址 @@ -53,15 +53,12 @@ func NewConnByNetConn(udp *net.UDPConn) *Conn { } // 发送数据 -func (c *Conn) Send(data []byte, retry...Retry) error { - var err error - var size int - var length int +func (c *Conn) Send(data []byte, retry...Retry) (err error) { for { if c.raddr != nil { - size, err = c.conn.WriteToUDP(data, c.raddr) + _, err = c.conn.WriteToUDP(data, c.raddr) } else { - size, err = c.conn.Write(data) + _, err = c.conn.Write(data) } if err != nil { // 链接已关闭 @@ -80,10 +77,7 @@ func (c *Conn) Send(data []byte, retry...Retry) error { time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) } } else { - length += size - if length == len(data) { - return nil - } + return nil } } } @@ -110,7 +104,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { for { if length < 0 && index > 0 { bufferWait = true - c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) + if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil { + return nil, err + } } size, raddr, err = c.conn.ReadFromUDP(buffer[index:]) if err == nil { @@ -142,7 +138,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } // 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠) if bufferWait && isTimeout(err) { - c.conn.SetReadDeadline(c.recvDeadline) + if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil { + return nil, err + } err = nil break } @@ -179,14 +177,18 @@ func (c *Conn) SendRecv(data []byte, receive int, retry...Retry) ([]byte, error) // 带超时时间的数据获取 func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // 带超时时间的数据发送 func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gudp/gudp_func.go b/g/net/gudp/gudp_func.go index 859832637..afcca7d0e 100644 --- a/g/net/gudp/gudp_func.go +++ b/g/net/gudp/gudp_func.go @@ -22,18 +22,18 @@ func Checksum(buffer []byte) uint32 { // 创建标准库UDP链接操作对象 func NewNetConn(raddr string, laddr...string) (*net.UDPConn, error) { var err error - var rudpaddr, ludpaddr *net.UDPAddr - rudpaddr, err = net.ResolveUDPAddr("udp", raddr) + var remoteAddr, localAddr *net.UDPAddr + remoteAddr, err = net.ResolveUDPAddr("udp", raddr) if err != nil { return nil, err } if len(laddr) > 0 { - ludpaddr, err = net.ResolveUDPAddr("udp", laddr[0]) + localAddr, err = net.ResolveUDPAddr("udp", laddr[0]) if err != nil { return nil, err } } - conn, err := net.DialUDP("udp", ludpaddr, rudpaddr) + conn, err := net.DialUDP("udp", localAddr, remoteAddr) if err != nil { return nil, err } From 16d978dc5829c72c54b06a8b6de56745ff95bafa Mon Sep 17 00:00:00 2001 From: John Date: Tue, 4 Jun 2019 18:30:02 +0800 Subject: [PATCH 08/24] fix issue in gfile.MainPkgPath --- g/os/gfile/gfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index a5111de0c..e55e156f4 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -426,7 +426,7 @@ func MainPkgPath() string { } // separator of '/' will be converted to Separator. path = Dir(file) - for path != "/" && gstr.Contains(path, Separator) { + for path[len(path) - 1] != os.PathSeparator { files, _ := ScanDir(path, "*.go") for _, v := range files { if gregex.IsMatchString(`package\s+main`, GetContents(v)) { From dc29822e694149504890fc603265eb5eb7916ef5 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 4 Jun 2019 23:33:46 +0800 Subject: [PATCH 09/24] improve gtcp; add tls support for gtcp --- g/net/gtcp/gtcp_conn.go | 31 ++++++++-- g/net/gtcp/gtcp_conn_pkg.go | 10 +++- g/net/gtcp/gtcp_func.go | 43 +++++++++++++- g/net/gtcp/gtcp_pool.go | 12 ++-- g/net/gtcp/gtcp_pool_pkg.go | 8 ++- g/net/gtcp/gtcp_server.go | 31 +++++----- g/net/gudp/gudp_func.go | 1 + g/net/gudp/gudp_server.go | 4 +- .../ghttp/server/websocket/echo/main-group.go | 37 ++++++++++++ geg/net/gtcp/tls/gtcp_server_client.go | 58 +++++++++++++++++++ geg/net/gtcp/tls/server.crt | 23 ++++++++ geg/net/gtcp/tls/server.key | 27 +++++++++ geg/net/gtcp/tls/server.key.public | 27 +++++++++ geg/other/test.go | 22 +------ 14 files changed, 282 insertions(+), 52 deletions(-) create mode 100644 geg/net/ghttp/server/websocket/echo/main-group.go create mode 100644 geg/net/gtcp/tls/gtcp_server_client.go create mode 100644 geg/net/gtcp/tls/server.crt create mode 100644 geg/net/gtcp/tls/server.key create mode 100644 geg/net/gtcp/tls/server.key.public diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index a4729da0b..2b9952def 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -7,11 +7,12 @@ package gtcp import ( - "net" - "time" - "io" - "bufio" - "bytes" + "bufio" + "bytes" + "crypto/tls" + "io" + "net" + "time" ) // 封装的链接对象 @@ -21,7 +22,7 @@ type Conn struct { buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理) recvDeadline time.Time // 读取超时时间 sendDeadline time.Time // 写入超时时间 - recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔 + recvBufferWait time.Duration // 读取全部缓冲区数据时,读取缓冲区完毕后的等待间隔 } const ( @@ -38,6 +39,24 @@ func NewConn(addr string, timeout...int) (*Conn, error) { } } +// 创建支持TLS加密通信的TCP链接 +func NewConnTLS(addr string, tlsConfig *tls.Config) (*Conn, error) { + if conn, err := NewNetConnTLS(addr, tlsConfig); err == nil { + return NewConnByNetConn(conn), nil + } else { + return nil, err + } +} + +// 根据证书和密钥文件创建支持TLS加密通信的TCP链接 +func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) { + if conn, err := NewNetConnKeyCrt(addr, crtFile, keyFile); err == nil { + return NewConnByNetConn(conn), nil + } else { + return nil, err + } +} + // 将net.Conn接口对象转换为*gtcp.Conn对象 func NewConnByNetConn(conn net.Conn) *Conn { return &Conn { diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index f9d00cda9..e5a07cc17 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -61,7 +61,7 @@ func (c *Conn) SendPkg(data []byte, option...PkgOption) error { binary.BigEndian.PutUint32(buffer[0 : ], uint32(length)) copy(buffer[gPKG_HEADER_SIZE + 1 : ], data) if pkgOption.Retry.Count > 0 { - c.Send(buffer[1:], pkgOption.Retry) + return c.Send(buffer[1:], pkgOption.Retry) } //fmt.Println("SendPkg:", buffer[1:]) return c.Send(buffer[1:]) @@ -69,7 +69,9 @@ func (c *Conn) SendPkg(data []byte, option...PkgOption) error { // 简单协议: 带超时时间的数据发送 func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.SendPkg(data, option...) } @@ -137,7 +139,9 @@ func (c *Conn) RecvPkg(option...PkgOption) (result []byte, err error) { // 简单协议: 带超时时间的消息包获取 func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.RecvPkg(option...) } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_func.go b/g/net/gtcp/gtcp_func.go index cbc82106a..7f9f39f38 100644 --- a/g/net/gtcp/gtcp_func.go +++ b/g/net/gtcp/gtcp_func.go @@ -7,8 +7,11 @@ package gtcp import ( - "net" - "time" + "crypto/rand" + "crypto/tls" + "github.com/gogf/gf/g/os/gfile" + "net" + "time" ) const ( @@ -21,6 +24,7 @@ type Retry struct { Interval int // 重试间隔(毫秒) } +// Deprecated. // 常见的二进制数据校验方式,生成校验结果 func Checksum(buffer []byte) uint32 { var checksum uint32 @@ -39,6 +43,20 @@ func NewNetConn(addr string, timeout...int) (net.Conn, error) { } } +// 创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80 +func NewNetConnTLS(addr string, tlsConfig *tls.Config) (net.Conn, error) { + return tls.Dial("tcp", addr, tlsConfig) +} + +// 根据给定的证书和密钥文件创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80 +func NewNetConnKeyCrt(addr, crtFile, keyFile string) (net.Conn, error) { + tlsConfig, err := LoadKeyCrt(crtFile, keyFile) + if err != nil { + return nil, err + } + return NewNetConnTLS(addr, tlsConfig) +} + // (面向短链接)发送数据 func Send(addr string, data []byte, retry...Retry) error { conn, err := NewConn(addr) @@ -88,4 +106,25 @@ func isTimeout(err error) bool { return true } return false +} + +// 根据证书和密钥生成TLS对象 +func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) { + crtPath, err := gfile.Search(crtFile) + if err != nil { + return nil, err + } + keyPath, err := gfile.Search(keyFile) + if err != nil { + return nil, err + } + crt, err := tls.LoadX509KeyPair(crtPath, keyPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{} + tlsConfig.Certificates = []tls.Certificate{crt} + tlsConfig.Time = time.Now + tlsConfig.Rand = rand.Reader + return tlsConfig, nil } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_pool.go b/g/net/gtcp/gtcp_pool.go index fb739e2a8..0c45ecc01 100644 --- a/g/net/gtcp/gtcp_pool.go +++ b/g/net/gtcp/gtcp_pool.go @@ -67,7 +67,7 @@ func (c *PoolConn) Close() error { c.status = gCONN_STATUS_UNKNOWN c.pool.Put(c) } else { - c.Conn.Close() + return c.Conn.Close() } return nil } @@ -114,15 +114,19 @@ func (c *PoolConn) RecvLine(retry...Retry) ([]byte, error) { } // (方法覆盖)带超时时间的数据获取 -func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) +func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) (data []byte, err error) { + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // (方法覆盖)带超时时间的数据发送 func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gtcp/gtcp_pool_pkg.go b/g/net/gtcp/gtcp_pool_pkg.go index 08eb566d4..e30b909e0 100644 --- a/g/net/gtcp/gtcp_pool_pkg.go +++ b/g/net/gtcp/gtcp_pool_pkg.go @@ -41,14 +41,18 @@ func (c *PoolConn) RecvPkg(option...PkgOption) ([]byte, error) { // 简单协议: (方法覆盖)带超时时间的数据获取 func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.RecvPkg(option...) } // 简单协议: (方法覆盖)带超时时间的数据发送 func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.SendPkg(data, option...) } diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index 591fd8c89..2b11a6dde 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -8,14 +8,12 @@ package gtcp import ( - "crypto/rand" "crypto/tls" "errors" "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/util/gconv" "net" - "time" ) const ( @@ -58,34 +56,41 @@ func NewServer(address string, handler func (*Conn), name...string) *Server { return s } -// NewTlsServer creates and returns a new TCP server with TLS support. +// NewServerTLS creates and returns a new TCP server with TLS support. // The param is optional, which is used to specify the instance name of the server. -func NewTLSServer(address, crtFile, keyFile string, handler func (*Conn), name...string) *Server { +func NewServerTLS(address string, tlsConfig *tls.Config, handler func (*Conn), name...string) *Server { s := NewServer(address, handler, name...) - s.SetTLSKeyCrt(crtFile, keyFile) + s.SetTLSConfig(tlsConfig) + return s +} + +// NewServerKeyCrt creates and returns a new TCP server with TLS support. +// The param is optional, which is used to specify the instance name of the server. +func NewServerKeyCrt(address, crtFile, keyFile string, handler func (*Conn), name...string) *Server { + s := NewServer(address, handler, name...) + if err := s.SetTLSKeyCrt(crtFile, keyFile); err != nil { + glog.Error(err) + } return s } // SetAddress sets the listening address for server. -func (s *Server) SetAddress (address string) { +func (s *Server) SetAddress(address string) { s.address = address } // SetHandler sets the connection handler for server. -func (s *Server) SetHandler (handler func (*Conn)) { +func (s *Server) SetHandler(handler func (*Conn)) { s.handler = handler } // SetTlsKeyCrt sets the certificate and key file for TLS configuration of server. -func (s *Server) SetTLSKeyCrt (crtFile, keyFile string) error { - crt, err := tls.LoadX509KeyPair(crtFile,keyFile) +func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error { + tlsConfig, err := LoadKeyCrt(crtFile, keyFile) if err != nil { return err } - s.tlsConfig = &tls.Config{} - s.tlsConfig.Certificates = []tls.Certificate{crt} - s.tlsConfig.Time = time.Now - s.tlsConfig.Rand = rand.Reader + s.tlsConfig = tlsConfig return nil } diff --git a/g/net/gudp/gudp_func.go b/g/net/gudp/gudp_func.go index afcca7d0e..f77621ca5 100644 --- a/g/net/gudp/gudp_func.go +++ b/g/net/gudp/gudp_func.go @@ -10,6 +10,7 @@ import ( "net" ) +// Deprecated. // 常见的二进制数据校验方式,生成校验结果 func Checksum(buffer []byte) uint32 { var checksum uint32 diff --git a/g/net/gudp/gudp_server.go b/g/net/gudp/gudp_server.go index a14a8d932..cf71b87a6 100644 --- a/g/net/gudp/gudp_server.go +++ b/g/net/gudp/gudp_server.go @@ -30,7 +30,7 @@ var serverMapping = gmap.NewStrAnyMap() // 获取/创建一个空配置的UDP Server // 单例模式,请保证name的唯一性 -func GetServer(name...interface{}) (*Server) { +func GetServer(name...interface{}) *Server { serverName := gDEFAULT_SERVER if len(name) > 0 { serverName = gconv.String(name[0]) @@ -44,7 +44,7 @@ func GetServer(name...interface{}) (*Server) { } // 创建一个tcp server对象,并且可以选择指定一个单例名字 -func NewServer (address string, handler func (*Conn), names...string) *Server { +func NewServer(address string, handler func (*Conn), names...string) *Server { s := &Server{address, handler} if len(names) > 0 { serverMapping.Set(names[0], s) diff --git a/geg/net/ghttp/server/websocket/echo/main-group.go b/geg/net/ghttp/server/websocket/echo/main-group.go new file mode 100644 index 000000000..8b6851cb6 --- /dev/null +++ b/geg/net/ghttp/server/websocket/echo/main-group.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/os/gfile" + "github.com/gogf/gf/g/os/glog" +) + + +func ws(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + glog.Error(err) + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } +} + +func main() { + s := g.Server() + s.Group().Bind([]ghttp.GroupItem{ + {"ALL", "/ws", ws}, + }) + + s.SetServerRoot(gfile.MainPkgPath()) + s.SetPort(8199) + s.Run() +} diff --git a/geg/net/gtcp/tls/gtcp_server_client.go b/geg/net/gtcp/tls/gtcp_server_client.go new file mode 100644 index 000000000..cfac328ca --- /dev/null +++ b/geg/net/gtcp/tls/gtcp_server_client.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + glog.Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + tlsConfig, err := gtcp.LoadKeyCrt(crtFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.InsecureSkipVerify = true + + conn, err := gtcp.NewConnTLS(address, tlsConfig) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} diff --git a/geg/net/gtcp/tls/server.crt b/geg/net/gtcp/tls/server.crt new file mode 100644 index 000000000..4d254ea21 --- /dev/null +++ b/geg/net/gtcp/tls/server.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAJYpWLkC2lEXMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAkNIMRAwDgYDVQQIDAdTaUNodWFuMRAwDgYDVQQHDAdDaGVuZ2R1MRAwDgYD +VQQKDAdKb2huLmNuMQwwCgYDVQQLDANEZXYxDTALBgNVBAMMBEpvaG4xHDAaBgkq +hkiG9w0BCQEWDWpvaG5Aam9obmcuY24wHhcNMTgwNDIzMTMyNjA4WhcNMTkwNDIz +MTMyNjA4WjB+MQswCQYDVQQGEwJDSDEQMA4GA1UECAwHU2lDaHVhbjEQMA4GA1UE +BwwHQ2hlbmdkdTEQMA4GA1UECgwHSm9obi5jbjEMMAoGA1UECwwDRGV2MQ0wCwYD +VQQDDARKb2huMRwwGgYJKoZIhvcNAQkBFg1qb2huQGpvaG5nLmNuMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlS +F/tCPyKINhsePGqHkR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg ++Vps/RimBpiz+RU3FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMz +CRKoZ2dQ+98o5LwES+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJIN +DIp5Ypszg1cPs0gIr5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIO +NcqaHeGaTUVjzX9XwRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABo1Aw +TjAdBgNVHQ4EFgQU1/tUQpOK0xEwLLlYDiNrckqPlDowHwYDVR0jBBgwFoAU1/tU +QpOK0xEwLLlYDiNrckqPlDowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEA5MbG2xU3s/GDU1MV4f0wKhWCNhXfrLaYSwNYGT/eb8ZG2iHSTO0dvl0+pjO2 +EK63PDMvMhUtL1Zlyvl+OqssYcDhVfDzdFoYX6TZNbYxFwSzcx78mO6boAADk9ro +GEQWN+VHsl984SzBRZRJbtNbiw5iVuPruofeKHrrk4dLMiCsStyUaz9lUZxjo2Fi +vVJOY+mRNOBqz1HgU2+RilFTl04zWadCWPJMugQSgJcUPgxRXQ96PkC8uYevEnmR +2DUReSRULIOYEjHw0DZ6yGlqUkJcUGge3XAQEx3LlCpJasOC8Xpsh5i6WBnDPbMh +kPBjRRTooSrJOQJC5v3QW+0Kgw== +-----END CERTIFICATE----- diff --git a/geg/net/gtcp/tls/server.key b/geg/net/gtcp/tls/server.key new file mode 100644 index 000000000..e0f909629 --- /dev/null +++ b/geg/net/gtcp/tls/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlSF/tCPyKINhsePGqH +kR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg+Vps/RimBpiz+RU3 +FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMzCRKoZ2dQ+98o5LwE +S+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJINDIp5Ypszg1cPs0gI +r5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIONcqaHeGaTUVjzX9X +wRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABAoIBAHF7cMHPvL49F88j +nr7GnIntRUhwBB19EIBbknibBotc9nxVKaEjds0dbCSAdfslAyL7tbmrdaIJFXk3 +zsckgGceDLLuyz7B26CuaCEjCdRB43qQ9b9zsEoFBHMGrC6dGul+H+uuPn9FbVOc +NSWumuxa22W6qdJAiJFq4RvwZrsbVnYs5V29Y4Y20IlVUj3siJpAny//UUHequW9 +A/U7RvVssDsEEbbKvCpfcS7STNJKU7GlgV5l5hMKN2xLs1bVG5OKiZN82Zh9r7e1 +m2irxu/ehu6rENxZN0gsfPE4vqoQpbRMNAJlCfq9a3k0PH0TOy5oOVJXPGTIDQab +E3PeAwECgYEA9wh4+bPgMuO04hsAqsoO0DJ9Cwa+BzoDPYOvENobDzmcMErSDLKb +ekl1ej+fBTHRHVaBkuOf/9neLjhjMLad1B+I5gLksqwoMh87odDRCCpkO/B20ln8 +IN6RFiMiNjOaZqjPCCUobgzjbaIz3I69lCQQnMNPwjllSgZs9Lh/PjUCgYEA8kZU +hhUN6ctHIo8ocnmqa4AUPbt2l4qOoBGHCMmhjthyft6g8y6cQlACVJzbco37MhjY +uCOhhOClyUS1tyfds3NXdzAxXPl8SwQJGvl3zqkDQG7/GhCh6AzvHhZR8u7UaweC +kVnAG87Ck6Qqo5ZNbjhMIUm0ujm2cdVd3vyV3fECgYEAmJSMHDck8GnCzLE+/T5m +XeQBZfEZKF+FptYSKId+lS3RMebUzHD5JVQAEqz/LHczoTpQOAkORzorSEMdyPXS +kDWWGfOJjG5XOXYfH/hZVADS/k6tJYnc9/RgitrSg8XlxSjZDz/cM/UT+CBqhf1I +TRrlg94DAoTu8gT8AT9/oE0CgYB5CSPO/JO/2jtGi6iUUC4QmKMEGDRuDt2kID2K +6ViaCY5hzY0xEHcmNdyEMvz7JO16oKkcjUhzHtwUSgxSXUtIDHaE6AGxRj6PJ4v4 ++uqcxxkFxq4Rcn/Acz2+lT4JlMFwWwci4Gi2O7w/kENxCHTUfLGj67OrWYvJIORN +s3iXsQKBgD1I+v+simBvKZKmozzv99EgGfxkRxmrUQsclg1V8a1VTNfE5X9oNaE5 +kjp+dTnwbtmFl3SHVdFUzX/L6FvQIQ9FIwWI2bsszPm4rw8FBeOvH+8lXwVhCwPs +y9him/PhdjBPX0zydDI+h+fmrxH/XbmryZcq1rNmEtFRHBsUs5jg +-----END RSA PRIVATE KEY----- diff --git a/geg/net/gtcp/tls/server.key.public b/geg/net/gtcp/tls/server.key.public new file mode 100644 index 000000000..e0f909629 --- /dev/null +++ b/geg/net/gtcp/tls/server.key.public @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlSF/tCPyKINhsePGqH +kR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg+Vps/RimBpiz+RU3 +FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMzCRKoZ2dQ+98o5LwE +S+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJINDIp5Ypszg1cPs0gI +r5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIONcqaHeGaTUVjzX9X +wRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABAoIBAHF7cMHPvL49F88j +nr7GnIntRUhwBB19EIBbknibBotc9nxVKaEjds0dbCSAdfslAyL7tbmrdaIJFXk3 +zsckgGceDLLuyz7B26CuaCEjCdRB43qQ9b9zsEoFBHMGrC6dGul+H+uuPn9FbVOc +NSWumuxa22W6qdJAiJFq4RvwZrsbVnYs5V29Y4Y20IlVUj3siJpAny//UUHequW9 +A/U7RvVssDsEEbbKvCpfcS7STNJKU7GlgV5l5hMKN2xLs1bVG5OKiZN82Zh9r7e1 +m2irxu/ehu6rENxZN0gsfPE4vqoQpbRMNAJlCfq9a3k0PH0TOy5oOVJXPGTIDQab +E3PeAwECgYEA9wh4+bPgMuO04hsAqsoO0DJ9Cwa+BzoDPYOvENobDzmcMErSDLKb +ekl1ej+fBTHRHVaBkuOf/9neLjhjMLad1B+I5gLksqwoMh87odDRCCpkO/B20ln8 +IN6RFiMiNjOaZqjPCCUobgzjbaIz3I69lCQQnMNPwjllSgZs9Lh/PjUCgYEA8kZU +hhUN6ctHIo8ocnmqa4AUPbt2l4qOoBGHCMmhjthyft6g8y6cQlACVJzbco37MhjY +uCOhhOClyUS1tyfds3NXdzAxXPl8SwQJGvl3zqkDQG7/GhCh6AzvHhZR8u7UaweC +kVnAG87Ck6Qqo5ZNbjhMIUm0ujm2cdVd3vyV3fECgYEAmJSMHDck8GnCzLE+/T5m +XeQBZfEZKF+FptYSKId+lS3RMebUzHD5JVQAEqz/LHczoTpQOAkORzorSEMdyPXS +kDWWGfOJjG5XOXYfH/hZVADS/k6tJYnc9/RgitrSg8XlxSjZDz/cM/UT+CBqhf1I +TRrlg94DAoTu8gT8AT9/oE0CgYB5CSPO/JO/2jtGi6iUUC4QmKMEGDRuDt2kID2K +6ViaCY5hzY0xEHcmNdyEMvz7JO16oKkcjUhzHtwUSgxSXUtIDHaE6AGxRj6PJ4v4 ++uqcxxkFxq4Rcn/Acz2+lT4JlMFwWwci4Gi2O7w/kENxCHTUfLGj67OrWYvJIORN +s3iXsQKBgD1I+v+simBvKZKmozzv99EgGfxkRxmrUQsclg1V8a1VTNfE5X9oNaE5 +kjp+dTnwbtmFl3SHVdFUzX/L6FvQIQ9FIwWI2bsszPm4rw8FBeOvH+8lXwVhCwPs +y9him/PhdjBPX0zydDI+h+fmrxH/XbmryZcq1rNmEtFRHBsUs5jg +-----END RSA PRIVATE KEY----- diff --git a/geg/other/test.go b/geg/other/test.go index 41ec4db51..e9ed58c18 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,27 +2,9 @@ package main import ( "fmt" - - "github.com/gogf/gf" - "github.com/gogf/gf/g/net/ghttp" - - "github.com/gogf/gf/g" + "github.com/gogf/gf/g/os/gtime" ) func main() { - // fmt.Print(g.) - fmt.Println(gf.VERSION) - s := g.Server() - - s.BindHandler("/status/:status", func(r *ghttp.Request) { - r.Response.Write("woops, status ", r.Get("status"), " found") - }) - s.BindStatusHandler(404, func(r *ghttp.Request) { - r.Response.RedirectTo("/status/404") - }) - - s.SetErrorLogEnabled(true) - s.SetAccessLogEnabled(true) - s.SetPort(8890) - s.Run() + fmt.Println(gtime.Second()) } \ No newline at end of file From 1458e486d778cc9f11d3afa2ffe4277fcebc6c07 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 4 Jun 2019 23:39:33 +0800 Subject: [PATCH 10/24] v1.6.17 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 904859e1e..9dae0d748 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.6.16" +const VERSION = "v1.6.17" const AUTHORS = "john" From 6d44f02a385feef10f5dac11dab1ef54f8f8d285 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 18:40:26 +0800 Subject: [PATCH 11/24] fix issue in internal/empty --- g/internal/empty/empty.go | 25 +++--- g/net/ghttp/ghttp_unit_param_json_test.go | 57 +++++++++++- g/util/gconv/gconv_struct.go | 1 + geg/net/ghttp/server/hello.go | 105 +++++++++++++++++++++- geg/net/gtcp/gtcp_func.go | 2 +- 5 files changed, 176 insertions(+), 14 deletions(-) diff --git a/g/internal/empty/empty.go b/g/internal/empty/empty.go index 60d4d3406..a0a31d3d2 100644 --- a/g/internal/empty/empty.go +++ b/g/internal/empty/empty.go @@ -7,7 +7,7 @@ package empty import ( - "reflect" + "reflect" ) // 判断给定的变量是否为空。 @@ -35,17 +35,22 @@ func IsEmpty(value interface{}) bool { case string: return value == "" case []byte: return len(value) == 0 default: - // 最后通过反射来判断 + // Finally using reflect. rv := reflect.ValueOf(value) - if rv.IsNil() { - return true - } - kind := rv.Kind() - switch kind { - case reflect.Map: fallthrough - case reflect.Slice: fallthrough - case reflect.Array: + switch rv.Kind() { + case reflect.Chan, + reflect.Map, + reflect.Slice, + reflect.Array: return rv.Len() == 0 + + case reflect.Func, + reflect.Ptr, + reflect.Interface, + reflect.UnsafePointer: + if rv.IsNil() { + return true + } } } return false diff --git a/g/net/ghttp/ghttp_unit_param_json_test.go b/g/net/ghttp/ghttp_unit_param_json_test.go index a6dfd8522..9e7188771 100644 --- a/g/net/ghttp/ghttp_unit_param_json_test.go +++ b/g/net/ghttp/ghttp_unit_param_json_test.go @@ -46,6 +46,48 @@ func Test_Params_Json(t *testing.T) { Pass2 : "456", }) }) + s.BindHandler("/json3", func(r *ghttp.Request){ + type Message struct { + Code int `json:"code"` + Body string `json:"body,omitempty"` + Error string `json:"error,omitempty"` + } + type ResponseJson struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + ExtData interface{} `json:"ext_data,omitempty"` + Paginate interface{} `json:"paginate,omitempty"` + Message Message `json:"message,omitempty"` + } + responseJson := &ResponseJson{ + Success: true, + Data: nil, + ExtData: nil, + Message: Message{3, "测试", "error"}, + } + r.Response.WriteJson(responseJson) + }) + s.BindHandler("/json4", func(r *ghttp.Request){ + type Message struct { + Code int `json:"code"` + Body string `json:"body,omitempty"` + Error string `json:"error,omitempty"` + } + type ResponseJson struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + ExtData interface{} `json:"ext_data,omitempty"` + Paginate interface{} `json:"paginate,omitempty"` + Message *Message `json:"message,omitempty"` + } + responseJson := ResponseJson{ + Success: true, + Data: nil, + ExtData: nil, + Message: &Message{3, "测试", "error"}, + } + r.Response.WriteJson(responseJson) + }) s.SetPort(p) s.SetDumpRouteMap(false) s.Start() @@ -67,7 +109,7 @@ func Test_Params_Json(t *testing.T) { gtest.Assert(map1["password2"], "456") map2 := make(map[string]interface{}) - err2 := json.Unmarshal([]byte(client.GetContent("/json1")), &map2) + err2 := json.Unmarshal([]byte(client.GetContent("/json2")), &map2) gtest.Assert(err2, nil) gtest.Assert(len(map2), 4) gtest.Assert(map2["Name"], "john") @@ -75,5 +117,18 @@ func Test_Params_Json(t *testing.T) { gtest.Assert(map2["password1"], "123") gtest.Assert(map2["password2"], "456") + map3 := make(map[string]interface{}) + err3 := json.Unmarshal([]byte(client.GetContent("/json3")), &map3) + gtest.Assert(err3, nil) + gtest.Assert(len(map3), 2) + gtest.Assert(map3["success"], "true") + gtest.Assert(map3["message"], g.Map{"body":"测试", "code":3, "error":"error"}) + + map4 := make(map[string]interface{}) + err4 := json.Unmarshal([]byte(client.GetContent("/json4")), &map4) + gtest.Assert(err4, nil) + gtest.Assert(len(map4), 2) + gtest.Assert(map4["success"], "true") + gtest.Assert(map4["message"], g.Map{"body":"测试", "code":3, "error":"error"}) }) } diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index e84455496..8ffe29a09 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -46,6 +46,7 @@ func Struct(params interface{}, pointer interface{}, mapping...map[string]string if kind := rv.Kind(); kind != reflect.Ptr { return fmt.Errorf("object pointer should be type of: %v", kind) } + // Using IsNil on reflect.Ptr variable is OK. if !rv.IsValid() || rv.IsNil() { return errors.New("object pointer cannot be nil") } diff --git a/geg/net/ghttp/server/hello.go b/geg/net/ghttp/server/hello.go index f3ba0a357..9bcce536b 100644 --- a/geg/net/ghttp/server/hello.go +++ b/geg/net/ghttp/server/hello.go @@ -1,16 +1,117 @@ package main import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gproc" ) +type Session struct { + Token string `json:"token,omitempty"` + Ttl int `json:"ttl,omitempty"` //token 存活时间,单位:秒 +} + +type Message struct { + Code int `json:"code"` + Body string `json:"body,omitempty"` + Error string `json:"error,omitempty"` +} + +type Paginate struct { + CurrentPage string `json:"current_page,omitempty"` + PrePage string `json:"pre_page,omitempty"` + NextPage string `json:"next_page,omitempty"` + LastPage string `json:"last_page,omitempty"` + PageSize string `json:"page_size,omitempty"` + TotalCount string `json:"total_count,omitempty"` + TotalPage string `json:"total_page,omitempty"` + PrePageUrl string `json:"pre_page_url,omitempty"` + NextPageUrl string `json:"next_page_url,omitempty"` + CurrentPageUrl string `json:"current_page_url,omitempty"` +} + +type ResponseJson struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + ExtData interface{} `json:"ext_data,omitempty"` + Paginate interface{} `json:"paginate,omitempty"` + Message Message `json:"message,omitempty"` +} + +// 错误json +func ErrorJson(r *ghttp.Request, message Message) { + responseJson := &ResponseJson{ + Success: false, + Message: message, + } + //responseJson := struct { + // Code string + //}{ + // Code:"ok", + //} + fmt.Println(responseJson) + _ = r.Response.WriteJson(responseJson) + //glog.Error(error.Error()) + r.Exit() +} + +// 成功但没有数据的json +func SuccessJson(r *ghttp.Request, message Message) { + responseJson := &ResponseJson{ + Success: true, + Message: message, + } + _ = r.Response.WriteJson(responseJson) + //glog.Error(error.Error()) + r.Exit() +} + +// 成功但带有数据的json +func DataJson(r *ghttp.Request, success bool, message Message, data interface{}, extData interface{}) { + responseJson := &ResponseJson{ + Success: true, + Data: data, + ExtData: extData, + Message: message, + } + _ = r.Response.WriteJson(responseJson) + //glog.Error(error.Error()) + r.Exit() +} + +// 成功但带有数据和Session的json +func SessionJson(r *ghttp.Request, success bool, message Message, data interface{}, session Session) { + responseJson := &ResponseJson{ + Success: success, + Data: data, + ExtData: session, + Message: message, + } + _ = r.Response.WriteJson(responseJson) + //glog.Error(error.Error()) + r.Exit() +} + +// 成功带有数据和分页信息的json +func PaginateJson(r *ghttp.Request, success bool, message Message, paginate Paginate, data interface{}, extData interface{}) { + responseJson := &ResponseJson{ + Success: success, + Data: data, + ExtData: extData, + Paginate: paginate, + Message: message, + } + _ = r.Response.WriteJson(responseJson) + //glog.Error(error.Error()) + r.Exit() +} + + func main() { s := g.Server() s.SetIndexFolder(true) s.BindHandler("/", func(r *ghttp.Request) { - r.Response.Write("pid:", gproc.Pid()) + DataJson(r, true, Message{3, "测试", ""}, nil, nil) }) s.SetPort(8199) s.Run() diff --git a/geg/net/gtcp/gtcp_func.go b/geg/net/gtcp/gtcp_func.go index b28d9154c..96e7563a7 100644 --- a/geg/net/gtcp/gtcp_func.go +++ b/geg/net/gtcp/gtcp_func.go @@ -7,7 +7,7 @@ import ( ) func main() { - data, err := gtcp.SendRecv("www.baidu.com:80", []byte("GET / HTTP/1.1\n\n"), -1) + data, err := gtcp.SendRecv("www.baidu.com:80", []byte("HEAD / HTTP/1.1\n\n"), -1) if len(data) > 0 { fmt.Println(string(data)) } From 6fecf8bb01910921c2c340d9eaedd89da188cdbe Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 18:41:11 +0800 Subject: [PATCH 12/24] update example --- geg/net/ghttp/server/hello.go | 104 +--------------------------------- 1 file changed, 1 insertion(+), 103 deletions(-) diff --git a/geg/net/ghttp/server/hello.go b/geg/net/ghttp/server/hello.go index 9bcce536b..c23e92b92 100644 --- a/geg/net/ghttp/server/hello.go +++ b/geg/net/ghttp/server/hello.go @@ -1,117 +1,15 @@ package main import ( - "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" ) -type Session struct { - Token string `json:"token,omitempty"` - Ttl int `json:"ttl,omitempty"` //token 存活时间,单位:秒 -} - -type Message struct { - Code int `json:"code"` - Body string `json:"body,omitempty"` - Error string `json:"error,omitempty"` -} - -type Paginate struct { - CurrentPage string `json:"current_page,omitempty"` - PrePage string `json:"pre_page,omitempty"` - NextPage string `json:"next_page,omitempty"` - LastPage string `json:"last_page,omitempty"` - PageSize string `json:"page_size,omitempty"` - TotalCount string `json:"total_count,omitempty"` - TotalPage string `json:"total_page,omitempty"` - PrePageUrl string `json:"pre_page_url,omitempty"` - NextPageUrl string `json:"next_page_url,omitempty"` - CurrentPageUrl string `json:"current_page_url,omitempty"` -} - -type ResponseJson struct { - Success bool `json:"success"` - Data interface{} `json:"data,omitempty"` - ExtData interface{} `json:"ext_data,omitempty"` - Paginate interface{} `json:"paginate,omitempty"` - Message Message `json:"message,omitempty"` -} - -// 错误json -func ErrorJson(r *ghttp.Request, message Message) { - responseJson := &ResponseJson{ - Success: false, - Message: message, - } - //responseJson := struct { - // Code string - //}{ - // Code:"ok", - //} - fmt.Println(responseJson) - _ = r.Response.WriteJson(responseJson) - //glog.Error(error.Error()) - r.Exit() -} - -// 成功但没有数据的json -func SuccessJson(r *ghttp.Request, message Message) { - responseJson := &ResponseJson{ - Success: true, - Message: message, - } - _ = r.Response.WriteJson(responseJson) - //glog.Error(error.Error()) - r.Exit() -} - -// 成功但带有数据的json -func DataJson(r *ghttp.Request, success bool, message Message, data interface{}, extData interface{}) { - responseJson := &ResponseJson{ - Success: true, - Data: data, - ExtData: extData, - Message: message, - } - _ = r.Response.WriteJson(responseJson) - //glog.Error(error.Error()) - r.Exit() -} - -// 成功但带有数据和Session的json -func SessionJson(r *ghttp.Request, success bool, message Message, data interface{}, session Session) { - responseJson := &ResponseJson{ - Success: success, - Data: data, - ExtData: session, - Message: message, - } - _ = r.Response.WriteJson(responseJson) - //glog.Error(error.Error()) - r.Exit() -} - -// 成功带有数据和分页信息的json -func PaginateJson(r *ghttp.Request, success bool, message Message, paginate Paginate, data interface{}, extData interface{}) { - responseJson := &ResponseJson{ - Success: success, - Data: data, - ExtData: extData, - Paginate: paginate, - Message: message, - } - _ = r.Response.WriteJson(responseJson) - //glog.Error(error.Error()) - r.Exit() -} - - func main() { s := g.Server() s.SetIndexFolder(true) s.BindHandler("/", func(r *ghttp.Request) { - DataJson(r, true, Message{3, "测试", ""}, nil, nil) + r.Response.Write("hello world") }) s.SetPort(8199) s.Run() From d1f0fa1a47ccb8b4801cdbbc255eee6400ca9ab2 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 20:22:57 +0800 Subject: [PATCH 13/24] fix issue in concurrent safety for gview.Parse; rename glog.*fln functions to glog.*f --- g/encoding/gjson/gjson_api_new_load.go | 5 ++++- g/frame/gins/gins.go | 6 +++--- g/net/ghttp/ghttp_server.go | 4 ++-- g/net/ghttp/ghttp_server_admin_process.go | 14 ++++++------- g/net/ghttp/ghttp_server_graceful.go | 6 +++--- g/net/ghttp/ghttp_server_router.go | 2 +- g/net/ghttp/ghttp_server_router_group.go | 6 +++--- .../ghttp/ghttp_server_service_controller.go | 8 ++++---- g/net/ghttp/ghttp_server_service_object.go | 8 ++++---- g/os/gcfg/gcfg.go | 6 +++--- g/os/gcron/gcron_entry.go | 8 ++++---- g/os/gmlock/gmlock.go | 12 +++++++++-- g/os/gmlock/gmlock_locker.go | 14 +++++++++++++ g/os/gview/gview.go | 2 +- g/os/gview/gview_doparse.go | 7 +++++-- geg/other/test.go | 20 ++++++++++++++++--- 16 files changed, 85 insertions(+), 43 deletions(-) diff --git a/g/encoding/gjson/gjson_api_new_load.go b/g/encoding/gjson/gjson_api_new_load.go index 04a4b3166..712a4dd51 100644 --- a/g/encoding/gjson/gjson_api_new_load.go +++ b/g/encoding/gjson/gjson_api_new_load.go @@ -136,7 +136,10 @@ func LoadContent(data interface{}, unsafe...bool) (*Json, error) { var err error var result interface{} b := gconv.Bytes(data) - t := "json" + t := "" + if len(b) == 0 { + return New(nil, unsafe...), nil + } // auto check data type if json.Valid(b) { t = "json" diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 0794b6367..e055ac72d 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -219,13 +219,13 @@ func Redis(name...string) *gredis.Redis { Pass : array[4], }) } else { - glog.Errorfln(`invalid redis node configuration: "%s"`, line) + glog.Errorf(`invalid redis node configuration: "%s"`, line) } } else { - glog.Errorfln(`configuration for redis not found for group "%s"`, group) + glog.Errorf(`configuration for redis not found for group "%s"`, group) } } else { - glog.Errorfln(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()) + glog.Errorf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()) } return nil }) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 13efff4da..aa32e5359 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -389,7 +389,7 @@ func (s *Server) Run() error { // 阻塞等待服务执行完成 <- s.closeChan - glog.Printfln("%d: all servers shutdown", gproc.Pid()) + glog.Printf("%d: all servers shutdown", gproc.Pid()) return nil } @@ -400,7 +400,7 @@ func Wait() { // 阻塞等待服务执行完成 <- allDoneChan - glog.Printfln("%d: all servers shutdown", gproc.Pid()) + glog.Printf("%d: all servers shutdown", gproc.Pid()) } diff --git a/g/net/ghttp/ghttp_server_admin_process.go b/g/net/ghttp/ghttp_server_admin_process.go index 70865c4d1..5b9c1057c 100644 --- a/g/net/ghttp/ghttp_server_admin_process.go +++ b/g/net/ghttp/ghttp_server_admin_process.go @@ -129,7 +129,7 @@ func forkReloadProcess(newExeFilePath...string) error { buffer, _ := gjson.Encode(sfm) p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer)) if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) + glog.Errorf("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) return err } return nil @@ -147,7 +147,7 @@ func forkRestartProcess(newExeFilePath...string) error { env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1") p := gproc.NewProcess(path, os.Args, env) if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) + glog.Errorf("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) return err } return nil @@ -197,14 +197,14 @@ func restartWebServers(signal string, newExeFilePath...string) error { } } else { if err := forkReloadProcess(newExeFilePath...); err != nil { - glog.Printfln("%d: server restarts failed", gproc.Pid()) + glog.Printf("%d: server restarts failed", gproc.Pid()) serverProcessStatus.Set(gADMIN_ACTION_NONE) return err } else { if len(signal) > 0 { - glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal) + glog.Printf("%d: server restarting by signal: %s", gproc.Pid(), signal) } else { - glog.Printfln("%d: server restarting by web admin", gproc.Pid()) + glog.Printf("%d: server restarting by web admin", gproc.Pid()) } } @@ -216,12 +216,12 @@ func restartWebServers(signal string, newExeFilePath...string) error { func shutdownWebServers(signal...string) { serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN) if len(signal) > 0 { - glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0]) + glog.Printf("%d: server shutting down by signal: %s", gproc.Pid(), signal[0]) // 在终端信号下,立即执行关闭操作 forceCloseWebServers() allDoneChan <- struct{}{} } else { - glog.Printfln("%d: server shutting down by api", gproc.Pid()) + glog.Printf("%d: server shutting down by api", gproc.Pid()) // 非终端信号下,异步1秒后再执行关闭, // 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) gtimer.SetTimeout(time.Second, func() { diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 7ea854cd3..dbe99d32a 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -130,7 +130,7 @@ func (s *gracefulServer) doServe() error { if s.fd != 0 { action = "reloaded" } - glog.Printfln("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr) + glog.Printf("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr) s.status = SERVER_STATUS_RUNNING err := s.httpServer.Serve(s.listener) s.status = SERVER_STATUS_STOPPED @@ -173,7 +173,7 @@ func (s *gracefulServer) shutdown() { return } if err := s.httpServer.Shutdown(context.Background()); err != nil { - glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) + glog.Errorf("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } } @@ -183,7 +183,7 @@ func (s *gracefulServer) close() { return } if err := s.httpServer.Close(); err != nil { - glog.Errorfln("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) + glog.Errorf("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) } } diff --git a/g/net/ghttp/ghttp_server_router.go b/g/net/ghttp/ghttp_server_router.go index c367e371f..c7838c793 100644 --- a/g/net/ghttp/ghttp_server_router.go +++ b/g/net/ghttp/ghttp_server_router.go @@ -84,7 +84,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin caller := s.getHandlerRegisterCallerLine(handler) if len(hook) == 0 { if item, ok := s.routesMap[regkey]; ok { - glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file) + glog.Errorf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file) return } } diff --git a/g/net/ghttp/ghttp_server_router_group.go b/g/net/ghttp/ghttp_server_router_group.go index 5e859d0f5..075645b4f 100644 --- a/g/net/ghttp/ghttp_server_router_group.go +++ b/g/net/ghttp/ghttp_server_router_group.go @@ -50,7 +50,7 @@ func (d *Domain) Group(prefix...string) *RouterGroup { func (g *RouterGroup) Bind(items []GroupItem) { for _, item := range items { if len(item) < 3 { - glog.Fatalfln("invalid router item: %s", item) + glog.Fatalf("invalid router item: %s", item) } if strings.EqualFold(gconv.String(item[0]), "REST") { g.bind("REST", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2]) @@ -124,7 +124,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{}, if len(g.prefix) > 0 { domain, method, path, err := g.server.parsePattern(pattern) if err != nil { - glog.Fatalfln("invalid pattern: %s", pattern) + glog.Fatalf("invalid pattern: %s", pattern) } if bindType == "HANDLER" { pattern = g.server.serveHandlerKey(method, g.prefix + "/" + strings.TrimLeft(path, "/"), domain) @@ -196,7 +196,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{}, g.domain.BindHookHandler(pattern, methods[0], h) } } else { - glog.Fatalfln("invalid hook handler for pattern:%s", pattern) + glog.Fatalf("invalid hook handler for pattern:%s", pattern) } } } diff --git a/g/net/ghttp/ghttp_server_service_controller.go b/g/net/ghttp/ghttp_server_service_controller.go index 994d4124f..b5058ab23 100644 --- a/g/net/ghttp/ghttp_server_service_controller.go +++ b/g/net/ghttp/ghttp_server_service_controller.go @@ -50,11 +50,11 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) { if _, ok := v.Method(i).Interface().(func()); !ok { if len(methodMap) > 0 { // 指定的方法名称注册,那么需要使用错误提示 - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, v.Method(i).Type().String()) } else { // 否则只是Debug提示 - glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`, + glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`, pkgPath, ctlName, mname, v.Method(i).Type().String()) } continue @@ -108,7 +108,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string ctlName = fmt.Sprintf(`(%s)`, ctlName) } if _, ok := fval.Interface().(func()); !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, fval.Type().String()) return } @@ -147,7 +147,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) { ctlName = fmt.Sprintf(`(%s)`, ctlName) } if _, ok := v.Method(i).Interface().(func()); !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, v.Method(i).Type().String()) return } diff --git a/g/net/ghttp/ghttp_server_service_object.go b/g/net/ghttp/ghttp_server_service_object.go index 74dcd8a32..5f07203de 100644 --- a/g/net/ghttp/ghttp_server_service_object.go +++ b/g/net/ghttp/ghttp_server_service_object.go @@ -57,11 +57,11 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) { if !ok { if len(methodMap) > 0 { // 指定的方法名称注册,那么需要使用错误提示 - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, v.Method(i).Type().String()) } else { // 否则只是Debug提示 - glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`, + glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`, pkgPath, objName, mname, v.Method(i).Type().String()) } continue @@ -127,7 +127,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) } faddr, ok := fval.Interface().(func(*Request)) if !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, fval.Type().String()) return } @@ -174,7 +174,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) { } faddr, ok := v.Method(i).Interface().(func(*Request)) if !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, v.Method(i).Type().String()) continue } diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index 70fcd6833..1233a3702 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -57,7 +57,7 @@ func New(file...string) *Config { if gfile.Exists(envPath) { c.SetPath(envPath) } else { - glog.Errorfln("Configuration directory path does not exist: %s", envPath) + glog.Errorf("Configuration directory path does not exist: %s", envPath) } } else { // Dir path of working dir. @@ -276,9 +276,9 @@ func (c *Config) getJson(file...string) *gjson.Json { return j } else { if filePath != "" { - glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) + glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) } else { - glog.Criticalfln(`[gcfg] Load configuration failed: %s`, err.Error()) + glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error()) } } return nil diff --git a/g/os/gcron/gcron_entry.go b/g/os/gcron/gcron_entry.go index ecc880089..b4c136e5b 100644 --- a/g/os/gcron/gcron_entry.go +++ b/g/os/gcron/gcron_entry.go @@ -115,7 +115,7 @@ func (entry *Entry) check() { return case STATUS_CLOSED: - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) entry.Close() case STATUS_READY: fallthrough @@ -130,12 +130,12 @@ func (entry *Entry) check() { if times < 2000000000 && times > 1000000000 { entry.times.Set(gDEFAULT_TIMES) } - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) defer func() { if err := recover(); err != nil { - glog.Path(path).Level(level).Errorfln("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) + glog.Path(path).Level(level).Errorf("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) } else { - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) } if entry.entry.Status() == STATUS_CLOSED { entry.Close() diff --git a/g/os/gmlock/gmlock.go b/g/os/gmlock/gmlock.go index ee785b27a..8bfcbbd6e 100644 --- a/g/os/gmlock/gmlock.go +++ b/g/os/gmlock/gmlock.go @@ -5,8 +5,6 @@ // You can obtain one at https://github.com/gogf/gf. // Package gmlock implements a thread-safe memory locker. -// -// 内存锁. package gmlock import "time" @@ -43,4 +41,14 @@ func RLock(key string) { // 解除基于内存锁的读锁 func RUnlock(key string) { locker.RUnlock(key) +} + +// 通过闭包的方式写锁执行回调方法,执行完毕后释放写锁 +func LockFunc(key string, f func(), expire...time.Duration) { + locker.LockFunc(key, f, expire...) +} + +// 通过闭包的方式读锁执行回调方法,执行完毕后释放读锁 +func RLockFunc(key string, f func()) { + locker.RLockFunc(key, f) } \ No newline at end of file diff --git a/g/os/gmlock/gmlock_locker.go b/g/os/gmlock/gmlock_locker.go index a43fb55e8..133a15053 100644 --- a/g/os/gmlock/gmlock_locker.go +++ b/g/os/gmlock/gmlock_locker.go @@ -58,6 +58,20 @@ func (l *Locker) RUnlock(key string) { } } +// 通过闭包的方式写锁执行回调方法,执行完毕后释放写锁 +func (l *Locker) LockFunc(key string, f func(), expire...time.Duration) { + l.Lock(key, expire...) + defer l.Unlock(key) + f() +} + +// 通过闭包的方式读锁执行回调方法,执行完毕后释放读锁 +func (l *Locker) RLockFunc(key string, f func()) { + l.RLock(key) + defer l.RUnlock(key) + f() +} + // 获得过期时间,没有设置时默认为0不过期 func (l *Locker) getExpire(expire...time.Duration) time.Duration { e := time.Duration(0) diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 5d97f39aa..98957cef0 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -69,7 +69,7 @@ func New(path...string) *View { if gfile.Exists(envPath) { view.SetPath(envPath) } else { - glog.Errorfln("Template directory path does not exist: %s", envPath) + glog.Errorf("Template directory path does not exist: %s", envPath) } } else { // Dir path of working dir. diff --git a/g/os/gview/gview_doparse.go b/g/os/gview/gview_doparse.go index b122d977c..2dc12e0e9 100644 --- a/g/os/gview/gview_doparse.go +++ b/g/os/gview/gview_doparse.go @@ -15,6 +15,7 @@ import ( "github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/os/gfsnotify" "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gmlock" "github.com/gogf/gf/g/os/gspath" "github.com/gogf/gf/g/text/gstr" "text/template" @@ -40,7 +41,7 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa if tpl, err = tpl.ParseFiles(files...); err != nil { return nil } - gfsnotify.Add(path, func(event *gfsnotify.Event) { + _, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) { templates.Remove(path) gfsnotify.Exit() }) @@ -102,7 +103,9 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error) if err != nil { return "", err } - tpl, err = tpl.Parse(gfcache.GetContents(path)) + gmlock.LockFunc("gview-template-parsing:" + folder, func() { + tpl, err = tpl.Parse(gfcache.GetContents(path)) + }) if err != nil { return "", err } diff --git a/geg/other/test.go b/geg/other/test.go index e9ed58c18..cd63d8660 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,10 +1,24 @@ package main import ( - "fmt" - "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/os/gcache" ) func main() { - fmt.Println(gtime.Second()) + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + rs :="GF(Go Frame)是一款模块化、松耦合、生产级的Go应用开发框架。提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、路由注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。" + Weijin_word := gcache.Get("weijin_word") + if Weijin_word == nil { + Weijin_word = gcache.GetOrSet("weijin_word", rs, 0) + } + //此行压测会提示map并发错误 webbench -c 8000 -t 60 http://IP 局域网两台机器测试 + r.Response.WriteTpl("layout.html", g.Map{ + "Contentb": Weijin_word, + }) + }) + s.SetPort(8199) + s.Run() } \ No newline at end of file From 841224372ba24d15af3ec2d5fea88e614c4ae967 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 21:58:27 +0800 Subject: [PATCH 14/24] update comment for gmlock --- g/net/ghttp/ghttp_response_view.go | 5 ++- g/net/ghttp/ghttp_server_session.go | 6 +-- g/os/gcfg/gcfg.go | 8 ++-- g/os/gmlock/gmlock.go | 36 ++++++++++++---- g/os/gmlock/gmlock_locker.go | 65 ++++++++++++++++++++++------- g/os/gmlock/gmlock_mutex.go | 38 ++++++++++------- g/os/gview/gview_doparse.go | 18 ++++++-- geg/other/test.go | 9 +--- 8 files changed, 130 insertions(+), 55 deletions(-) diff --git a/g/net/ghttp/ghttp_response_view.go b/g/net/ghttp/ghttp_response_view.go index 62ec7dd17..93d7a35b4 100644 --- a/g/net/ghttp/ghttp_response_view.go +++ b/g/net/ghttp/ghttp_response_view.go @@ -52,7 +52,10 @@ func (r *Response) buildInVars(params...map[string]interface{}) map[string]inter } else { vars = make(map[string]interface{}) } - vars["Config"] = gins.Config().GetMap("") + // 当配置文件不存在时就不赋值该模板变量,不然会报错 + if c := gins.Config(); c.FilePath() != "" { + vars["Config"] = c.GetMap("") + } vars["Cookie"] = r.request.Cookie.Map() vars["Session"] = r.request.Session.Map() vars["Get"] = r.request.GetQueryMap() diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 58e588be6..706b18e76 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -20,10 +20,10 @@ import ( // SESSION对象 type Session struct { - id string // SessionId + id string // SessionId data *gmap.StrAnyMap // Session数据 - server *Server // 所属Server - request *Request // 关联的请求 + server *Server // 所属Server + request *Request // 关联的请求 } // 生成一个唯一的SessionId字符串,长度18位。 diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index 1233a3702..057216f16 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -55,20 +55,20 @@ func New(file...string) *Config { // Customized dir path from env/cmd. if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" { if gfile.Exists(envPath) { - c.SetPath(envPath) + _ = c.SetPath(envPath) } else { glog.Errorf("Configuration directory path does not exist: %s", envPath) } } else { // Dir path of working dir. - c.SetPath(gfile.Pwd()) + _ = c.SetPath(gfile.Pwd()) // Dir path of binary. if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { - c.AddPath(selfPath) + _ = c.AddPath(selfPath) } // Dir path of main package. if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { - c.AddPath(mainPath) + _ = c.AddPath(mainPath) } } return c diff --git a/g/os/gmlock/gmlock.go b/g/os/gmlock/gmlock.go index 8bfcbbd6e..a31aee8b8 100644 --- a/g/os/gmlock/gmlock.go +++ b/g/os/gmlock/gmlock.go @@ -13,42 +13,62 @@ var ( locker = New() ) -// 内存写锁,如果锁成功返回true,失败则返回false;过期时间单位为秒,默认为0表示不过期 +// TryLock tries locking the with write lock, +// it returns true if success, or if there's a write/read lock the , +// it returns false. The parameter specifies the max duration it locks. func TryLock(key string, expire...time.Duration) bool { return locker.TryLock(key, expire...) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间单位为秒,默认为0表示不过期 +// Lock locks the with write lock. +// If there's a write/read lock the , +// it will blocks until the lock is released. +// The parameter specifies the max duration it locks. func Lock(key string, expire...time.Duration) { locker.Lock(key, expire...) } -// 解除基于内存锁的写锁 +// Unlock unlocks the write lock of the . func Unlock(key string) { locker.Unlock(key) } -// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期 +// TryRLock tries locking the with read lock. +// It returns true if success, or if there's a write lock on , it returns false. func TryRLock(key string) bool { return locker.TryRLock(key) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期 +// RLock locks the with read lock. +// If there's a write lock on , +// it will blocks until the write lock is released. func RLock(key string) { locker.RLock(key) } -// 解除基于内存锁的读锁 +// RUnlock unlocks the read lock of the . func RUnlock(key string) { locker.RUnlock(key) } -// 通过闭包的方式写锁执行回调方法,执行完毕后释放写锁 +// LockFunc locks the with write lock and callback function . +// If there's a write/read lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. func LockFunc(key string, f func(), expire...time.Duration) { locker.LockFunc(key, f, expire...) } -// 通过闭包的方式读锁执行回调方法,执行完毕后释放读锁 +// RLockFunc locks the with read lock and callback function . +// If there's a write lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. func RLockFunc(key string, f func()) { locker.RLockFunc(key, f) } \ No newline at end of file diff --git a/g/os/gmlock/gmlock_locker.go b/g/os/gmlock/gmlock_locker.go index 133a15053..bd653eb13 100644 --- a/g/os/gmlock/gmlock_locker.go +++ b/g/os/gmlock/gmlock_locker.go @@ -12,67 +12,89 @@ import ( "time" ) -// 内存锁管理对象 +// Memory locker. type Locker struct { m *gmap.StrAnyMap } -// 创建一把内存锁, 底层使用的是Mutex +// New creates and returns a new memory locker. +// A memory locker can lock/unlock with dynamic string key. func New() *Locker { return &Locker{ m : gmap.NewStrAnyMap(), } } -// 内存写锁,如果锁成功返回true,失败则返回false; 过期时间默认为0表示不过期 +// TryLock tries locking the with write lock, +// it returns true if success, or if there's a write/read lock the , +// it returns false. The parameter specifies the max duration it locks. func (l *Locker) TryLock(key string, expire...time.Duration) bool { return l.doLock(key, l.getExpire(expire...), true) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间默认为0表示不过期 +// Lock locks the with write lock. +// If there's a write/read lock the , +// it will blocks until the lock is released. +// The parameter specifies the max duration it locks. func (l *Locker) Lock(key string, expire...time.Duration) { l.doLock(key, l.getExpire(expire...), false) } -// 解除基于内存锁的写锁 +// Unlock unlocks the write lock of the . func (l *Locker) Unlock(key string) { if v := l.m.Get(key); v != nil { v.(*Mutex).Unlock() } } -// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期 +// TryRLock tries locking the with read lock. +// It returns true if success, or if there's a write lock on , it returns false. func (l *Locker) TryRLock(key string) bool { return l.doRLock(key, true) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期 +// RLock locks the with read lock. +// If there's a write lock on , +// it will blocks until the write lock is released. func (l *Locker) RLock(key string) { l.doRLock(key, false) } -// 解除基于内存锁的读锁 +// RUnlock unlocks the read lock of the . func (l *Locker) RUnlock(key string) { if v := l.m.Get(key); v != nil { v.(*Mutex).RUnlock() } } -// 通过闭包的方式写锁执行回调方法,执行完毕后释放写锁 +// LockFunc locks the with write lock and callback function . +// If there's a write/read lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. func (l *Locker) LockFunc(key string, f func(), expire...time.Duration) { l.Lock(key, expire...) defer l.Unlock(key) f() } -// 通过闭包的方式读锁执行回调方法,执行完毕后释放读锁 +// RLockFunc locks the with read lock and callback function . +// If there's a write lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. func (l *Locker) RLockFunc(key string, f func()) { l.RLock(key) defer l.RUnlock(key) f() } -// 获得过期时间,没有设置时默认为0不过期 +// getExpire returns the duration object passed. +// If is not passed, it returns a default duration object. func (l *Locker) getExpire(expire...time.Duration) time.Duration { e := time.Duration(0) if len(expire) > 0 { @@ -81,7 +103,14 @@ func (l *Locker) getExpire(expire...time.Duration) time.Duration { return e } -// 内存写锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待 +// doLock locks writing on . +// It returns true if success, or else returns false. +// +// The parameter is true, +// it returns false immediately if it fails getting the write lock. +// If is false, it blocks until it gets the write lock. +// +// The parameter specifies the max duration it locks. func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { mu := l.getOrNewMutex(key) ok := true @@ -102,7 +131,12 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { return ok } -// 内存读锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待 +// doRLock locks reading on . +// It returns true if success, or else returns false. +// +// The parameter is true, +// it returns false immediately if it fails getting the read lock. +// If is false, it blocks until it gets the read lock. func (l *Locker) doRLock(key string, try bool) bool { mu := l.getOrNewMutex(key) ok := true @@ -114,8 +148,9 @@ func (l *Locker) doRLock(key string, try bool) bool { return ok } -// 根据指定key查询或者创建新的Mutex -func (l *Locker) getOrNewMutex(key string) (*Mutex) { +// getOrNewMutex returns the mutex of given if it exists, +// or else creates and returns a new one. +func (l *Locker) getOrNewMutex(key string) *Mutex { return l.m.GetOrSetFuncLock(key, func() interface{} { return NewMutex() }).(*Mutex) diff --git a/g/os/gmlock/gmlock_mutex.go b/g/os/gmlock/gmlock_mutex.go index 8bc7aaadb..fc4d992a2 100644 --- a/g/os/gmlock/gmlock_mutex.go +++ b/g/os/gmlock/gmlock_mutex.go @@ -11,15 +11,15 @@ import ( "sync" ) -// 互斥锁对象 +// The mutex. type Mutex struct { mu sync.RWMutex - wid *gtype.Int64 // 当前Lock产生的唯一id(主要用于计时Unlock的校验) - rcount *gtype.Int // RLock次数 - wcount *gtype.Int // Lock次数 + wid *gtype.Int64 // Unique id for this mutex. + rcount *gtype.Int // RLock count. + wcount *gtype.Int // Lock count. } -// 创建一把内存锁使用的底层RWMutex +// NewMutex creates and returns a new mutex. func NewMutex() *Mutex { return &Mutex{ wid : gtype.NewInt64(), @@ -28,46 +28,55 @@ func NewMutex() *Mutex { } } +// Lock locks mutex for writing. +// If the lock is already locked for reading or writing, +// Lock blocks until the lock is available. func (l *Mutex) Lock() { l.wcount.Add(1) l.mu.Lock() l.wid.Add(1) } -// 安全的Unlock +// Unlock unlocks the write lock. +// It is safe to be called multiple times. func (l *Mutex) Unlock() { if l.wcount.Val() > 0 { if l.wcount.Add(-1) >= 0 { l.mu.Unlock() } else { - // 标准库这里会panic l.wcount.Add(1) } } } +// RLock locks mutex for reading. func (l *Mutex) RLock() { l.rcount.Add(1) l.mu.RLock() } -// 安全的RUnlock +// RUnlock undoes a single RLock call; +// it does not affect other simultaneous readers. +// It is a run-time error if rw is not locked for reading +// on entry to RUnlock. +// It is safe to be called multiple times. func (l *Mutex) RUnlock() { if l.rcount.Val() > 0 { if l.rcount.Add(-1) >= 0 { l.mu.RUnlock() } else { - // 标准库这里会panic l.rcount.Add(1) } } } -// 不阻塞Lock +// TryLock tries locking the mutex with write lock, +// it returns true if success, or if there's a write/read lock the mutex, +// it returns false. func (l *Mutex) TryLock() bool { - // 初步读写次数检查, 但无法保证原子性 + // The first check, but it cannot ensure the atomicity. if l.wcount.Val() == 0 && l.rcount.Val() == 0 { - // 第二次检查, 保证原子操作 + // The second check, it can ensure the atomicity. if l.wcount.Add(1) == 1 { l.mu.Lock() l.wid.Add(1) @@ -79,9 +88,10 @@ func (l *Mutex) TryLock() bool { return false } -// 不阻塞RLock +// TryRLock tries locking the mutex with read lock. +// It returns true if success, or if there's a write lock on mutex, it returns false. func (l *Mutex) TryRLock() bool { - // 只要不存在写锁 + // There must be no write lock on mutex. if l.wcount.Val() == 0 { l.rcount.Add(1) l.mu.RLock() diff --git a/g/os/gview/gview_doparse.go b/g/os/gview/gview_doparse.go index 2dc12e0e9..1ae3d750e 100644 --- a/g/os/gview/gview_doparse.go +++ b/g/os/gview/gview_doparse.go @@ -21,6 +21,11 @@ import ( "text/template" ) +const ( + // Template name for content parsing. + gCONTENT_TEMPLATE_NAME = "template content" +) + var ( // Templates cache map for template folder. templates = gmap.NewStrAnyMap() @@ -103,7 +108,8 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error) if err != nil { return "", err } - gmlock.LockFunc("gview-template-parsing:" + folder, func() { + // Using memory lock to ensure concurrent safety for template parsing. + gmlock.LockFunc("gview-parsing:" + folder, func() { tpl, err = tpl.Parse(gfcache.GetContents(path)) }) if err != nil { @@ -149,8 +155,14 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error) func (view *View) ParseContent(content string, params...Params) (string, error) { view.mu.RLock() defer view.mu.RUnlock() - tpl := template.New("template content").Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) - tpl, err := tpl.Parse(content) + err := (error)(nil) + tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface {} { + return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) + }).(*template.Template) + // Using memory lock to ensure concurrent safety for content parsing. + gmlock.LockFunc("gview-parsing:content", func() { + tpl, err = tpl.Parse(content) + }) if err != nil { return "", err } diff --git a/geg/other/test.go b/geg/other/test.go index cd63d8660..729ac6946 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -3,20 +3,15 @@ package main import ( "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gcache" ) func main() { s := g.Server() s.BindHandler("/", func(r *ghttp.Request) { rs :="GF(Go Frame)是一款模块化、松耦合、生产级的Go应用开发框架。提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、路由注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。" - Weijin_word := gcache.Get("weijin_word") - if Weijin_word == nil { - Weijin_word = gcache.GetOrSet("weijin_word", rs, 0) - } //此行压测会提示map并发错误 webbench -c 8000 -t 60 http://IP 局域网两台机器测试 - r.Response.WriteTpl("layout.html", g.Map{ - "Contentb": Weijin_word, + r.Response.WriteTplContent(rs, g.Map{ + "Contentb": 1, }) }) s.SetPort(8199) From 4bbe51fb4be702aa4821fc569a32566c1337c010 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 22:12:22 +0800 Subject: [PATCH 15/24] add Close function for gtcp.Server/gudp.Server --- g/net/gtcp/gtcp_server.go | 13 +++++++++---- g/net/gudp/gudp_server.go | 24 +++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index 2b11a6dde..f254d87ad 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -22,6 +22,7 @@ const ( // TCP Server. type Server struct { + listen net.Listener address string handler func (*Conn) tlsConfig *tls.Config @@ -99,6 +100,11 @@ func (s *Server) SetTLSConfig(tlsConfig *tls.Config) { s.tlsConfig = tlsConfig } +// Close closes the listener and shutdowns the server. +func (s *Server) Close() error { + return s.listen.Close() +} + // Run starts running the TCP Server. func (s *Server) Run() (err error) { if s.handler == nil { @@ -106,10 +112,9 @@ func (s *Server) Run() (err error) { glog.Error(err) return } - listen := net.Listener(nil) if s.tlsConfig != nil { // TLS Server - listen, err = tls.Listen("tcp", s.address, s.tlsConfig) + s.listen, err = tls.Listen("tcp", s.address, s.tlsConfig) if err != nil { glog.Error(err) return @@ -121,14 +126,14 @@ func (s *Server) Run() (err error) { glog.Error(err) return err } - listen, err = net.ListenTCP("tcp", addr) + s.listen, err = net.ListenTCP("tcp", addr) if err != nil { glog.Error(err) return err } } for { - if conn, err := listen.Accept(); err != nil { + if conn, err := s.listen.Accept(); err != nil { glog.Error(err) return err } else if conn != nil { diff --git a/g/net/gudp/gudp_server.go b/g/net/gudp/gudp_server.go index cf71b87a6..82abe518d 100644 --- a/g/net/gudp/gudp_server.go +++ b/g/net/gudp/gudp_server.go @@ -21,8 +21,9 @@ const ( // tcp server结构体 type Server struct { - address string - handler func (*Conn) + conn *Conn // UDP server connection object. + address string // Listening address. + handler func (*Conn) } // Server表,用以存储和检索名称与Server对象之间的关联关系 @@ -45,7 +46,10 @@ func GetServer(name...interface{}) *Server { // 创建一个tcp server对象,并且可以选择指定一个单例名字 func NewServer(address string, handler func (*Conn), names...string) *Server { - s := &Server{address, handler} + s := &Server{ + address : address, + handler : handler, + } if len(names) > 0 { serverMapping.Set(names[0], s) } @@ -58,10 +62,16 @@ func (s *Server) SetAddress (address string) { } // 设置参数 - handler -func (s *Server) SetHandler (handler func (*Conn)) { +func (s *Server) SetHandler(handler func (*Conn)) { s.handler = handler } +// Close closes the connection. +// It will make server shutdowns immediately. +func (s *Server) Close() error { + return s.conn.Close() +} + // 执行监听 func (s *Server) Run() error { if s.handler == nil { @@ -79,7 +89,7 @@ func (s *Server) Run() error { glog.Error(err) return err } - for { - s.handler(NewConnByNetConn(conn)) - } + s.conn = NewConnByNetConn(conn) + s.handler(s.conn) + return nil } From 055074246e2856823ea6dcf0a9fe2d3183a71555 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 5 Jun 2019 23:50:24 +0800 Subject: [PATCH 16/24] add TryLockFunc/TryRLockFunc for gmlock.Mutex --- g/os/gmlock/gmlock.go | 23 ++++++- g/os/gmlock/gmlock_locker.go | 31 ++++++++- g/os/gmlock/gmlock_mutex.go | 127 ++++++++++++++++++++++++----------- 3 files changed, 140 insertions(+), 41 deletions(-) diff --git a/g/os/gmlock/gmlock.go b/g/os/gmlock/gmlock.go index a31aee8b8..18bc88fa0 100644 --- a/g/os/gmlock/gmlock.go +++ b/g/os/gmlock/gmlock.go @@ -4,12 +4,13 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gmlock implements a thread-safe memory locker. +// Package gmlock implements a concurrent-safe memory-based locker. package gmlock import "time" var ( + // Default locker. locker = New() ) @@ -51,6 +52,26 @@ func RUnlock(key string) { locker.RUnlock(key) } +// TryLockFunc locks the with write lock and callback function . +// It returns true if success, or else if there's a write/read lock the , it return false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func TryLockFunc(key string, f func(), expire...time.Duration) bool { + return locker.TryLockFunc(key, f, expire...) +} + +// TryRLockFunc locks the with read lock and callback function . +// It returns true if success, or else if there's a write lock the , it returns false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func TryRLockFunc(key string, f func()) bool { + return locker.TryRLockFunc(key, f) +} + // LockFunc locks the with write lock and callback function . // If there's a write/read lock the , // it will blocks until the lock is released. diff --git a/g/os/gmlock/gmlock_locker.go b/g/os/gmlock/gmlock_locker.go index bd653eb13..6eeafbff7 100644 --- a/g/os/gmlock/gmlock_locker.go +++ b/g/os/gmlock/gmlock_locker.go @@ -67,6 +67,36 @@ func (l *Locker) RUnlock(key string) { } } +// TryLockFunc locks the with write lock and callback function . +// It returns true if success, or else if there's a write/read lock the , it return false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) TryLockFunc(key string, f func(), expire...time.Duration) bool { + if l.TryLock(key, expire...) { + defer l.Unlock(key) + f() + return true + } + return false +} + +// TryRLockFunc locks the with read lock and callback function . +// It returns true if success, or else if there's a write lock the , it returns false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) TryRLockFunc(key string, f func()) bool { + if l.TryRLock(key) { + defer l.RUnlock(key) + f() + return true + } + return false +} + // LockFunc locks the with write lock and callback function . // If there's a write/read lock the , // it will blocks until the lock is released. @@ -120,7 +150,6 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { mu.Lock() } if ok && expire > 0 { - // 异步goroutine计时处理 wid := mu.wid.Val() gtimer.AddOnce(expire, func() { if wid == mu.wid.Val() { diff --git a/g/os/gmlock/gmlock_mutex.go b/g/os/gmlock/gmlock_mutex.go index fc4d992a2..4faf7b82d 100644 --- a/g/os/gmlock/gmlock_mutex.go +++ b/g/os/gmlock/gmlock_mutex.go @@ -7,16 +7,17 @@ package gmlock import ( - "github.com/gogf/gf/g/container/gtype" - "sync" + "github.com/gogf/gf/g/container/gtype" + "sync" ) -// The mutex. +// The high level Mutex. +// It wraps the sync.RWMutex to implements more rich features. type Mutex struct { mu sync.RWMutex - wid *gtype.Int64 // Unique id for this mutex. - rcount *gtype.Int // RLock count. - wcount *gtype.Int // Lock count. + wid *gtype.Int64 // Unique id, used for multiple safely Unlock. + rcount *gtype.Int // Reading locks count. + wcount *gtype.Int // Writing locks count. } // NewMutex creates and returns a new mutex. @@ -31,72 +32,120 @@ func NewMutex() *Mutex { // Lock locks mutex for writing. // If the lock is already locked for reading or writing, // Lock blocks until the lock is available. -func (l *Mutex) Lock() { - l.wcount.Add(1) - l.mu.Lock() - l.wid.Add(1) +func (m *Mutex) Lock() { + m.wcount.Add(1) + m.mu.Lock() + m.wid.Add(1) } // Unlock unlocks the write lock. // It is safe to be called multiple times. -func (l *Mutex) Unlock() { - if l.wcount.Val() > 0 { - if l.wcount.Add(-1) >= 0 { - l.mu.Unlock() +func (m *Mutex) Unlock() { + if m.wcount.Val() > 0 { + if m.wcount.Add(-1) >= 0 { + m.mu.Unlock() } else { - l.wcount.Add(1) + m.wcount.Add(1) } } } // RLock locks mutex for reading. -func (l *Mutex) RLock() { - l.rcount.Add(1) - l.mu.RLock() +// If the mutex is already locked for writing, +// It blocks until the lock is available. +func (m *Mutex) RLock() { + m.rcount.Add(1) + m.mu.RLock() } // RUnlock undoes a single RLock call; // it does not affect other simultaneous readers. -// It is a run-time error if rw is not locked for reading +// It is a run-time error if mutex is not locked for reading // on entry to RUnlock. // It is safe to be called multiple times. -func (l *Mutex) RUnlock() { - if l.rcount.Val() > 0 { - if l.rcount.Add(-1) >= 0 { - l.mu.RUnlock() +func (m *Mutex) RUnlock() { + if m.rcount.Val() > 0 { + if m.rcount.Add(-1) >= 0 { + m.mu.RUnlock() } else { - l.rcount.Add(1) + m.rcount.Add(1) } } } -// TryLock tries locking the mutex with write lock, -// it returns true if success, or if there's a write/read lock the mutex, +// TryLock tries locking the mutex for writing. +// It returns true if success, or if there's a write/read lock on the mutex, // it returns false. -func (l *Mutex) TryLock() bool { +func (m *Mutex) TryLock() bool { // The first check, but it cannot ensure the atomicity. - if l.wcount.Val() == 0 && l.rcount.Val() == 0 { - // The second check, it can ensure the atomicity. - if l.wcount.Add(1) == 1 { - l.mu.Lock() - l.wid.Add(1) + if m.wcount.Val() == 0 && m.rcount.Val() == 0 { + // The second check, it ensures the atomicity with atomic Add. + if m.wcount.Add(1) == 1 { + m.mu.Lock() + m.wid.Add(1) return true } else { - l.wcount.Add(-1) + m.wcount.Add(-1) } } return false } -// TryRLock tries locking the mutex with read lock. -// It returns true if success, or if there's a write lock on mutex, it returns false. -func (l *Mutex) TryRLock() bool { +// TryRLock tries locking the mutex for reading. +// It returns true if success, or if there's a write lock on the mutex, it returns false. +func (m *Mutex) TryRLock() bool { // There must be no write lock on mutex. - if l.wcount.Val() == 0 { - l.rcount.Add(1) - l.mu.RLock() + if m.wcount.Val() == 0 { + m.rcount.Add(1) + m.mu.RLock() return true } return false } +// TryLockFunc tries locking the mutex for writing with given callback function . +// it returns true if success, or if there's a write/read lock on the mutex, +// it returns false. +// +// It releases the lock after is executed. +func (m *Mutex) TryLockFunc(f func()) bool { + if m.TryLock() { + defer m.Unlock() + f() + return true + } + return false +} + +// TryRLockFunc tries locking the mutex for reading with given callback function . +// It returns true if success, or if there's a write lock on the mutex, it returns false. +// +// It releases the lock after is executed. +func (m *Mutex) TryRLockFunc(f func()) bool { + if m.TryRLock() { + defer m.RUnlock() + f() + return true + } + return false +} + +// LockFunc locks the mutex for writing with given callback function . +// If there's a write/read lock the mutex, it will blocks until the lock is released. +// +// It releases the lock after is executed. +func (m *Mutex) LockFunc(f func()) { + m.Lock() + defer m.Unlock() + f() +} + +// RLockFunc locks the mutex for reading with given callback function . +// If there's a write lock the mutex, it will blocks until the lock is released. +// +// It releases the lock after is executed. +func (m *Mutex) RLockFunc(f func()) { + m.RLock() + defer m.RUnlock() + f() +} From a908e4d4b1174ccd78215888fb551759cf814104 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 7 Jun 2019 22:27:37 +0800 Subject: [PATCH 17/24] fix issue in session initialization for ghttp.Server --- TODO.MD | 2 +- g/net/ghttp/ghttp_server_session.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.MD b/TODO.MD index f68a8f50e..072b2c7d4 100644 --- a/TODO.MD +++ b/TODO.MD @@ -47,7 +47,7 @@ 1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进; 1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式; 1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现; - +1. grpool增加支持阻塞添加任务接口; diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 706b18e76..be2a6959f 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -58,7 +58,7 @@ func (s *Session) init() { // 否则执行初始化创建 s.id = s.request.Cookie.MakeSessionId() s.data = gmap.NewStrAnyMap() - s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()) + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) } } From c0c97b76fb485dc04e7b55bcc4e877fbf6ffa5b7 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 7 Jun 2019 23:00:34 +0800 Subject: [PATCH 18/24] update lead string position for glog --- g/os/glog/glog_logger.go | 13 +++++++------ geg/os/glog/glog_line.go | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 7b9329c5f..5d6562d98 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -243,6 +243,13 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { if len(timeFormat) > 0 { buffer.WriteString(time.Now().Format(timeFormat)) } + // Lead string. + if len(lead) > 0 { + buffer.WriteString(lead) + if len(value) > 0 { + buffer.WriteByte(' ') + } + } // Caller path. callerPath := "" if l.flags & F_FILE_LONG > 0 { @@ -259,12 +266,6 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { buffer.WriteString(l.prefix + " ") } } - if len(lead) > 0 { - buffer.WriteString(lead) - if len(value) > 0 { - buffer.WriteByte(' ') - } - } for k, v := range value { if k > 0 { buffer.WriteByte(' ') diff --git a/geg/os/glog/glog_line.go b/geg/os/glog/glog_line.go index 41d2e0779..ab28cb622 100644 --- a/geg/os/glog/glog_line.go +++ b/geg/os/glog/glog_line.go @@ -5,6 +5,6 @@ import ( ) func main() { - glog.Line().Println("this is the short file name with its line number") - glog.Line(true).Println("lone file name with line number") + glog.Line().Debug("this is the short file name with its line number") + glog.Line(true).Debug("lone file name with line number") } From f9826104d8f566bfe8455fa335e3b7ffcec6de4c Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jun 2019 19:25:20 +0800 Subject: [PATCH 19/24] improve gconv --- g/util/gconv/gconv_struct.go | 28 +++++++++++++++------ g/util/gconv/gconv_z_unit_struct_test.go | 32 +++++++++++++++++++----- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index 8ffe29a09..d5cc119c4 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -157,7 +157,9 @@ func StructDeep(params interface{}, pointer interface{}, mapping...map[string]st trv := rv.Field(i) switch trv.Kind() { case reflect.Struct: - StructDeep(params, trv, mapping...) + if err := StructDeep(params, trv, mapping...); err != nil { + return err + } } } } @@ -239,7 +241,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er switch structFieldValue.Kind() { // 属性为结构体 case reflect.Struct: - Struct(value, structFieldValue) + if err := Struct(value, structFieldValue); err != nil { + structFieldValue.Set(reflect.ValueOf(value)) + } // 属性为数组类型 case reflect.Slice: fallthrough @@ -253,11 +257,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er for i := 0; i < v.Len(); i++ { if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e) } } @@ -267,11 +275,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er t := a.Index(0).Type() if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e) } } @@ -280,7 +292,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er // 属性为指针类型 case reflect.Ptr: e := reflect.New(structFieldValue.Type().Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } structFieldValue.Set(e.Addr()) default: diff --git a/g/util/gconv/gconv_z_unit_struct_test.go b/g/util/gconv/gconv_z_unit_struct_test.go index 35d4274f8..0efe7987f 100644 --- a/g/util/gconv/gconv_z_unit_struct_test.go +++ b/g/util/gconv/gconv_z_unit_struct_test.go @@ -7,12 +7,12 @@ package gconv_test import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/os/gtime" - "github.com/gogf/gf/g/test/gtest" - "github.com/gogf/gf/g/util/gconv" - "testing" - "time" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/g/test/gtest" + "github.com/gogf/gf/g/util/gconv" + "testing" + "time" ) func Test_Struct_Basic1(t *testing.T) { @@ -104,6 +104,26 @@ func Test_Struct_Basic2(t *testing.T) { }) } +// 带有指针的基础类型属性 +func Test_Struct_Basic3(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Uid int + Name *string + } + user := new(User) + params := g.Map { + "uid" : 1, + "Name" : "john", + } + if err := gconv.Struct(params, user); err != nil { + gtest.Error(err) + } + gtest.Assert(user.Uid, 1) + gtest.Assert(*user.Name, "john") + }) +} + // slice类型属性的赋值 func Test_Struct_Attr_Slice(t *testing.T) { gtest.Case(t, func() { From 136ad3b0b527a233a1b92a8deee8453e634422bf Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jun 2019 19:38:46 +0800 Subject: [PATCH 20/24] add more unit test cases for hook feature of ghttp.Server --- g/net/ghttp/ghttp_unit_router_hook_test.go | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/g/net/ghttp/ghttp_unit_router_hook_test.go b/g/net/ghttp/ghttp_unit_router_hook_test.go index 3fb7bf3c2..581c10c8d 100644 --- a/g/net/ghttp/ghttp_unit_router_hook_test.go +++ b/g/net/ghttp/ghttp_unit_router_hook_test.go @@ -85,3 +85,36 @@ func Test_Router_Hook_Priority(t *testing.T) { }) } +func Test_Router_Hook_Multi(t *testing.T) { + p := ports.PopRand() + s := g.Server(p) + s.BindHandler("/multi-hook", func(r *ghttp.Request) { + r.Response.Write("show") + }) + + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("1") + }, + }) + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("2") + }, + }) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + gtest.Assert(client.GetContent("/"), "Not Found") + gtest.Assert(client.GetContent("/multi-hook"), "12show") + }) +} + From e400a94ffb9ffb935bc38b3d681404242f2e6146 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 9 Jun 2019 10:33:16 +0800 Subject: [PATCH 21/24] improving grpool --- g/container/gchan/gchan.go | 8 +++++--- g/container/gqueue/gqueue.go | 6 +++--- g/os/grpool/grpool.go | 12 +++++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/g/container/gchan/gchan.go b/g/container/gchan/gchan.go index 6b6be6da3..9e762162f 100644 --- a/g/container/gchan/gchan.go +++ b/g/container/gchan/gchan.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gchan provides graceful channel for safe operations. +// Package gchan provides graceful channel for no panic operations. // // It's safe to call Chan.Push/Close functions repeatedly. package gchan @@ -14,12 +14,13 @@ import ( "github.com/gogf/gf/g/container/gtype" ) +// Graceful channel. type Chan struct { channel chan interface{} closed *gtype.Bool } -// New creates a graceful channel with given limit. +// New creates a graceful channel with given . func New(limit int) *Chan { return &Chan { channel : make(chan interface{}, limit), @@ -31,7 +32,7 @@ func New(limit int) *Chan { // It is safe to be called repeatedly. func (c *Chan) Push(value interface{}) error { if c.closed.Val() { - return errors.New("closed") + return errors.New("channel is closed") } c.channel <- value return nil @@ -39,6 +40,7 @@ func (c *Chan) Push(value interface{}) error { // Pop pops value from channel. // If there's no value in channel, it would block to wait. +// If the channel is closed, it will return a nil value immediately. func (c *Chan) Pop() interface{} { return <- c.channel } diff --git a/g/container/gqueue/gqueue.go b/g/container/gqueue/gqueue.go index ea99c0c6f..9ec7b12c7 100644 --- a/g/container/gqueue/gqueue.go +++ b/g/container/gqueue/gqueue.go @@ -37,8 +37,8 @@ const ( ) // New returns an empty queue object. -// Optional parameter is used to limit the size of the queue, which is unlimited by default. -// When is given, the queue will be static and high performance which is comparable with stdlib chan. +// Optional parameter is used to limit the size of the queue, which is unlimited in default. +// When is given, the queue will be static and high performance which is comparable with stdlib channel. func New(limit...int) *Queue { q := &Queue { closed : make(chan struct{}, 0), @@ -103,7 +103,7 @@ func (q *Queue) Pop() interface{} { // Close closes the queue. // Notice: It would notify all goroutines return immediately, -// which are being blocked reading by Pop method. +// which are being blocked reading using Pop method. func (q *Queue) Close() { close(q.C) close(q.events) diff --git a/g/os/grpool/grpool.go b/g/os/grpool/grpool.go index 8d2bf116c..675e15193 100644 --- a/g/os/grpool/grpool.go +++ b/g/os/grpool/grpool.go @@ -14,10 +14,11 @@ import ( // Goroutine Pool type Pool struct { - limit int // Max goroutine count limit. - count *gtype.Int // Current running goroutine count. - list *glist.List // Job list. - closed *gtype.Bool // Is pool closed or not. + limit int // Max goroutine count limit. + count *gtype.Int // Current running goroutine count. + list *glist.List // Job list for asynchronous job adding purpose. + closed *gtype.Bool // Is pool closed or not. + workers chan struct{} // Goroutine workers using channel to implements blocking feature. } // Default goroutine pool. @@ -33,7 +34,7 @@ func New(limit...int) *Pool { list : glist.New(), closed : gtype.NewBool(), } - if len(limit) > 0 { + if len(limit) > 0 && limit[0] > 0 { p.limit = limit[0] } return p @@ -72,6 +73,7 @@ func (p *Pool) Add(f func()) { p.fork() } + // Size returns current goroutine count of the pool. func (p *Pool) Size() int { return p.count.Val() From 0b8c9713e690fa68d640884c3b97128934fd4033 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 9 Jun 2019 10:37:35 +0800 Subject: [PATCH 22/24] improve grpool --- g/os/grpool/grpool.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/g/os/grpool/grpool.go b/g/os/grpool/grpool.go index 675e15193..9add6fe8b 100644 --- a/g/os/grpool/grpool.go +++ b/g/os/grpool/grpool.go @@ -14,11 +14,10 @@ import ( // Goroutine Pool type Pool struct { - limit int // Max goroutine count limit. - count *gtype.Int // Current running goroutine count. - list *glist.List // Job list for asynchronous job adding purpose. - closed *gtype.Bool // Is pool closed or not. - workers chan struct{} // Goroutine workers using channel to implements blocking feature. + limit int // Max goroutine count limit. + count *gtype.Int // Current running goroutine count. + list *glist.List // Job list for asynchronous job adding purpose. + closed *gtype.Bool // Is pool closed or not. } // Default goroutine pool. From cc0a385c22d841214c96dcde04514827201786e8 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 9 Jun 2019 11:56:58 +0800 Subject: [PATCH 23/24] RELEASE updates --- RELEASE.MD | 60 +++++++++++++++++++++++++++++++++++++++++++++ g/os/gtime/gtime.go | 2 -- geg/other/test.go | 16 +++--------- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/RELEASE.MD b/RELEASE.MD index 93ce84596..31a031b1a 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,3 +1,63 @@ +# `v1.7.0` +## 新功能/改进 +1. 重构改进`glog`模块: + - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 + - 增加日志内容的异步输出特性:https://goframe.org/os/glog/async + - 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json + - 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags + - 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer + - 修改`SetStdPrint`方法名为`SetStdoutPrint` + - 修改链式方法`StdPrint`方法名为`Stdout` + - 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出 + - 新增更多的链式方法支持:https://goframe.org/os/glog/chain +1. 重构改进`gmap`模块: + - 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap` + - 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap` + - 改进`Map/Keys/Values`方法以提高性能 + - 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes` + - 新增更多功能方法支持:https://goframe.org/container/gmap/index +1. 改进`gtime`时间模块: + - 增加并完善更多的类`PHP`时间格式支持 + - 新增更多功能方法,如`FormatTo`/`LayoutTo`等等 + - 详见开发文档:https://goframe.org/os/gtime/index +1. 改进`gdb`数据库模块: + - 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior + - 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句 + - 其他的细节处理改进 +1. 改进`gtcp`通信模块: + - 完善处理细节,提高通信性能; + - 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls + - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg + - TCP服务端增加`Close`方法 + - 更多细节查看开发文档:https://goframe.org/net/gtcp/index +1. 改进`gconv`类型转换模块 + - 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration` + - 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换 + - 详见开发文档:https://goframe.org/util/gconv/struct +1. 改进`ghttp`模块: + - 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs + - 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性 + - 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址 + - `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client +1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index +1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index +1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index +1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index +1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes` +1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index +1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境; +1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index +1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理 +1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法 +1. 去除`gvar.VarRead`接口类型支持 + +## Bug Fix +1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突; +1. 修复`gcron.AddOnce`方法的细节逻辑问题; +1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误; +1. 修复`gview`模板引擎的并发安全问题; +1. 修复`ghttp.Server`的SESSION初始化过期时间问题; + # `v1.6.0` (2019-04-09) ## 新功能/改进 diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index 5e45231a9..abd557567 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -5,8 +5,6 @@ // You can obtain one at https://github.com/gogf/gf. // Package gtime provides functionality for measuring and displaying time. -// -// 时间管理. package gtime import ( diff --git a/geg/other/test.go b/geg/other/test.go index 729ac6946..9f3000841 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,19 +1,11 @@ package main import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" + "fmt" + "github.com/gogf/gf/g/os/gtime" ) func main() { - s := g.Server() - s.BindHandler("/", func(r *ghttp.Request) { - rs :="GF(Go Frame)是一款模块化、松耦合、生产级的Go应用开发框架。提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、路由注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。" - //此行压测会提示map并发错误 webbench -c 8000 -t 60 http://IP 局域网两台机器测试 - r.Response.WriteTplContent(rs, g.Map{ - "Contentb": 1, - }) - }) - s.SetPort(8199) - s.Run() + fmt.Println(gtime.Now().Format("U")) + fmt.Println(gtime.Second()) } \ No newline at end of file From aa73c5ed530f50f5736bc900b957b93847337836 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 10 Jun 2019 09:01:19 +0800 Subject: [PATCH 24/24] version updates --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 9dae0d748..ad997480a 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.6.17" +const VERSION = "v1.7.0" const AUTHORS = "john"