📜 ⬆️ ⬇️

Chat on Go (part 1)

We start the development of chat on Go. With a stack of technologies have not yet decided, but first we will make a framework for Go. We take as a basis the standard example and try to figure out what is what:


https://github.com/golang-samples/websocket/tree/master/websocket-chat


Structure


We enter 3 structures Message, Client, Server, which define the server, the client from the server side and the message.


Message


The message is defined by the structure:


type Message struct { Author string `json:"author"` Body string `json:"body"` } func (self *Message) String() string { return self.Author + " says " + self.Body } 

With the message, everything is quite simple ... So, let's go straight to the client.


Client


The client is defined by the structure and has an id, a link to the socket ws websocket.Conn, a link to the server, server , channel to send messages ch chan * Message, channel to complete doneCh chan bool.


 type Client struct { id int ws *websocket.Conn server *Server ch chan *Message doneCh chan bool } 

The Listen method starts the process of listening to the read and write to the socket.


 func (c *Client) Listen() { go c.listenWrite() c.listenRead() } 

The record listening method starts an infinite loop in which we check the channels. As soon as a message arrives on the c.ch channel, we send it to the websocket.JSON.Send socket (c.ws, msg). And if it arrives in the c.doneCh channel, then we complete the cycle and the gorutin.


 func (c *Client) listenWrite() { ... for { select { // send message to the client case msg := <-c.ch: websocket.JSON.Send(c.ws, msg) // receive done request case <-c.doneCh: c.server.Del(c) c.doneCh <- true // for listenRead method return } } } 

The method of listening to the reading, also starts an infinite loop and also listens to the channels. Upon the arrival of a message in the channel c.doneCh - completes the cycle and gorutina. And by default, it polls the websocket.JSON.Receive socket (c.ws, & msg). And only in the socket there is a message, it is sent to the server c.server.SendAll (& msg) for mass distribution to all clients.


 func (c *Client) listenRead() { ... for { select { // receive done request case <-c.doneCh: c.server.Del(c) c.doneCh <- true // for listenWrite method return // read data from websocket connection default: var msg Message err := websocket.JSON.Receive(c.ws, &msg) if err == io.EOF { c.doneCh <- true } else if err != nil { c.server.Err(err) } else { c.server.SendAll(&msg) } } } } 

Server


Now let's look at the server. It is defined by the structure and has a string to determine the path that the pattern string server will work on, an array for storing user messages messages [] Message, a client storage card by client id clients map [int] Client, channels for adding a new client to the list of clients addCh chan Client and to remove a client from the delCh chan Client client list , a channel to send all messages sendAllCh chan * Message, channels to complete doneCh chan bool and for errors errCh chan error


 type Server struct { pattern string messages []*Message clients map[int]*Client addCh chan *Client delCh chan *Client sendAllCh chan *Message doneCh chan bool errCh chan error } 

The most interesting method in the server is the Listen method, the rest I think is more than clear, so let's deal with it.


At the beginning, an anonymous function is implemented that will be called when accessing our server using the ws protocol along the path contained in s.pattern. When calling this function, we create a new client, add it to the server and tell the client to listen ... client: = NewClient (ws, s) ... s.Add (client) ... client.Listen ()


 func (s *Server) Listen() { ... onConnected := func(ws *websocket.Conn) { defer func() { err := ws.Close() if err != nil { s.errCh <- err } }() client := NewClient(ws, s) s.Add(client) client.Listen() } http.Handle(s.pattern, websocket.Handler(onConnected)) ... } 

In the second part of the method, an infinite loop is started, in which channels are polled.


In principle, everything is intuitive here, but let's go through:


  • Arrives in the channel s.addCh = add the arriving client to the card s.clients by client id s.clients [c.id] = c and send all messages to the new client s.sendPastMessages ©
  • to the channel s.delCh = delete the client from the map s.clients by client id delete (s.clients, c.id)
  • in the channel s.sendAllCh = add the arriving message to the message array s.messages s.messages = append (s.messages, msg) and let the server send the message to all clients s.sendAll (msg)
  • channel s.errCh = display error
  • to channel s.doneCh = complete infinite loop and gorutin

 func (s *Server) Listen() { ... for { select { // Add new a client case c := <-s.addCh: s.clients[c.id] = c s.sendPastMessages(c) // del a client case c := <-s.delCh: delete(s.clients, c.id) // broadcast message for all clients case msg := <-s.sendAllCh: s.messages = append(s.messages, msg) s.sendAll(msg) case err := <-s.errCh: log.Println("Error:", err.Error()) case <-s.doneCh: return } } } 

So, we have a fairly good frame to start developing.


Let's define the main cases for our chat:


  1. Login / logout
  2. Find user by login
  3. Start a private chat with 1 user
  4. Start a conference with 2 or more users at once (1st case)
  5. Invite 1 or more users to the existing private chat (start the conference 2nd case)
  6. View a list of private chats and conferences
  7. Exit conference
  8. View or edit your profile
  9. View another user profile

The main cases are defined, you can start development.


The development process will be covered in the following sections. As a result, we get a working prototype, which can be developed at its discretion.


')

Source: https://habr.com/ru/post/306840/


All Articles