Codementor Events

Networking Server Library using Golang

Published Dec 02, 2020
Networking Server Library using Golang

In todays world many web and mobile applications are developed which requires real time communication between client and server frameworks especially in domains like Gaming, FinTech (AlgoTrading, Data Gathering), etc. I Would like to share more details related to this Networking server library which is used in Golang programming language and is fully opensource that can be accessed in github.

Nano is a high real time application framework which works based on the Binary communication protocol which is divided into message and package layer, package layer provides a series of communication including handshake, heartbeat and byte stream based encoding/decoding and these are transmitted using Tcp or WebSocket and is faster and secure to communicate between client and Golang server based framework.

Server Side Code to implement Nano Framework in Golang for developing a chat
application.

// These are the nano libraries to import into Server.

"github.com/lonng/nano"
"github.com/lonng/nano/component"
"github.com/lonng/nano/pipeline"
"github.com/lonng/nano/scheduler"
"github.com/lonng/nano/serialize/json"
"github.com/lonng/nano/session"

// List of types to pass data between nano client and nano server components.
 type (
   Room struct {
	 group *nano.Group
   }

 // RoomManager represents a component that contains a bundle of room
  RoomManager struct {
	component.Base
	timer *scheduler.Timer
	rooms map[int]*Room
  }

// UserMessage represents a message that user sent
UserMessage struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}

// NewUser message will be received when new user join room
NewUser struct {
	Content string `json:"content"`
}

// AllMembers contains all members uid
AllMembers struct {
	Members []int64 `json:"members"`
}

// JoinResponse represents the result of joining room
JoinResponse struct {
	Code   int    `json:"code"`
	Result string `json:"result"`
}

stats struct {
	component.Base
	timer         *scheduler.Timer
	outboundBytes int
	inboundBytes  int
}

)

// System Defined Outbound function to pushback statistics and calculate size of // message.
func (stats *stats) outbound(s *session.Session, msg *pipeline.Message) error {
stats.outboundBytes += len(msg.Data)
return nil
}

// System defined inbound function to pushback statistics and calculate size of
// communcation message.
func (stats *stats) inbound(s *session.Session, msg *pipeline.Message) error {
stats.inboundBytes += len(msg.Data)
return nil
}

func (stats *stats) AfterInit() {
stats.timer = scheduler.NewTimer(time.Minute, func() {
println("OutboundBytes", stats.outboundBytes)
println("InboundBytes", stats.outboundBytes)
})
}

const (
testRoomID = 1
roomIDKey = "ROOM_ID"
)

// Component which is initiated to create a new Chat Room.
func NewRoomManager() *RoomManager {
return &RoomManager{
rooms: map[int]*Room{},
}
}

// AfterInit component lifetime callback to close chat room and to display a log // based on each chat room.
func (mgr *RoomManager) AfterInit() {
session.Lifetime.OnClosed(func(s *session.Session) {
if !s.HasKey(roomIDKey) {
return
}
room := s.Value(roomIDKey).(*Room)
room.group.Leave(s)
})
mgr.timer = scheduler.NewTimer(time.Minute, func() {
for roomId, room := range mgr.rooms {
println(fmt.Sprintf("UserCount: RoomID=%d, Time=%s, Count=%d",
roomId, time.Now().String(), room.group.Count()))
}
})
}

//Function to Join chat room and send broadcast message to all joinned members in //client side.
func (mgr *RoomManager) Join(s *session.Session, msg []byte) error {
// NOTE: join test room only in demo
room, found := mgr.rooms[testRoomID]
if !found {
room = &Room{
group: nano.NewGroup(fmt.Sprintf("room-%d", testRoomID)),
}
mgr.rooms[testRoomID] = room
}

fakeUID := s.ID() //just use s.ID as uid !!!
s.Bind(fakeUID)   // binding session uids.Set(roomIDKey, room)
s.Set(roomIDKey, room)
s.Push("onMembers", &AllMembers{Members: room.group.Members()})
// notify others
room.group.Broadcast("onNewUser", &NewUser{Content: fmt.Sprintf("New user: %d", s.ID())})
// new user join group
room.group.Add(s) // add session to group
return s.Response(&JoinResponse{Result: "success"})

}

//Message sync last message to all members
func (mgr *RoomManager) Message(s *session.Session, msg *UserMessage) error {
if !s.HasKey(roomIDKey) {
return fmt.Errorf("not join room yet")
}
room := s.Value(roomIDKey).(*Room)
return room.group.Broadcast("onMessage", msg)
}

//Main Function
func main() {
Initialize components by registering chat room manager

components := &component.Components{}
components.Register(
	NewRoomManager(),
	component.WithName("room"), // rewrite component and handler name
	component.WithNameFunc(strings.ToLower),
)

// traffic stats
pip := pipeline.New()
var stats = &stats{}
pip.Outbound().PushBack(stats.outbound)
pip.Inbound().PushBack(stats.inbound)

log.SetFlags(log.LstdFlags | log.Llongfile)
http.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir("web"))))
// Port to listen based on tcp from client based on /nano url.
nano.Listen(":3250",
	nano.WithIsWebsocket(true),
	nano.WithPipeline(pip),
	nano.WithCheckOriginFunc(func(_ *http.Request) bool { return true }),
	nano.WithWSPath("/nano"),
	nano.WithDebugMode(),
	nano.WithSerializer(json.NewSerializer()), // override default serializer
	nano.WithComponents(components),
)

}

Client Side code using Vue.js and javascript to communicate with Server side coding.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chat Demo</title>
</head>
<body>
<div id="container">
<ul>
<li v-for="msg in messages">[<span style="color:red;">{{msg.name}}</span>]{{msg.content}}</li>
</ul>
<div class="controls">
<input type="text" v-model="nickname">
<input type="text" v-model="inputMessage">
<input type="button" v-on:click="sendMessage" value="Send">
</div>
</div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js" type="text/javascript"></script>
<!--starx websocket library-->
<script src="protocol.js" type="text/javascript"></script>
<script src="starx-wsclient.js" type="text/javascript"></script>
<script>
var v = new Vue({
el: "#container",
data: {
nickname:'guest' + Date.now(),
inputMessage:'',
messages: []
},
methods: {
sendMessage: function () {
console.log(this.inputMessage);
starx.notify('room.message', {name: this.nickname, content: this.inputMessage});
this.inputMessage = '';
}
}
});

var onMessage = function (msg) {
    v.messages.push(msg)
};

var join = function (data) {
    console.log(data);
    if(data.code === 0) {
        v.messages.push({name:'system', content:data.result});
        starx.on('onMessage', onMessage)
    }
};

var onNewUser = function (data) {
    console.log(data);
    v.messages.push({name:'system', content:data.content});
};

var onMembers = function (data) {
    console.log(data);
    v.messages.push({name:'system', content: "members: "+data.members});
};
 
<!-- Initializing nano server and initalizing new events "onNewUser" and "onMembers" -->
starx.init({host: '127.0.0.1', port: 3250, path: '/nano'}, function () {
    console.log("initialized");
    starx.on("onNewUser", onNewUser);
    starx.on("onMembers", onMembers);
    starx.request("room.join", {}, join);
})

</script>
</body>
</html>

More details can be found in github url : https://github.com/lonng/nano .

Discover and read more posts from SHAILESH B NAIR
get started