func LoadConfig() { config_file := flag.String("config", "", "Use -config <filesource>") config_url := flag.String("config_url", "", "Use -config_url <filesource>") flag.Parse() utils.GetParentDirectory(utils.GetCurrentDirectory()) if len(*config_file) > 1 { fmt.Println("读取配置文件: " + *config_file) utils.LoadJsonFile(*config_file, C) } else if len(*config_url) > 1 { fmt.Println("读取配置文件: " + *config_url) utils.LoadJsonURL(*config_url, C) } log.Release = (C.Release != 0) Release = (C.Release != 0) if Release { log.Trace("Release模式运行") } else { log.Trace("Debug模式运行") } //一些特殊值 if C.LoginTimeOut <= 1 { C.LoginTimeOut = 3 } log.Debug("%v", C) }
func clientCall(min int32) { startTime := currentTimeMillis() transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory()) protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() transport, err := thrift.NewTSocket(NetworkAddr) for err != nil { transport, err = thrift.NewTSocket(NetworkAddr) if err != nil { log.Error("error resolving address:", err) } time.Sleep(1 * time.Second) } useTransport := transportFactory.GetTransport(transport) client := demo.NewRpcServiceClientFactory(useTransport, protocolFactory) if err := transport.Open(); err != nil { log.Error("Error opening socket to 127.0.0.1:19090", " ", err) return } defer transport.Close() for i := min; i < min+3; i++ { r1, e1 := client.Add(i, i+1) log.Trace("%d %s %v %v", i, "Call->", r1, e1) } endTime := currentTimeMillis() log.Trace("Program exit. time->", endTime, startTime, (endTime - startTime)) }
//登录SuperService func (service *superRpc) Login(req *msg.LoginRequst, res *msg.LoginRespose) error { log.Trace("[SUPER] %s(%s) 的连接到来", utils.GetServiceName(int(req.GetServiceType())), string(req.GetServiceIp())) res.ServiceType = proto.Uint32(req.GetServiceType()) res.ServiceId = proto.Uint64(100) res.ServiceIp = proto.String(req.GetServiceIp()) res.RetCode = proto.Uint32(1) res.ExterPort = proto.Uint32(300) log.Trace("[SUPER] 返回 %s 服务器ID %d", utils.GetServiceName(int(res.GetServiceType())), res.GetServiceId()) return nil }
//检查参数合法性 func (server *TCPServer) check_valid() { if server.MaxConnNum <= 0 { server.MaxConnNum = 100 log.Trace("invalid MaxConnNum, reset to %v", server.MaxConnNum) } if server.PendingWriteNum <= 0 { server.PendingWriteNum = 100 log.Trace("invalid PendingWriteNum, reset to %v", server.PendingWriteNum) } if server.NewAgent == nil { log.Fatal("NewAgent must not be nil") } }
//获取微信服务器的Token func RefreshAccessToken(loop bool) { type wxJsonToken struct { WeiXinError AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` } wxToken := new(WeiXinAccessToken) if !wxToken.Load(wxProfile.AppID) { wxToken.ExpireIn = 0 wxProfile.AccessToken = "" } else { wxProfile.AccessToken = wxToken.AccessToken log.Trace("加载数据库微信Accesstoken: %s", wxProfile.AccessToken) } for loop { newSec := time.Now().UnixNano() / time.Second.Nanoseconds() for newSec < wxToken.ExpireIn { time.Sleep(time.Second) newSec = time.Now().UnixNano() / time.Second.Nanoseconds() } wxProfile.AccessToken = "" log.Trace("微信Accesstoken过期,重新获取Token") url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET" url = strings.Replace(url, "APPID", wxProfile.AppID, -1) url = strings.Replace(url, "APPSECRET", wxProfile.AppSecret, -1) //log.Debug(url) res, err := http.Get(url) if err != nil { log.Error("获取Token失败 %v", err) } result, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Error("解析Token失败 %v", err) } log.Debug(string(result)) var jsonData wxJsonToken if err := json.Unmarshal(result, &jsonData); err == nil { wxProfile.AccessToken = jsonData.AccessToken log.Trace("收到Token: %s", wxProfile.AccessToken) wxToken.AccessToken = jsonData.AccessToken wxToken.ExpireIn = newSec + int64(jsonData.ExpiresIn) - 60*10 wxToken.Save() } else { log.Error(err.Error()) } } }
//刷新JsApiTicket func RefreshJsApiTicket(loop bool) { type wxJsonToken struct { WeiXinError JsApiTicket string `json:"ticket"` ExpiresIn int `json:"expires_in"` } wxTicket := new(WeiXinJsApiTicket) if !wxTicket.Load(wxProfile.AppID) { wxTicket.ExpireIn = 0 } else { wxProfile.JsApiTicket = wxTicket.JsApiTicket log.Trace("加载数据库微信JsApiTicket: %s", wxProfile.JsApiTicket) } for loop { newSec := time.Now().UnixNano() / time.Second.Nanoseconds() for newSec < wxTicket.ExpireIn || len(wxProfile.AccessToken) == 0 { time.Sleep(time.Second) newSec = time.Now().UnixNano() / time.Second.Nanoseconds() } log.Trace("微信JsApiTicket过期,重新获取JsApiTicket") url := "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi" url = strings.Replace(url, "ACCESS_TOKEN", wxProfile.AccessToken, -1) //log.Debug(url) res, err := http.Get(url) if err != nil { log.Error("获取JsApiTicket失败 %v", err) } result, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Error("解析JsApiTicket失败 %v", err) } log.Debug(string(result)) var jsonData wxJsonToken if err := json.Unmarshal(result, &jsonData); err == nil { wxProfile.JsApiTicket = wxTicket.JsApiTicket log.Trace("收到JsApiTicket: %s", wxProfile.AccessToken) wxTicket.JsApiTicket = jsonData.JsApiTicket wxTicket.ExpireIn = newSec + int64(jsonData.ExpiresIn) - 60*10 wxTicket.Save() } else { log.Error(err.Error()) } } }
func handleStatic(w http.ResponseWriter, r *http.Request) { path := r.URL.Path index := strings.LastIndex(path, ".") if index != -1 { request_type := path[index:] switch request_type { case ".css": w.Header().Set("content-type", "text/css") case ".js": w.Header().Set("content-type", "text/javascript") default: } } path = baseDir + path log.Trace("获取静态文件: %s", path) fin, err := os.Open(path) defer fin.Close() if err != nil { log.Error("static resource: %v", err) w.WriteHeader(505) } else { fd, _ := ioutil.ReadAll(fin) w.Write(fd) } }
func (g *gateService) StartService(superRpcAddr string) { superClient, err := msg.DialSuperService("tcp", superRpcAddr) maxRetry := 10 for err != nil { if maxRetry > 0 { maxRetry = maxRetry - 1 } else { log.Fatal("连接SuperService失败") return } log.Error("连接SuperService失败,1秒后重试 :%v", err) time.Sleep(time.Second * 1) superClient, err = msg.DialSuperService("tcp", superRpcAddr) } res, err := superClient.Login(&msg.LoginRequst{ServiceIp: proto.String("127.0.0.1")}) if err != nil { log.Fatal("[GATE] 登录SuperService失败 rpc:%s", superRpcAddr) return } g.SuperClient = superClient g.gateway.Addr = string(res.GetServiceIp()) + ":" + strconv.Itoa(int(res.GetExterPort())) g.gateway.HTTPTimeout = 3 * 60 g.gateway.MaxConnNum = 1000 g.gateway.PendingWriteNum = 1000 g.gateway.ProtobufProcessor = g.processor log.Trace("[GATE] 网关服务在%s:%d 启动", string(res.GetServiceIp()), res.GetExterPort()) g.gateway.Run(utils.CloseSig) }
//配置合理性检查 func (server *WSServer) checkValid() { if server.MaxConnNum <= 0 { server.MaxConnNum = 100 log.Trace("invalid MaxConnNum, reset to %v", server.MaxConnNum) } if server.PendingWriteNum <= 0 { server.PendingWriteNum = 100 log.Trace("invalid PendingWriteNum, reset to %v", server.PendingWriteNum) } if server.NewAgent == nil { log.Fatal("NewAgent must not be nil") } if server.HTTPTimeout <= 0 { server.HTTPTimeout = 10 * time.Second log.Trace("invalid HTTPTimeout, reset to %v", server.HTTPTimeout) } }
func (t *EchoService) Echo(args *Msg.EchoRequest, reply *Msg.EchoResponse) error { //t.Lock() //defer t.Unlock() reply.Msg = proto.String("Echo:" + args.GetMsg()) t.count++ log.Trace(strconv.Itoa(int(t.count))) time.Sleep(time.Second * 4) return nil }
//获取SIna授权URL func (service *superRpc) GetAuthUrlBySina(req *msg.OAuth2Request, res *msg.OAuth2Url) error { auth_sid := utils.GenEasyNextId(utils.SnowflakeSystemWork, utils.SnowflakeCatalogAuth) auth_ch := make(chan *auth.AuthUserData) service.auths[auth_sid] = auth_ch url, _ := auth.SinaOAuth2(auth_sid, func(data *auth.AuthUserData, accid uint64, err error) { if data != nil && err == nil { log.Trace("获得授权 auth_sid:%d data:%v", auth_sid, data) auth_ch <- data } else { log.Trace(err.Error()) auth_ch <- nil } }) res.Accid = proto.Uint64(req.GetAccid()) res.AuthSid = proto.Uint64(auth_sid) res.Url = proto.String(url) return nil }
func UserAuthorized(state string, token *weixin.WebAccessToken, w http.ResponseWriter, r *http.Request) bool { key := "NikeName:" + token.OpenID ret, err := nosql.Redis.Get(key).Result() if err != nil { userinfo := weixin.GetUserInfo(token.OpenID, true) if userinfo != nil { ret, err = nosql.Redis.Set(key, userinfo.NikeName, 0).Result() if err != nil { log.Debug(err.Error() + " | " + ret) } else { log.Trace("App: %s 有人授权了(new)name: %s", state, userinfo.NikeName) } } } else { log.Trace("App: %s 有人授权了name: %s", state, ret) } return true }
func InitAuthHttpService() { if default_http_service == nil { default_http_service = new(AuthHttpService) default_http_service.mux = http.NewServeMux() default_http_service.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Mustang authservice is running...")) }) log.Trace("AuthService Listen On: " + AuthServicePort) go http.ListenAndServe(":"+AuthServicePort, default_http_service.mux) } }
func (token *WeiXinJsApiTicket) Save() { var err error if sqldb.Ormer.QueryTable(token).Filter("AppID", token.AppID).Exist() { _, err = sqldb.Ormer.Update(token) } else { _, err = sqldb.Ormer.Insert(token) } if err != nil { log.Error(err.Error()) } else { log.Trace("保存微信AccessToken") } }
func main() { nosql.InitRedis(global.RedisURL, global.RedisPin, 0) sqldb.Init() mux := http.NewServeMux() weixin.InitWeiXin(&weixin.WeiXinConfig{AppID: global.AppID, AppSecret: global.AppSecret, OwnerID: global.OwnerID, ProcessMsg: nil, UserAuthorized: UserAuthorized, ServiceToken: "test_wx", MyServiceDomain: global.ServiceDomain, AuthRedirectURL: "http://" + global.ServiceDomain + "/wx/authorize_redirect"}, mux) weixin.RegistAuthRedirectUrl("FIRST_TEST", "http://"+global.ServiceDomain+"/app/demo") mux.HandleFunc("/htdoc/", handleStatic) mux.HandleFunc("/app/demo", handleAppDemo) mux.HandleFunc("/wx/api/", handleWxApi) if err := http.ListenAndServe(":3000", mux); err != nil { log.Error(err.Error()) } //if err := http.ListenAndServeTLS(":3000", baseDir+"https/cert.pem", baseDir+"https/key.pem", mux); err != nil { // log.Error(err.Error()) //} return log.Trace("启动......") go superservice.Instance.StartService(utils.SuperRpcAddr) go gateservice.Instance.StartService(utils.SuperRpcAddr) <-utils.CloseSig log.Trace("结束......") return test.TestSinaAuth() auth.InitAuthHttpService() test.TestProtorpc() utils.TestSnow() }
func (p *Processor) Register(msg proto.Message, msgHandler network.MsgHandler) { msgType := reflect.TypeOf(msg) msgName := msgType.String()[1:] if msgType == nil || msgType.Kind() != reflect.Ptr { log.Fatal("protobuf message pointer required") } if len(p.msgInfo) >= math.MaxUint16 { log.Fatal("too many protobuf messages (max = %v)", math.MaxUint16) } i := new(MsgInfo) i.msgType = msgType i.msgName = msgName i.msgHandler = msgHandler p.msgInfo[msgName] = i log.Trace("Register Msg Handle: %s", msgName) }
func TestProtorpc() { go startProtorpcService() echoClient, err := Msg.DialEchoService("tcp", NetworkAddr) for err != nil { log.Error("连接服务失败 :%v", err) time.Sleep(time.Second * 1) echoClient, err = Msg.DialEchoService("tcp", NetworkAddr) } echoClient, err = Msg.DialEchoService("tcp", NetworkAddr) wg := new(sync.WaitGroup) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() log.Debug("RPC Call Begin..." + strconv.Itoa(i)) reply, err := echoClient.Echo(&Msg.EchoRequest{Msg: proto.String("Hello!" + strconv.Itoa(i))}) if err != nil { log.Error("EchoTwice: %v", err) } log.Trace(reply.GetMsg()) log.Debug("RPC Call Finish..." + strconv.Itoa(i)) }(i) } /* client, err := protorpc.Dial("tcp", `127.0.0.1:9527`) if err != nil { log.Fatal("protorpc.Dial: %v", err) } defer client.Close() echoClient1 := &Msg.EchoServiceClient{client} echoClient2 := &Msg.EchoServiceClient{client} reply, err = echoClient1.Echo(args) log.Trace(reply.GetMsg()) reply, err = echoClient2.EchoTwice(args) log.Trace(reply.GetMsg()) _, _ = reply, err */ wg.Wait() echoClient.Close() }
func serverStub() { transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory()) protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() //protocolFactory := thrift.NewTCompactProtocolFactory() serverTransport, err := thrift.NewTServerSocket(NetworkAddr) if err != nil { log.Error("Error!", err) return } handler := &RpcServiceImpl{} processor := demo.NewRpcServiceProcessor(handler) server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory) log.Trace("thrift server in", NetworkAddr) server.Serve() }
//实现net.http.handler接口 //每次HTTP请求的时候被调用,用于产生链接 func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer utils.PrintPanicStack() log.Trace("new connect from %s(url:%s)", r.RemoteAddr, r.RequestURI) if r.Method != "GET" { http.Error(w, "Method not allowed", 405) return } conn, err := handler.upgrader.Upgrade(w, r, nil) if err != nil { log.Debug("upgrade error: %v", err) return } handler.wg.Add(1) defer handler.wg.Done() handler.mutexConns.Lock() if handler.conns == nil { handler.mutexConns.Unlock() conn.Close() log.Debug("upgrade error: nil") return } if len(handler.conns) >= handler.maxConnNum { handler.mutexConns.Unlock() conn.Close() log.Debug("too many connections") return } handler.conns[conn] = struct{}{} handler.mutexConns.Unlock() wsConn := newWSConn(conn, handler.pendingWriteNum) agent := handler.newAgent(wsConn) agent.Run() // cleanup wsConn.Close() handler.mutexConns.Lock() delete(handler.conns, conn) handler.mutexConns.Unlock() agent.OnClose() }
func (s *TcpServer) Run(closeSig chan bool) { server := &network.TCPServer{} server.Addr = s.Addr server.MaxConnNum = 1000 server.PendingWriteNum = 1000 server.NewAgent = func(conn *network.TCPConn) network.IAgent { a := new(TCPServerAgent) a.server = s a.SetConn(conn) a.SetName("TCPServerAgent") a.SetProtobufProcessor(protobuf.ProtobufProcess) s.agent = a return a } log.Trace("TCP服务器启动") server.Start() //等待close信号 <-closeSig server.Close() }
//初始化微信服务(要使用微信 就要先用这个函数初始化) //func InitWeiXin(appID, appSecret, ownerID string, mux *http.ServeMux, processFunc ProcessWeixinMessage) error { func InitWeiXin(config *WeiXinConfig, mux *http.ServeMux) error { if wxProfile == nil { log.Trace("APPID: %s ", config.AppID) wxProfile = &WeiXinProfile{} wxProfile.msgCollection = make(map[string]bool) wxProfile.authRedirectAppUrl = make(map[string]string) //wxProfile.AppID = appID //wxProfile.AppSecret = appSecret //wxProfile.OwnerID = ownerID wxProfile.WeiXinConfig = *config wxProfile.mux = mux if mux != nil { mux.HandleFunc("/wx/msg", handleWeiXinRoot) } InitWiXinOAuth() go RefreshAccessToken(true) go RefreshJsApiTicket(true) return nil } else { return errors.New("Already inited") } }
func handleAuthorize(w http.ResponseWriter, r *http.Request) { log.Trace("\n--") log.Trace("Visit: " + r.RequestURI) //Get the Google URL which shows the Authentication page to the user url, _ := auth.SinaOAuth2(123456, func(data *auth.AuthUserData, accid uint64, err error) { if data != nil && err == nil { log.Trace("获得授权 %d", accid) log.Trace("%v", data) http.Redirect(data.Response, data.Request, "http://www.tuntun.site/", http.StatusFound) } else { log.Trace(err.Error()) } }) log.Trace("URL: %v\n", url) //redirect user to that page http.Redirect(w, r, url, http.StatusFound) }
func handleWeiXinRoot(w http.ResponseWriter, r *http.Request) { log.Debug("\n--") log.Debug("Visit: " + r.RequestURI) r.ParseForm() if !checkWeiXinUrlVailed(r) { w.WriteHeader(505) log.Trace("微信认证失败") return } if r.Method == "GET" { //微信服务器验证请求 echostr := strings.Join(r.Form["echostr"], "") fmt.Fprintf(w, echostr) log.Trace("微信认证成功") } else { log.Debug("其他请求 %v", r) result, _ := ioutil.ReadAll(r.Body) r.Body.Close() log.Debug("%s\n", result) revMsg := parseRecvWeiXinMsg(result) if revMsg == nil { return } if _, ok := wxProfile.msgCollection[revMsg.MsgId]; ok == true { log.Debug("过滤重复MsgID: %s", revMsg.MsgId) return } wxProfile.msgCollection[revMsg.MsgId] = true sendMsg := new(SendWeiXinMsg) sendMsg.FromUserName = wxProfile.OwnerID sendMsg.ToUserName = revMsg.FromUserName sendMsg.CreateTime = fmt.Sprintf("%d", time.Now().Second()) if wxProfile.ProcessMsg == nil { sendMsg.MsgType = "text" if revMsg.Content == "我是谁" { info := GetUserInfo(revMsg.FromUserName, true) if info != nil { sendMsg.Content = "你是: " + info.NikeName } else { sendMsg.Content = "不认识你哦..." } } else { sendMsg.Content = "回你: " + revMsg.Content } } else { wxProfile.ProcessMsg(revMsg, sendMsg) } bytes, err := xml.Marshal(sendMsg) if err != nil { w.WriteHeader(500) log.Error(err.Error()) return } else { w.WriteHeader(200) w.Write(bytes) log.Debug("微信返回: %s", string(bytes)) } } }
func (service *superService) StartService(addr string) { go msg.ListenAndServeSuperService("tcp", addr, service.rpc) log.Trace("[SUPER] RPC在%s启动", addr) <-utils.CloseSig }
func TestSnow() { for i := 0; i < 10; i++ { log.Trace("Gen %d", GenEasyNextId(1, 1)) } }
func getUserInfo(token string, openID string, sns bool, cache bool) *WeiXinUserInfo { key := "UserInfo:" + openID if cache { ret, err := nosql.Redis.Get(key).Result() if err == nil { userinfo := &WeiXinUserInfo{} err = json.Unmarshal([]byte(ret), userinfo) if err == nil { return userinfo } else { log.Error(err.Error()) } } else { log.Error(err.Error()) } } if wxProfile == nil { log.Error("还没有获取AccessToken") return nil } if len(wxProfile.AccessToken) == 0 { log.Error("还没有获取AccessToken") return nil } url := "" if sns { url = "https://api.weixin.qq.com/sns/userinfo?access_token={{.AccessToken}}&openid={{.OpenID}}&lang=zh_CN" url = strings.Replace(url, "{{.AccessToken}}", token, -1) url = strings.Replace(url, "{{.OpenID}}", openID, -1) } else { url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={{.ACCESS_TOKEN}}&openid={{.OPENID}}&lang=zh_CN" url = strings.Replace(url, "{{.ACCESS_TOKEN}}", wxProfile.AccessToken, -1) url = strings.Replace(url, "{{.OPENID}}", openID, -1) } res, err := http.Get(url) if err != nil { log.Trace("请求URL: %s", url) log.Error("获取微信用户信息失败 %v", err) return nil } result, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Error("解析微信用户信息失败 %v", err) return nil } log.Debug(string(result)) info := &WeiXinUserInfo{} if err := json.Unmarshal(result, info); err == nil { err = nosql.Redis.Set(key, string(result), 0).Err() if err != nil { log.Error("Redis Error: %s", err.Error()) } if checkWeiXinReturn(&(info.WeiXinError)) { return info } else { return info } } else { log.Error(err.Error()) } return nil }
func InitAuthService(supply IAuthSupply, http_service *AuthHttpService) *AuthService { if http_service == nil { log.Error("AuthHttpService没有初始化, 调用InitAuthHttpService函数初始化") return nil } else { service := new(AuthService) service = new(AuthService) service.callback = make(map[uint64]AuthCallBackFunc) service.oauthCfg = &oauth2.Config{ ClientID: supply.GetClientID(), ClientSecret: supply.GetClientSecret(), Endpoint: supply.GetEndPoint(), RedirectURL: AuthServiceUrl + supply.GetRedirectURL(), Scopes: nil, } http_service.mux.HandleFunc(supply.GetRedirectURL(), func(w http.ResponseWriter, r *http.Request) { data := new(AuthUserData) data.Response = w data.Request = r data.ServiceName = "sina" log.Trace("OAuth2接口授权返回: " + r.RequestURI + "来自" + r.RemoteAddr) code := r.FormValue("code") state, err := strconv.ParseInt(r.FormValue("state"), 10, 64) if err != nil { log.Error("OAuth2接口授权返回accid错误") return } accid := uint64(state) defer delete(service.callback, accid) cb, ok := service.callback[uint64(accid)] if !ok { log.Error("没有找到授权回调函数: " + strconv.FormatUint(accid, 10)) return } var ( ctx context.Context cancel context.CancelFunc ) ctx, cancel = context.WithCancel(context.Background()) defer cancel() token, err := service.oauthCfg.Exchange(ctx, code) if err != nil { log.Error(err.Error()) cb(nil, accid, err) } else { req, err := service.oauthCfg.Client(ctx, token).Get(supply.GetProfileInfoURL(token)) if err != nil { log.Error("OAuth2接口获取用户信息错误:%s accid:%d", err.Error(), accid) cb(nil, accid, err) } else { body, _ := ioutil.ReadAll(io.LimitReader(req.Body, 1<<20)) data.Accid = accid data.AcessToken = token.AccessToken data.Raw = string(body) if err := supply.ParseAuthUserData(data, body); err == nil { cb(data, accid, nil) } else { cb(nil, accid, err) } } } }) return service } }
func (a *WSAgent) OnClose() { log.Trace("WSAgent 退出") if a.CloseFunc != nil { a.CloseFunc() } }
//关闭时调用 func (a *TCPAgent) OnClose() { log.Trace("TCPClientAgent exit") if a.CloseFunc != nil { a.CloseFunc() } }
func (a *TCPAgent) Trace(format string, d ...interface{}) { log.Trace(a.description()+format, d...) }