func (s *EngineSuite) BenchmarkSimple(c *C) { log.SetupLogging("error", "stdout") engine := CreateEngine() go engine.Run() mock := &MockConnection{} conn := NewMyConnection(nil) conn.SetMyConnection(mock) conn.SetId("debug") hndr := NewHandler(conn, engine) conn.SetOpaque(hndr) msg := codec.NewConnectMessage() msg.Magic = []byte("MQTT") msg.Version = uint8(4) msg.Identifier = "debug" msg.CleanSession = true msg.KeepAlive = uint16(0) // Ping is annoyed at this time codec.WriteMessageTo(msg, mock) // 6) just call conn.ParseMessage(). then handler will work. r, err := conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT) // NOTE: Client turn. don't care this. time.Sleep(time.Millisecond) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK) for i := 0; i < c.N; i++ { // (Client) Publish pub := codec.NewPublishMessage() pub.TopicName = "/debug" pub.Payload = []byte("hello") codec.WriteMessageTo(pub, mock) // (Server) conn.ParseMessage() } // That's it. engine.Terminate() }
func (self *MyConnection) writeMessage(msg codec.Message) error { self.logger.Debug("Write Message [%s]: %+v", msg.GetTypeAsString(), msg) var e error self.Mutex.Lock() self.Last = time.Now() if self.balancer != nil && self.balancer.PerSec > 0 { self.balancer.Execute(func() { _, e = codec.WriteMessageTo(msg, self.Writer) }) } else { _, e = codec.WriteMessageTo(msg, self.Writer) } if e != nil { self.Mutex.Unlock() return e } self.Last = time.Now() self.kickFlusher() self.Mutex.Unlock() return nil }
func (self *Momonga) SendPublishMessage(msg *codec.PublishMessage, client_id string, is_bridged bool) { // Don't pass wrong message here. user should validate the message be ore using this API. if len(msg.TopicName) < 1 { return } // TODO: Have to persist retain message. if msg.Retain > 0 { if len(msg.Payload) == 0 { log.Debug("[DELETE RETAIN: %s]\n%s", msg.TopicName, hex.Dump([]byte(msg.TopicName))) err := self.DataStore.Del([]byte(msg.TopicName), []byte(msg.TopicName)) if err != nil { log.Error("Error: %s\n", err) } // これ配送したらおかしいべ log.Debug("Deleted retain: %s", msg.TopicName) // あれ、ackとかかえすんだっけ? Metrics.System.Broker.Messages.RetainedCount.Add(-1) return } else { buffer := bytes.NewBuffer(nil) codec.WriteMessageTo(msg, buffer) self.DataStore.Put([]byte(msg.TopicName), buffer.Bytes()) Metrics.System.Broker.Messages.RetainedCount.Add(1) } } if Mflags["experimental.qos1"] { if msg.QosLevel == 1 { targets := self.TopicMatcher.Match(msg.TopicName) go func(msg *codec.PublishMessage, set []interface{}) { p := make(chan string, 1000) wg := sync.WaitGroup{} wg.Add(3) // bulk sernder, retry kun, receive kun mchan := make(chan string, 256) term := make(chan bool, 1) cnt := len(set) mng := make(map[string]*codec.PublishMessage) // retry kun。こういう実装だととても楽 go func(term chan bool, mchan chan string, mng map[string]*codec.PublishMessage) { for { select { case m := <-mchan: if msg, ok := mng[m]; ok { conn, err := self.GetConnectionByClientId(m) if err != nil { fmt.Printf("something wrong: %s %s", m, err) continue } if err == nil { log.Debug("sending a retry msg: %s", msg) conn.WriteMessageQueue(msg) } else { log.Debug("connection not exist. next retry") } } case <-term: log.Debug(" retry finished.") wg.Done() return } } }(term, mchan, mng) // reader go func(p chan string, term chan bool, cnt int, mng map[string]*codec.PublishMessage, mchan chan string) { limit := time.After(time.Second * 60) for { select { case id := <-p: cnt-- delete(mng, id) // これはcallbackでやってもいいようなきもするけど、wait groupとかもろもろ渡すの面倒くさい if cnt < 1 { log.Debug(" all delivery finished.") term <- true wg.Done() return } case <-time.After(time.Second * 20): // 終わってないやつをなめてリトライさせる for cid, m := range mng { m.Dupe = true mchan <- cid } case <-limit: log.Debug(" gave up retry.") term <- true wg.Done() return } } }(p, term, cnt, mng, mchan) // sender. これは勝手に終わる go func(msg *codec.PublishMessage, set []interface{}, p chan string, mng map[string]*codec.PublishMessage) { dp := make(map[string]bool) for i := range targets { var tbl *util.MessageTable var ok bool myset := targets[i].(*SubscribeSet) fmt.Printf("myset: %s", myset) // NOTE (from interoperability/client_test.py): // // overlapping subscriptions. When there is more than one matching subscription for the same client for a topic, // the server may send back one message with the highest QoS of any matching subscription, or one message for // each subscription with a matching QoS. // // Currently, We choose one message for each subscription with a matching QoS. // if _, ok := dp[myset.ClientId]; ok { continue } dp[myset.ClientId] = true x, _ := codec.CopyPublishMessage(msg) x.QosLevel = myset.QoS conn, err := self.GetConnectionByClientId(myset.ClientId) // これは面倒臭い。clean sessionがtrueで再接続した時はもはや別人として扱わなければならない if x.QosLevel == 0 { // QoS 0にダウングレードしたらそのまま終わらせる conn.WriteMessageQueue(x) p <- myset.ClientId continue } if tbl, ok = self.InflightTable[myset.ClientId]; !ok { self.InflightTable[myset.ClientId] = util.NewMessageTable() // callback仕込めるんだよなー。QoS1なら使わなくてもいいかなー。とかおもったり tbl = self.InflightTable[myset.ClientId] } id := tbl.NewId() x.PacketIdentifier = id x.Opaque = p tbl.Register2(id, x, 1, x) if err != nil { continue } mng[myset.ClientId] = x conn.WriteMessageQueue(x) } log.Debug(" all fisrt delivery finished.") wg.Done() }(msg, targets, p, mng) wg.Wait() close(p) close(mchan) close(term) mng = nil log.Debug(" okay, cleanup qos1 sending thread.") }(msg, targets) return } } // Publishで受け取ったMessageIdのやつのCountをとっておく // で、Pubackが帰ってきたらrefcountを下げて0になったらMessageを消す //log.Debug("TopicName: %s %s", m.TopicName, m.Payload) targets := self.TopicMatcher.Match(msg.TopicName) if msg.TopicName[0:1] == "#" { // TODO: [MQTT-4.7.2-1] The Server MUST NOT match Topic Filters starting with a wildcard character // (# or +) with Topic Names beginning with a $ character } // list つくってからとって、だとタイミング的に居ない奴も出てくるんだよな。マジカオス // ここで必要なのは, connection(clientId), subscribed qosがわかればあとは投げるだけ // なんで、Qlobberがかえすのはであるべきなんだけど。これすっげー消しづらいのよね・・・ // { // Connection: Connection or client id // QoS: // } // いやまぁエラーハンドリングちゃんとやってれば問題ない。 // client idのほうがベターだな。Connectionを無駄に参照つけると後が辛い dp := make(map[string]bool) count := 0 for i := range targets { var cn Connection var ok error myset := targets[i].(*SubscribeSet) clientId := myset.ClientId //clientId := targets[i].(string) // NOTE (from interoperability/client_test.py): // // overlapping subscriptions. When there is more than one matching subscription for the same client for a topic, // the server may send back one message with the highest QoS of any matching subscription, or one message for // each subscription with a matching QoS. // // Currently, We choose one message for each subscription with a matching QoS. // if _, ok := dp[clientId]; ok { continue } dp[clientId] = true cn, ok = self.GetConnectionByClientId(clientId) if ok != nil { // どちらかというとClientが悪いと思うよ! // リスト拾った瞬間にはいたけど、その後いなくなったんだから配送リストからこれらは無視すべき log.Info("(can't fetch %s. already disconnected, or unsubscribed?)", clientId) continue } if cn.IsBridge() && clientId == client_id { // Don't send message to same bridge continue } var x *codec.PublishMessage if msg.QosLevel == 0 { // we don't need to copy message on QoS 0 x = msg } else { x = codec.MustCopyPublishMessage(msg) } subscriberQos := myset.QoS // Downgrade QoS if subscriberQos < x.QosLevel { x.QosLevel = subscriberQos } if x.QosLevel > 0 { // TODO: ClientごとにInflightTableを持つ // engineのOutGoingTableなのはとりあえず、ということ id := self.OutGoingTable.NewId() x.PacketIdentifier = id if sender, ok := x.Opaque.(Connection); ok { // TODO: ここ(でなにをするつもりだったのか・・w) self.OutGoingTable.Register2(x.PacketIdentifier, x, len(targets), sender) } } cn.WriteMessageQueue(x) count++ } Metrics.System.Broker.Messages.Sent.Add(int64(count)) }
func (s *EngineSuite) TestBasic(c *C) { log.SetupLogging("error", "stdout") // This test introduce how to setup custom MQTT server // 1) You need to setup Momonga engine like this. engine := CreateEngine() // 2) Running Engine go engine.Run() // 3) engine expects net.Conn (see MockConnection) mock := &MockConnection{} conn := NewMyConnection(nil) conn.SetMyConnection(mock) conn.SetId("debug") // 4) setup handler. This handler implementation is example. // you can customize behaviour with On method. hndr := NewHandler(conn, engine) conn.SetOpaque(hndr) // 5) Now, msg := codec.NewConnectMessage() msg.Magic = []byte("MQTT") msg.Version = uint8(4) msg.Identifier = "debug" msg.CleanSession = true msg.KeepAlive = uint16(0) // Ping is annoyed at this time codec.WriteMessageTo(msg, mock) // 6) just call conn.ParseMessage(). then handler will work. r, err := conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT) // NOTE: Client turn. don't care this. time.Sleep(time.Millisecond) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK) // Subscribe sub := codec.NewSubscribeMessage() sub.Payload = append(sub.Payload, codec.SubscribePayload{TopicPath: "/debug"}) sub.PacketIdentifier = 1 codec.WriteMessageTo(sub, mock) // (Server) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBSCRIBE) rsub := r.(*codec.SubscribeMessage) c.Assert(rsub.PacketIdentifier, Equals, uint16(1)) c.Assert(rsub.Payload[0].TopicPath, Equals, "/debug") // (Client) suback time.Sleep(time.Millisecond) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBACK) // (Client) Publish pub := codec.NewPublishMessage() pub.TopicName = "/debug" pub.Payload = []byte("hello") codec.WriteMessageTo(pub, mock) // (Server) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH) rpub := r.(*codec.PublishMessage) c.Assert(rpub.TopicName, Equals, "/debug") // (Client) received publish message time.Sleep(time.Millisecond) r, err = conn.ParseMessage() rp := r.(*codec.PublishMessage) c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH) c.Assert(rp.TopicName, Equals, "/debug") c.Assert(string(rp.Payload), Equals, "hello") // okay, now disconnect from server dis := codec.NewDisconnectMessage() codec.WriteMessageTo(dis, mock) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_DISCONNECT) // then, receive EOF from server. time.Sleep(time.Millisecond) r, err = conn.ParseMessage() c.Assert(err, Equals, nil) // That's it. engine.Terminate() }