Browse Source

提交修改依赖

master
xyiege 4 months ago
parent
commit
8e41b2084e
  1. 12
      mblog/app/config.go
  2. 414
      mblog/app/myserver.go
  3. 21
      mblog/go.mod
  4. 73
      mblog/go.sum
  5. 49
      mblog/mbmain.go

12
mblog/app/config.go

@ -0,0 +1,12 @@
package app
type Config struct {
Host string
Port int
User string
Pass string
ServerId int
LogFile string
Position int
}

414
mblog/app/myserver.go

@ -0,0 +1,414 @@
package app
import (
"bufio"
"bytes"
"context"
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"time"
"github.com/go-mysql-org/go-mysql/replication"
)
const (
MinProtocolVersion byte = 10
OK_HEADER byte = 0x00
ERR_HEADER byte = 0xff
EOF_HEADER byte = 0xfe
LocalInFile_HEADER byte = 0xfb
)
const MaxPayloadLength = 1<<24 - 1
type Server struct {
Cfg *Config
Ctx context.Context
conn net.Conn
io *PacketIo
registerSucc bool
}
func (s *Server) Run() {
defer func() {
s.Quit()
}()
s.dump()
}
func (s *Server) dump() {
err := s.handshake()
if err != nil {
panic(err)
}
s.invalidChecksum()
fmt.Println("dump ...")
s.register()
s.writeDumpCommand()
parser := replication.NewBinlogParser()
for {
//time.Sleep(2 * time.Second)
//s.query("select 1")
data, err := s.io.readPacket()
if err != nil || len(data) == 0 {
continue
}
//s.Quit()
if data[0] == OK_HEADER {
//skip ok
data = data[1:]
if e, err := parser.Parse(data); err == nil {
e.Dump(os.Stdout)
} else {
fmt.Println(err)
}
} else {
s.io.HandleError(data)
}
}
}
func (s *Server) invalidChecksum() {
sql := `SET @master_binlog_checksum='NONE'`
if err := s.query(sql); err != nil {
fmt.Println(err)
}
//must read from tcp connection , either will be blocked
_, _ = s.io.readPacket()
}
func (s *Server) handshake() error {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", s.Cfg.Host, s.Cfg.Port), 10*time.Second)
if err != nil {
return err
}
tc := conn.(*net.TCPConn)
tc.SetKeepAlive(true)
tc.SetNoDelay(true)
s.conn = tc
s.io = &PacketIo{}
s.io.r = bufio.NewReaderSize(s.conn, 16*1024)
s.io.w = tc
data, err := s.io.readPacket()
if err != nil {
return err
}
if data[0] == ERR_HEADER {
return errors.New("error packet")
}
if data[0] < MinProtocolVersion {
return fmt.Errorf("version is too lower, current:%d", data[0])
}
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1
connId := uint32(binary.LittleEndian.Uint32(data[pos : pos+4]))
pos += 4
salt := data[pos : pos+8]
pos += 8 + 1
capability := uint32(binary.LittleEndian.Uint16(data[pos : pos+2]))
pos += 2
var status uint16
var pluginName string
if len(data) > pos {
//skip charset
pos++
status = binary.LittleEndian.Uint16(data[pos : pos+2])
pos += 2
capability = uint32(binary.LittleEndian.Uint16(data[pos:pos+2]))<<16 | capability
pos += 2
pos += 10 + 1
salt = append(salt, data[pos:pos+12]...)
pos += 13
if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
pluginName = string(data[pos : pos+end])
} else {
pluginName = string(data[pos:])
}
}
fmt.Printf("conn_id:%v, status:%d, plugin:%v\n", connId, status, pluginName)
//write
capability = 500357
length := 4 + 4 + 1 + 23
length += len(s.Cfg.User) + 1
pass := []byte(s.Cfg.Pass)
auth := calPassword(salt[:20], pass)
length += 1 + len(auth)
data = make([]byte, length+4)
data[4] = byte(capability)
data[5] = byte(capability >> 8)
data[6] = byte(capability >> 16)
data[7] = byte(capability >> 24)
//utf8
data[12] = byte(33)
pos = 13 + 23
if len(s.Cfg.User) > 0 {
pos += copy(data[pos:], s.Cfg.User)
}
pos++
data[pos] = byte(len(auth))
pos += 1 + copy(data[pos+1:], auth)
err = s.io.writePacket(data)
if err != nil {
return fmt.Errorf("write auth packet error")
}
pk, err := s.io.readPacket()
if err != nil {
return err
}
if pk[0] == OK_HEADER {
fmt.Println("handshake ok ")
return nil
} else if pk[0] == ERR_HEADER {
s.io.HandleError(pk)
return errors.New("handshake error ")
}
return nil
}
func (s *Server) writeDumpCommand() {
s.io.seq = 0
data := make([]byte, 4+1+4+2+4+len(s.Cfg.LogFile))
pos := 4
data[pos] = 18 //dump binlog
pos++
binary.LittleEndian.PutUint32(data[pos:], uint32(s.Cfg.Position))
pos += 4
//dump command flag
binary.LittleEndian.PutUint16(data[pos:], 0)
pos += 2
binary.LittleEndian.PutUint32(data[pos:], uint32(s.Cfg.ServerId))
pos += 4
copy(data[pos:], s.Cfg.LogFile)
s.io.writePacket(data)
//ok
res, _ := s.io.readPacket()
if res[0] == OK_HEADER {
fmt.Println("send dump command return ok.")
} else {
s.io.HandleError(res)
}
}
func (s *Server) register() {
s.io.seq = 0
hostname, _ := os.Hostname()
data := make([]byte, 4+1+4+1+len(hostname)+1+len(s.Cfg.User)+1+len(s.Cfg.Pass)+2+4+4)
pos := 4
data[pos] = 21 //register slave command
pos++
binary.LittleEndian.PutUint32(data[pos:], uint32(s.Cfg.ServerId))
pos += 4
data[pos] = uint8(len(hostname))
pos++
n := copy(data[pos:], hostname)
pos += n
data[pos] = uint8(len(s.Cfg.User))
pos++
n = copy(data[pos:], s.Cfg.User)
pos += n
data[pos] = uint8(len(s.Cfg.Pass))
pos++
n = copy(data[pos:], s.Cfg.Pass)
pos += n
binary.LittleEndian.PutUint16(data[pos:], uint16(s.Cfg.Port))
pos += 2
binary.LittleEndian.PutUint32(data[pos:], 0)
pos += 4
//master id = 0
binary.LittleEndian.PutUint32(data[pos:], 0)
s.io.writePacket(data)
//ok
res, _ := s.io.readPacket()
if res[0] == OK_HEADER {
fmt.Println("register success.")
s.registerSucc = true
} else {
s.io.HandleError(data)
}
}
func (s *Server) writeCommand(command byte) {
s.io.seq = 0
_ = s.io.writePacket([]byte{
0x01, //1 byte long
0x00,
0x00,
0x00, //seq
command,
})
}
func (s *Server) query(q string) error {
s.io.seq = 0
length := len(q) + 1
data := make([]byte, length+4)
data[4] = 3
copy(data[5:], q)
return s.io.writePacket(data)
}
func (s *Server) Quit() {
//quit
s.writeCommand(byte(1))
//maybe only close
if err := s.conn.Close(); nil != err {
fmt.Printf("error in close :%v\n", err)
}
}
type PacketIo struct {
r *bufio.Reader
w io.Writer
seq uint8
}
func (p *PacketIo) readPacket() ([]byte, error) {
//to read header
header := []byte{0, 0, 0, 0}
if _, err := io.ReadFull(p.r, header); err != nil {
return nil, err
}
length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)
if length == 0 {
p.seq++
return []byte{}, nil
}
if length == 1 {
return nil, fmt.Errorf("invalid payload")
}
seq := uint8(header[3])
if p.seq != seq {
return nil, fmt.Errorf("invalid seq %d", seq)
}
p.seq++
data := make([]byte, length)
if _, err := io.ReadFull(p.r, data); err != nil {
return nil, err
} else {
if length < MaxPayloadLength {
return data, nil
}
var buf []byte
buf, err = p.readPacket()
if err != nil {
return nil, err
}
if len(buf) == 0 {
return data, nil
} else {
return append(data, buf...), nil
}
}
}
func (p *PacketIo) writePacket(data []byte) error {
length := len(data) - 4
if length >= MaxPayloadLength {
data[0] = 0xff
data[1] = 0xff
data[2] = 0xff
data[3] = p.seq
if n, err := p.w.Write(data[:4+MaxPayloadLength]); err != nil {
return fmt.Errorf("write find error")
} else if n != 4+MaxPayloadLength {
return fmt.Errorf("not equal max pay load length")
} else {
p.seq++
length -= MaxPayloadLength
data = data[MaxPayloadLength:]
}
}
data[0] = byte(length)
data[1] = byte(length >> 8)
data[2] = byte(length >> 16)
data[3] = p.seq
if n, err := p.w.Write(data); err != nil {
return errors.New("write find error")
} else if n != len(data) {
return errors.New("not equal length")
} else {
p.seq++
return nil
}
}
func calPassword(scramble, password []byte) []byte {
crypt := sha1.New()
crypt.Write(password)
stage1 := crypt.Sum(nil)
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}
func (p *PacketIo) HandleError(data []byte) {
pos := 1
code := binary.LittleEndian.Uint16(data[pos:])
pos += 2
pos++
state := string(data[pos : pos+5])
pos += 5
msg := string(data[pos:])
fmt.Printf("code:%d, state:%s, msg:%s\n", code, state, msg)
}

