// Publish sends a single MQTT PUBLISH message to the server. On completion, the // supplied OnCompleteFunc is called. For QOS 0 messages, onComplete is called // immediately after the message is sent to the outgoing buffer. For QOS 1 messages, // onComplete is called when PUBACK is received. For QOS 2 messages, onComplete is // called after the PUBCOMP message is received. func (this *Server) Publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error { if err := this.checkConfiguration(); err != nil { return err } if msg.Retain() { if err := this.topicsMgr.Retain(msg); err != nil { glog.Errorf("Error retaining message: %v", err) } } if err := this.topicsMgr.Subscribers(msg.Topic(), msg.QoS(), &this.subs, &this.qoss); err != nil { return err } msg.SetRetain(false) //glog.Debugf("(server) Publishing to topic %q and %d subscribers", string(msg.Topic()), len(this.subs)) for _, s := range this.subs { if s != nil { fn, ok := s.(*OnPublishFunc) if !ok { glog.Errorf("Invalid onPublish Function") } else { (*fn)(msg) } } } return nil }
// onPublish() is called when the server receives a PUBLISH message AND have completed // the ack cycle. This method will get the list of subscribers based on the publish // topic, and publishes the message to the list of subscribers. func (this *service) onPublish(msg *message.PublishMessage) error { if msg.Retain() { if err := this.topicsMgr.Retain(msg); err != nil { glog.Errorf("(%s) Error retaining message: %v", this.cid(), err) } } err := this.topicsMgr.Subscribers(msg.Topic(), msg.QoS(), &this.subs, &this.qoss) if err != nil { glog.Errorf("(%s) Error retrieving subscribers list: %v", this.cid(), err) return err } msg.SetRetain(false) //glog.Debugf("(%s) Publishing to topic %q and %d subscribers", this.cid(), string(msg.Topic()), len(this.subs)) for _, s := range this.subs { if s != nil { fn, ok := s.(*OnPublishFunc) if !ok { glog.Errorf("Invalid onPublish Function") return fmt.Errorf("Invalid onPublish Function") } else { (*fn)(msg) } } } return nil }
func main() { svr := &service.Server{ KeepAlive: keepAlive, ConnectTimeout: connectTimeout, AckTimeout: ackTimeout, TimeoutRetries: timeoutRetries, SessionsProvider: sessionsProvider, TopicsProvider: topicsProvider, } var f *os.File var err error if cpuprofile != "" { f, err = os.Create(cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) } sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt, os.Kill) go func() { sig := <-sigchan glog.Errorf("Existing due to trapped signal; %v", sig) if f != nil { glog.Errorf("Stopping profile") pprof.StopCPUProfile() f.Close() } svr.Close() os.Exit(0) }() mqttaddr := "tcp://:1883" if len(wsAddr) > 0 || len(wssAddr) > 0 { addr := "tcp://127.0.0.1:1883" AddWebsocketHandler("/mqtt", addr) /* start a plain websocket listener */ if len(wsAddr) > 0 { go ListenAndServeWebsocket(wsAddr) } /* start a secure websocket listener */ if len(wssAddr) > 0 && len(wssCertPath) > 0 && len(wssKeyPath) > 0 { go ListenAndServeWebsocketSecure(wssAddr, wssCertPath, wssKeyPath) } } /* create plain MQTT listener */ err = svr.ListenAndServe(mqttaddr) if err != nil { glog.Errorf("surgemq/main: %v", err) } }
func profile() { var f *os.File var err error if cpuprofile != "" { f, err = os.Create(cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) } sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt, os.Kill) go func() { select { case sig := <-sigchan: log.Printf("Existing due to trapped signal; %v", sig) case <-quit: log.Println("Quiting...") } if f != nil { glog.Errorf("Stopping profile") pprof.StopCPUProfile() f.Close() } close(done) os.Exit(0) }() }
func (this *service) start() error { var err error // Create the incoming ring buffer this.in, err = newBuffer(defaultBufferSize) if err != nil { return err } // Create the outgoing ring buffer this.out, err = newBuffer(defaultBufferSize) if err != nil { return err } // If this is a server if !this.client { // Creat the onPublishFunc so it can be used for published messages this.onpub = func(msg *message.PublishMessage) error { if err := this.publish(msg, nil); err != nil { glog.Errorf("service/onPublish: Error publishing message: %v", err) return err } return nil } // If this is a recovered session, then add any topics it subscribed before topics, qoss, err := this.sess.Topics() if err != nil { return err } else { for i, t := range topics { this.topicsMgr.Subscribe([]byte(t), qoss[i], &this.onpub) } } } // Processor is responsible for reading messages out of the buffer and processing // them accordingly. this.wgStarted.Add(1) this.wgStopped.Add(1) go this.processor() // Receiver is responsible for reading from the connection and putting data into // a buffer. this.wgStarted.Add(1) this.wgStopped.Add(1) go this.receiver() // Sender is responsible for writing data in the buffer into the connection. this.wgStarted.Add(1) this.wgStopped.Add(1) go this.sender() // Wait for all the goroutines to start before returning this.wgStarted.Wait() return nil }
func main() { svr := &service.Server{ KeepAlive: keepAlive, ConnectTimeout: connectTimeout, AckTimeout: ackTimeout, TimeoutRetries: timeoutRetries, SessionsProvider: sessionsProvider, TopicsProvider: topicsProvider, } var f *os.File var err error if cpuprofile != "" { f, err = os.Create(cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) } sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt, os.Kill) go func() { sig := <-sigchan glog.Errorf("Existing due to trapped signal; %v", sig) if f != nil { glog.Errorf("Stopping profile") pprof.StopCPUProfile() f.Close() } svr.Close() os.Exit(0) }() err = svr.ListenAndServe("tcp://:1883") if err != nil { glog.Errorf("surgemq/main: %v", err) } }
// ListenAndServe listents to connections on the URI requested, and handles any // incoming MQTT client sessions. It should not return until Close() is called // or if there's some critical error that stops the server from running. The URI // supplied should be of the form "protocol://host:port" that can be parsed by // url.Parse(). For example, an URI could be "tcp://0.0.0.0:1883". func (this *Server) ListenAndServe(uri string) error { defer atomic.CompareAndSwapInt32(&this.running, 1, 0) if !atomic.CompareAndSwapInt32(&this.running, 0, 1) { return fmt.Errorf("server/ListenAndServe: Server is already running") } this.quit = make(chan struct{}) u, err := url.Parse(uri) if err != nil { return err } this.ln, err = net.Listen(u.Scheme, u.Host) if err != nil { return err } defer this.ln.Close() glog.Infof("server/ListenAndServe: server is ready...") var tempDelay time.Duration // how long to sleep on accept failure for { conn, err := this.ln.Accept() if err != nil { // http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/ select { case <-this.quit: return nil default: } // Borrowed from go1.3.3/src/pkg/net/http/server.go:1699 if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } glog.Errorf("server/ListenAndServe: Accept error: %v; retrying in %v", err, tempDelay) time.Sleep(tempDelay) continue } return err } go this.handleConnection(conn) } }
// receiver() reads data from the network, and writes the data into the incoming buffer func (this *service) receiver() { defer func() { // Let's recover from panic if r := recover(); r != nil { glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) } this.wgStopped.Done() glog.Debugf("(%s) Stopping receiver", this.cid()) }() glog.Debugf("(%s) Starting receiver", this.cid()) this.wgStarted.Done() switch conn := this.conn.(type) { case net.Conn: //glog.Debugf("server/handleConnection: Setting read deadline to %d", time.Second*time.Duration(this.keepAlive)) keepAlive := time.Second * time.Duration(this.keepAlive) r := timeoutReader{ d: keepAlive + (keepAlive / 2), conn: conn, } for { _, err := this.in.ReadFrom(r) if err != nil { if err != io.EOF { glog.Errorf("(%s) error reading from connection: %v", this.cid(), err) } return } } //case *websocket.Conn: // glog.Errorf("(%s) Websocket: %v", this.cid(), ErrInvalidConnectionType) default: glog.Errorf("(%s) %v", this.cid(), ErrInvalidConnectionType) } }
func AddWebsocketHandler(urlPattern string, uri string) error { glog.Debugf("AddWebsocketHandler urlPattern=%s, uri=%s", urlPattern, uri) u, err := url.Parse(uri) if err != nil { glog.Errorf("surgemq/main: %v", err) return err } h := func(ws *websocket.Conn) { WebsocketTcpProxy(ws, u.Scheme, u.Host) } http.Handle(urlPattern, websocket.Handler(h)) return nil }
func startFanPublisher(t testing.TB, cid int, wg *sync.WaitGroup) { now := time.Now() runClientTest(t, cid, wg, func(svc *service.Client) { select { case <-done: case <-time.After(time.Second * time.Duration(subscribers)): glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) return } cnt := messages sent := 0 payload := make([]byte, size) msg := message.NewPublishMessage() msg.SetTopic(topic) msg.SetQoS(qos) for i := 0; i < cnt; i++ { binary.BigEndian.PutUint32(payload, uint32(cid*cnt+i)) msg.SetPayload(payload) err := svc.Publish(msg, nil) if err != nil { break } sent++ } since := time.Since(now).Nanoseconds() statMu.Lock() totalSent += int64(sent) totalSentTime += int64(since) if since > sentSince { sentSince = since } statMu.Unlock() glog.Debugf("(surgemq%d) Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, sent, since, int(float64(since)/float64(cnt)), int(float64(sent)/(float64(since)/float64(time.Second)))) select { case <-done2: case <-time.Tick(time.Second * time.Duration(nap*publishers)): glog.Errorf("Timed out waiting for messages to be received.") } }) }
// sender() writes data from the outgoing buffer to the network func (this *service) sender() { defer func() { // Let's recover from panic if r := recover(); r != nil { glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) } this.wgStopped.Done() glog.Debugf("(%s) Stopping sender", this.cid()) }() glog.Debugf("(%s) Starting sender", this.cid()) this.wgStarted.Done() switch conn := this.conn.(type) { case net.Conn: for { _, err := this.out.WriteTo(conn) if err != nil { if err != io.EOF { glog.Errorf("(%s) error writing data: %v", this.cid(), err) } return } } //case *websocket.Conn: // glog.Errorf("(%s) Websocket not supported", this.cid()) default: glog.Errorf("(%s) Invalid connection type", this.cid()) } }
// For SUBSCRIBE message, we should add subscriber, then send back SUBACK func (this *service) processSubscribe(msg *message.SubscribeMessage) error { resp := message.NewSubackMessage() resp.SetPacketId(msg.PacketId()) // Subscribe to the different topics var retcodes []byte topics := msg.Topics() qos := msg.Qos() this.rmsgs = this.rmsgs[0:0] for i, t := range topics { rqos, err := this.topicsMgr.Subscribe(t, qos[i], &this.onpub) if err != nil { return err } this.sess.AddTopic(string(t), qos[i]) retcodes = append(retcodes, rqos) // yeah I am not checking errors here. If there's an error we don't want the // subscription to stop, just let it go. this.topicsMgr.Retained(t, &this.rmsgs) glog.Debugf("(%s) topic = %s, retained count = %d", this.cid(), string(t), len(this.rmsgs)) } if err := resp.AddReturnCodes(retcodes); err != nil { return err } if _, err := this.writeMessage(resp); err != nil { return err } for _, rm := range this.rmsgs { if err := this.publish(rm, nil); err != nil { glog.Errorf("service/processSubscribe: Error publishing retained message: %v", err) return err } } return nil }
// FIXME: The order of closing here causes panic sometimes. For example, if receiver // calls this, and closes the buffers, somehow it causes buffer.go:476 to panid. func (this *service) stop() { defer func() { // Let's recover from panic if r := recover(); r != nil { glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) } }() doit := atomic.CompareAndSwapInt64(&this.closed, 0, 1) if !doit { return } // Close quit channel, effectively telling all the goroutines it's time to quit if this.done != nil { glog.Debugf("(%s) closing this.done", this.cid()) close(this.done) } // Close the network connection if this.conn != nil { glog.Debugf("(%s) closing this.conn", this.cid()) this.conn.Close() } this.in.Close() this.out.Close() // Wait for all the goroutines to stop. this.wgStopped.Wait() glog.Debugf("(%s) Received %d bytes in %d messages.", this.cid(), this.inStat.bytes, this.inStat.msgs) glog.Debugf("(%s) Sent %d bytes in %d messages.", this.cid(), this.outStat.bytes, this.outStat.msgs) // Unsubscribe from all the topics for this client, only for the server side though if !this.client && this.sess != nil { topics, _, err := this.sess.Topics() if err != nil { glog.Errorf("(%s/%d): %v", this.cid(), this.id, err) } else { for _, t := range topics { if err := this.topicsMgr.Unsubscribe([]byte(t), &this.onpub); err != nil { glog.Errorf("(%s): Error unsubscribing topic %q: %v", this.cid(), t, err) } } } } // Publish will message if WillFlag is set. Server side only. if !this.client && this.sess.Cmsg.WillFlag() { glog.Infof("(%s) service/stop: connection unexpectedly closed. Sending Will.", this.cid()) this.onPublish(this.sess.Will) } // Remove the client topics manager if this.client { topics.Unregister(this.sess.ID()) } // Remove the session from session store if it's suppose to be clean session if this.sess.Cmsg.CleanSession() && this.sessMgr != nil { this.sessMgr.Del(this.sess.ID()) } this.conn = nil this.in = nil this.out = nil }
// processor() reads messages from the incoming buffer and processes them func (this *service) processor() { defer func() { // Let's recover from panic if r := recover(); r != nil { //glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) } this.wgStopped.Done() this.stop() //glog.Debugf("(%s) Stopping processor", this.cid()) }() glog.Debugf("(%s) Starting processor", this.cid()) this.wgStarted.Done() for { // 1. Find out what message is next and the size of the message mtype, total, err := this.peekMessageSize() if err != nil { //if err != io.EOF { glog.Errorf("(%s) Error peeking next message size: %v", this.cid(), err) //} return } msg, n, err := this.peekMessage(mtype, total) if err != nil { //if err != io.EOF { glog.Errorf("(%s) Error peeking next message: %v", this.cid(), err) //} return } //glog.Debugf("(%s) Received: %s", this.cid(), msg) this.inStat.increment(int64(n)) // 5. Process the read message err = this.processIncoming(msg) if err != nil { if err != errDisconnect { glog.Errorf("(%s) Error processing %s: %v", this.cid(), msg.Name(), err) } else { return } } // 7. We should commit the bytes in the buffer so we can move on _, err = this.in.ReadCommit(total) if err != nil { if err != io.EOF { glog.Errorf("(%s) Error committing %d read bytes: %v", this.cid(), total, err) } return } // 7. Check to see if done is closed, if so, exit if this.isDone() && this.in.Len() == 0 { return } //if this.inStat.msgs%1000 == 0 { // glog.Debugf("(%s) Going to process message %d", this.cid(), this.inStat.msgs) //} } }
func (this *service) processAcked(ackq *sessions.Ackqueue) { for _, ackmsg := range ackq.Acked() { // Let's get the messages from the saved message byte slices. msg, err := ackmsg.Mtype.New() if err != nil { glog.Errorf("process/processAcked: Unable to creating new %s message: %v", ackmsg.Mtype, err) continue } if _, err := msg.Decode(ackmsg.Msgbuf); err != nil { glog.Errorf("process/processAcked: Unable to decode %s message: %v", ackmsg.Mtype, err) continue } ack, err := ackmsg.State.New() if err != nil { glog.Errorf("process/processAcked: Unable to creating new %s message: %v", ackmsg.State, err) continue } if _, err := ack.Decode(ackmsg.Ackbuf); err != nil { glog.Errorf("process/processAcked: Unable to decode %s message: %v", ackmsg.State, err) continue } //glog.Debugf("(%s) Processing acked message: %v", this.cid(), ack) // - PUBACK if it's QoS 1 message. This is on the client side. // - PUBREL if it's QoS 2 message. This is on the server side. // - PUBCOMP if it's QoS 2 message. This is on the client side. // - SUBACK if it's a subscribe message. This is on the client side. // - UNSUBACK if it's a unsubscribe message. This is on the client side. switch ackmsg.State { case message.PUBREL: // If ack is PUBREL, that means the QoS 2 message sent by a remote client is // releassed, so let's publish it to other subscribers. if err = this.onPublish(msg.(*message.PublishMessage)); err != nil { glog.Errorf("(%s) Error processing ack'ed %s message: %v", this.cid(), ackmsg.Mtype, err) } case message.PUBACK, message.PUBCOMP, message.SUBACK, message.UNSUBACK, message.PINGRESP: glog.Debugf("process/processAcked: %s", ack) // If ack is PUBACK, that means the QoS 1 message sent by this service got // ack'ed. There's nothing to do other than calling onComplete() below. // If ack is PUBCOMP, that means the QoS 2 message sent by this service got // ack'ed. There's nothing to do other than calling onComplete() below. // If ack is SUBACK, that means the SUBSCRIBE message sent by this service // got ack'ed. There's nothing to do other than calling onComplete() below. // If ack is UNSUBACK, that means the SUBSCRIBE message sent by this service // got ack'ed. There's nothing to do other than calling onComplete() below. // If ack is PINGRESP, that means the PINGREQ message sent by this service // got ack'ed. There's nothing to do other than calling onComplete() below. err = nil default: glog.Errorf("(%s) Invalid ack message type %s.", this.cid(), ackmsg.State) continue } // Call the registered onComplete function if ackmsg.OnComplete != nil { onComplete, ok := ackmsg.OnComplete.(OnCompleteFunc) if !ok { glog.Errorf("process/processAcked: Error type asserting onComplete function: %v", reflect.TypeOf(ackmsg.OnComplete)) } else if onComplete != nil { if err := onComplete(msg, ack, nil); err != nil { glog.Errorf("process/processAcked: Error running onComplete(): %v", err) } } } } }
func startMeshClient(t testing.TB, cid int, wg *sync.WaitGroup) { runClientTest(t, cid, wg, func(svc *service.Client) { done2 := make(chan struct{}) cnt := messages expected := publishers * cnt received := 0 sent := 0 now := time.Now() since := time.Since(now).Nanoseconds() sub := newSubscribeMessage("test", 0) svc.Subscribe(sub, func(msg, ack message.Message, err error) error { subs := atomic.AddInt64(&subdone, 1) if subs == int64(publishers) { close(done) } return nil }, func(msg *message.PublishMessage) error { if received == 0 { now = time.Now() } received++ //glog.Debugf("(surgemq%d) messages received=%d", cid, received) since = time.Since(now).Nanoseconds() if received == expected { close(done2) } return nil }) select { case <-done: case <-time.After(time.Second * time.Duration(publishers)): glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) return } payload := make([]byte, size) msg := message.NewPublishMessage() msg.SetTopic(topic) msg.SetQoS(qos) go func() { now := time.Now() for i := 0; i < cnt; i++ { binary.BigEndian.PutUint32(payload, uint32(cid*cnt+i)) msg.SetPayload(payload) err := svc.Publish(msg, nil) if err != nil { break } sent++ } since := time.Since(now).Nanoseconds() statMu.Lock() totalSent += int64(sent) totalSentTime += int64(since) if since > sentSince { sentSince = since } statMu.Unlock() glog.Debugf("(surgemq%d) Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, sent, since, int(float64(since)/float64(cnt)), int(float64(sent)/(float64(since)/float64(time.Second)))) }() select { case <-done2: case <-time.Tick(time.Second * time.Duration(nap*publishers)): glog.Errorf("Timed out waiting for messages to be received.") } statMu.Lock() totalRcvd += int64(received) totalRcvdTime += int64(since) if since > rcvdSince { rcvdSince = since } statMu.Unlock() glog.Debugf("(surgemq%d) Received %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, received, since, int(float64(since)/float64(cnt)), int(float64(received)/(float64(since)/float64(time.Second)))) }) }
func startFanSubscribers(t testing.TB, cid int, wg *sync.WaitGroup) { now := time.Now() runClientTest(t, cid, wg, func(svc *service.Client) { cnt := messages * publishers received := 0 since := time.Since(now).Nanoseconds() sub := newSubscribeMessage("test", 0) svc.Subscribe(sub, func(msg, ack message.Message, err error) error { subs := atomic.AddInt64(&subdone, 1) if subs == int64(subscribers) { now = time.Now() close(done) } return nil }, func(msg *message.PublishMessage) error { if received == 0 { now = time.Now() } received++ //glog.Debugf("(surgemq%d) messages received=%d", cid, received) since = time.Since(now).Nanoseconds() if received == cnt { rcvd := atomic.AddInt64(&rcvdone, 1) if rcvd == int64(subscribers) { close(done2) } } return nil }) select { case <-done: case <-time.After(time.Second * time.Duration(subscribers)): glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) return } select { case <-done2: case <-time.Tick(time.Second * time.Duration(nap*publishers)): glog.Errorf("Timed out waiting for messages to be received.") } statMu.Lock() totalRcvd += int64(received) totalRcvdTime += int64(since) if since > rcvdSince { rcvdSince = since } statMu.Unlock() glog.Debugf("(surgemq%d) Received %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, received, since, int(float64(since)/float64(cnt)), int(float64(received)/(float64(since)/float64(time.Second)))) }) }