3. nsqlookupd 源码阅读(2) tcp handler 处理函数
每个nsqd 生产者的状态发生变化时,都会通知nsqlookup将自己从nsqlookup的DB中删除。
func (p *tcpServer) Handle(clientConn net.Conn) {
p.ctx.nsqlookupd.logf(LOG_INFO, "TCP: new client(%s)", clientConn.RemoteAddr())
// The client should initialize itself by sending a 4 byte sequence indicating
// the version of the protocol that it intends to communicate, this will allow us
// to gracefully upgrade the protocol away from text/line oriented to whatever...
buf := make([]byte, 4)
_, err := io.ReadFull(clientConn, buf)
if err != nil {
p.ctx.nsqlookupd.logf(LOG_ERROR, "failed to read protocol version - %s", err)
return
}
// protocolMagic 默认是 " V1"
protocolMagic := string(buf)
p.ctx.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): desired protocol magic '%s'",
clientConn.RemoteAddr(), protocolMagic)
var prot protocol.Protocol
switch protocolMagic {
case " V1":
prot = &LookupProtocolV1{ctx: p.ctx}
default:
protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL"))
clientConn.Close()
p.ctx.nsqlookupd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'",
clientConn.RemoteAddr(), protocolMagic)
return
}
// 循环读取消息
err = prot.IOLoop(clientConn)
if err != nil {
p.ctx.nsqlookupd.logf(LOG_ERROR, "client(%s) - %s", clientConn.RemoteAddr(), err)
return
}
}
目前的 protocolMagic 只支持 V1 ,所以客户端在连接的时候,服务端首先要验证conn发过来的protocolMagic 是不是v1 是的话,new一个Protocol ,调用其IOLoop 循环读取方法。
func (p *LookupProtocolV1) IOLoop(conn net.Conn) error {
var err error
var line string
client := NewClientV1(conn)
reader := bufio.NewReader(client)
for {
line, err = reader.ReadString('\n')
if err != nil {
break
}
line = strings.TrimSpace(line)
params := strings.Split(line, " ")
fmt.Println("params", params)
var response []byte
response, err = p.Exec(client, reader, params)
if err != nil {
ctx := ""
if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil {
ctx = " - " + parentErr.Error()
}
p.ctx.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, err, ctx)
_, sendErr := protocol.SendResponse(client, []byte(err.Error()))
if sendErr != nil {
p.ctx.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx)
break
}
// errors of type FatalClientErr should forceably close the connection
if _, ok := err.(*protocol.FatalClientErr); ok {
break
}
continue
}
if response != nil {
_, err = protocol.SendResponse(client, response)
if err != nil {
break
}
}
}
conn.Close()
p.ctx.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): closing", client)
if client.peerInfo != nil {
registrations := p.ctx.nsqlookupd.DB.LookupRegistrations(client.peerInfo.id)
for _, r := range registrations {
if removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
client, r.Category, r.Key, r.SubKey)
}
}
}
return err
}
IOLoop 方法会不停的读取客户端的数据信息,并且默认有以下四种类型。
类型 | 功能 | 备注 |
---|---|---|
PING | nsqd 客户端通知nsqlookup 自己还活着 | |
IDENTIFY | nsqd客户端首次连接nsqlookupd的时候,首先进行鉴定,以及记录这个生产者 | |
REGISTER | 新的topic 或者channel创建的时候,通知nsqlookupd 进行注册 | |
UNREGISTER | 删除时,通知注销 |
客户端nsqd链接的时候首先发送一个IDENTIFY 消息类型,进行鉴定,鉴定通过后会将nsqd存在topic信息等进