21
mblog/go.mod

@ -0,0 +1,21 @@
module mblog
go 1.24.6
require github.com/go-mysql-org/go-mysql v1.13.0
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec // indirect
github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a // indirect
github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d // indirect
github.com/shopspring/decimal v1.2.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/text v0.24.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
)

73
mblog/go.sum

@ -0,0 +1,73 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-mysql-org/go-mysql v1.13.0 h1:Hlsa5x1bX/wBFtMbdIOmb6YzyaVNBWnwrb8gSIEPMDc=
github.com/go-mysql-org/go-mysql v1.13.0/go.mod h1:FQxw17uRbFvMZFK+dPtIPufbU46nBdrGaxOw0ac9MFs=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec h1:3EiGmeJWoNixU+EwllIn26x6s4njiWRXewdx2zlYa84=
github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a h1:WIhmJBlNGmnCWH6TLMdZfNEDaiU8cFpZe3iaqDbQ0M8=
github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a/go.mod h1:ORfBOFp1eteu2odzsyaxI+b8TzJwgjwyQcGhI+9SfEA=
github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d h1:3Ej6eTuLZp25p3aH/EXdReRHY12hjZYs3RrGp7iLdag=
github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

49
mblog/mbmain.go

@ -0,0 +1,49 @@
package main
import (
"flag"
"fmt"
"mblog/app"
"os"
"os/signal"
"runtime"
"syscall"
)
var myHost = flag.String("host", "127.0.0.1", "MySQL replication host")
var myPort = flag.Int("port", 3306, "MySQL replication port")
var myUser = flag.String("user", "root", "MySQL replication user")
var myPass = flag.String("pass", "****", "MySQL replication pass")
var serverId = flag.Int("server_id", 1111, "MySQL replication server id")
func main() {
sc := make(chan os.Signal, 1)
signal.Notify(sc,
os.Kill,
os.Interrupt,
syscall.SIGHUP,
syscall.SIGQUIT,
syscall.SIGINT,
syscall.SIGTERM,
)
runtime.GOMAXPROCS(runtime.NumCPU()/4 + 1)
flag.Parse()
cfg := &app.Config{
*myHost,
*myPort,
*myUser,
*myPass,
*serverId,
"mysql-bin.000032",
3070,
}
srv := &app.Server{Cfg: cfg}
go srv.Run()
select {
case n := <-sc:
srv.Quit()
fmt.Printf("receive signal %v, closing", n)
}
}
Loading…
Cancel
Save