github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/server/server.go (about) 1 // The MIT License (MIT) 2 // 3 // Copyright (c) 2014 wandoulabs 4 // Copyright (c) 2014 siddontang 5 // 6 // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 // this software and associated documentation files (the "Software"), to deal in 8 // the Software without restriction, including without limitation the rights to 9 // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 // the Software, and to permit persons to whom the Software is furnished to do so, 11 // subject to the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be included in all 14 // copies or substantial portions of the Software. 15 16 // Copyright 2015 PingCAP, Inc. 17 // 18 // Licensed under the Apache License, Version 2.0 (the "License"); 19 // you may not use this file except in compliance with the License. 20 // You may obtain a copy of the License at 21 // 22 // http://www.apache.org/licenses/LICENSE-2.0 23 // 24 // Unless required by applicable law or agreed to in writing, software 25 // distributed under the License is distributed on an "AS IS" BASIS, 26 // See the License for the specific language governing permissions and 27 // limitations under the License. 28 29 package server 30 31 import ( 32 "encoding/json" 33 "math/rand" 34 "net" 35 "net/http" 36 "sync" 37 "sync/atomic" 38 "time" 39 40 "github.com/insionng/yougam/libraries/juju/errors" 41 "github.com/insionng/yougam/libraries/ngaut/log" 42 "github.com/insionng/yougam/libraries/pingcap/tidb" 43 "github.com/insionng/yougam/libraries/pingcap/tidb/mysql" 44 "github.com/insionng/yougam/libraries/pingcap/tidb/util/arena" 45 ) 46 47 var ( 48 baseConnID uint32 = 10000 49 ) 50 51 // Server is the MySQL protocol server 52 type Server struct { 53 cfg *Config 54 driver IDriver 55 listener net.Listener 56 rwlock *sync.RWMutex 57 concurrentLimiter *TokenLimiter 58 clients map[uint32]*clientConn 59 } 60 61 // ConnectionCount gets current connection count. 62 func (s *Server) ConnectionCount() int { 63 return len(s.clients) 64 } 65 66 func (s *Server) getToken() *Token { 67 return s.concurrentLimiter.Get() 68 } 69 70 func (s *Server) releaseToken(token *Token) { 71 s.concurrentLimiter.Put(token) 72 } 73 74 // Generate a random string using ASCII characters but avoid separator character. 75 // See: https://yougam/libraries/mysql/mysql-server/blob/5.7/mysys_ssl/crypt_genhash_impl.cc#L435 76 func randomBuf(size int) []byte { 77 buf := make([]byte, size) 78 for i := 0; i < size; i++ { 79 buf[i] = byte(rand.Intn(127)) 80 if buf[i] == 0 || buf[i] == byte('$') { 81 buf[i]++ 82 } 83 } 84 return buf 85 } 86 87 func (s *Server) newConn(conn net.Conn) (cc *clientConn, err error) { 88 log.Info("newConn", conn.RemoteAddr().String()) 89 cc = &clientConn{ 90 conn: conn, 91 pkg: newPacketIO(conn), 92 server: s, 93 connectionID: atomic.AddUint32(&baseConnID, 1), 94 collation: mysql.DefaultCollationID, 95 charset: mysql.DefaultCharset, 96 alloc: arena.NewAllocator(32 * 1024), 97 } 98 cc.salt = randomBuf(20) 99 return 100 } 101 102 func (s *Server) skipAuth() bool { 103 return s.cfg.SkipAuth 104 } 105 106 // NewServer creates a new Server. 107 func NewServer(cfg *Config, driver IDriver) (*Server, error) { 108 s := &Server{ 109 cfg: cfg, 110 driver: driver, 111 concurrentLimiter: NewTokenLimiter(100), 112 rwlock: &sync.RWMutex{}, 113 clients: make(map[uint32]*clientConn), 114 } 115 116 var err error 117 if cfg.Socket != "" { 118 cfg.SkipAuth = true 119 s.listener, err = net.Listen("unix", cfg.Socket) 120 } else { 121 s.listener, err = net.Listen("tcp", s.cfg.Addr) 122 } 123 if err != nil { 124 return nil, errors.Trace(err) 125 } 126 127 // Init rand seed for randomBuf() 128 rand.Seed(time.Now().UTC().UnixNano()) 129 log.Infof("Server run MySql Protocol Listen at [%s]", s.cfg.Addr) 130 return s, nil 131 } 132 133 // Run runs the server. 134 func (s *Server) Run() error { 135 136 // Start http api to report tidb info such as tps. 137 s.startStatusHTTP() 138 139 for { 140 conn, err := s.listener.Accept() 141 if err != nil { 142 if opErr, ok := err.(*net.OpError); ok { 143 if opErr.Err.Error() == "use of closed network connection" { 144 return nil 145 } 146 } 147 log.Errorf("accept error %s", err.Error()) 148 return errors.Trace(err) 149 } 150 151 go s.onConn(conn) 152 } 153 } 154 155 // Close closes the server. 156 func (s *Server) Close() { 157 s.rwlock.Lock() 158 defer s.rwlock.Unlock() 159 160 if s.listener != nil { 161 s.listener.Close() 162 s.listener = nil 163 } 164 } 165 166 func (s *Server) onConn(c net.Conn) { 167 conn, err := s.newConn(c) 168 if err != nil { 169 log.Errorf("newConn error %s", errors.ErrorStack(err)) 170 return 171 } 172 if err := conn.handshake(); err != nil { 173 log.Errorf("handshake error %s", errors.ErrorStack(err)) 174 c.Close() 175 return 176 } 177 defer func() { 178 log.Infof("close %s", conn) 179 }() 180 181 s.rwlock.Lock() 182 s.clients[conn.connectionID] = conn 183 s.rwlock.Unlock() 184 185 conn.Run() 186 } 187 188 var once sync.Once 189 190 const defaultStatusAddr = ":10080" 191 192 func (s *Server) startStatusHTTP() { 193 once.Do(func() { 194 go func() { 195 http.HandleFunc("/status", func(w http.ResponseWriter, req *http.Request) { 196 w.Header().Set("Content-Type", "application/json") 197 s := status{TPS: tidb.GetTPS(), Connections: s.ConnectionCount(), Version: mysql.ServerVersion} 198 js, err := json.Marshal(s) 199 if err != nil { 200 w.WriteHeader(http.StatusInternalServerError) 201 log.Error("Encode json error", err) 202 } else { 203 w.Write(js) 204 } 205 206 }) 207 addr := s.cfg.StatusAddr 208 if len(addr) == 0 { 209 addr = defaultStatusAddr 210 } 211 log.Fatal(http.ListenAndServe(addr, nil)) 212 }() 213 }) 214 } 215 216 // TiDB status 217 type status struct { 218 TPS int64 `json:"tps"` 219 Connections int `json:"connections"` 220 Version string `json:"version"` 221 }