Home  >  Article  >  Backend Development  >  Chat one on one with golang socket

Chat one on one with golang socket

王林
王林forward
2024-02-08 20:45:04380browse

与 golang 套接字一对一聊天

Question content

I have a shopping app where users can post availability and other users can find them and add their availability.

I now have a chat service that is essentially for chatting. That is, customers can chat with the shopper to confirm details or other things. This chat should be one-on-one. So there might be 5 customers asking about a shopping post and I want the chat to be unique because customer A's chat about shopping should be separate from customer B's chat about the same shopping. Shoppers should be able to see the chat and respond.

This is what I currently have, but this seems to be broadcasting a message to everyone in the reference. I only want shoppers to receive messages from a specific sender without others having access to the chat.

"client.go"

type client struct {
    conn           *websocket.conn
    chatrepository chat.chatrepository
    message        chan *message
    id             string `json:"id"`
    reference      string `json:"reference"`
    username       string `json:"username"`
    sender         string `json:"sender"`
    recipient      string `json:"recipient"`
}

type message struct {
    content   string `json:"content"`
    reference string `json:"reference"`
    // username  string `json:"username"`
    sender string `json:"sender"`
}

func (c *client) writemessage() {
    defer func() {
        c.conn.close()
    }()

    for {
        message, ok := <-c.message
        if !ok {
            return
        }
        uuid, err := uuid.newv4()
        if err != nil {
            log.fatalf("failed to generate uuid: %v", err)
        }
        chatmessage := chat.chatmessage{
            id:     uuid.string(),
            sender: message.sender,
            // recipient: recipient,
            timestamp: time.now(),
            content:   message.content,
        }
        if c.sender == message.sender {
            _, errx := c.chatrepository.addmessage(message.reference, chatmessage)
            if err != nil {
                log.fatalf("failed to generate uuid: %v", errx)
            }
        }
        c.conn.writejson(chatmessage)
    }
}

func (c *client) readmessage(hub *hub) {
    defer func() {
        hub.unregister <- c
        c.conn.close()
    }()

    for {
        _, m, err := c.conn.readmessage()
        if err != nil {
            if websocket.isunexpectedcloseerror(err, websocket.closegoingaway, websocket.closeabnormalclosure) {
                log.printf("error: %v", err)
            }
            break
        }

        msg := &message{
            content:   string(m),
            reference: c.reference,
            sender:    c.sender,
            // username:  c.username,
        }

        hub.broadcast <- msg
    }
}

“hub.go”

type room struct {
    id      string             `json:"id"`
    name    string             `json:"name"`
    clients map[string]*client `json:"clients"`
}

type hub struct {
    rooms      map[string]*room
    register   chan *client
    unregister chan *client
    broadcast  chan *message
    emmiter    events.emitter
}

func newhub(emmiter events.emitter) *hub {
    return &hub{
        rooms:      make(map[string]*room),
        register:   make(chan *client),
        unregister: make(chan *client),
        broadcast:  make(chan *message, 5),
        emmiter:    emmiter,
    }
}

func (h *hub) run() {
    for {
        select {
        case cl := <-h.register:
            if _, ok := h.rooms[cl.reference]; ok {
                r := h.rooms[cl.reference]

                if _, ok := r.clients[cl.id]; !ok {
                    r.clients[cl.id] = cl
                }

            }
        case cl := <-h.unregister:
            if _, ok := h.rooms[cl.reference]; ok {
                if _, ok := h.rooms[cl.reference].clients[cl.id]; ok {
                    // if len(h.rooms[cl.reference].clients) != 0 {
                    //  h.broadcast <- &message{
                    //      content:   "user left the chat",
                    //      reference: cl.reference,
                    //      username:  cl.username,
                    //  }
                    // }

                    delete(h.rooms[cl.reference].clients, cl.id)
                    close(cl.message)
                }
            }

        case m := <-h.broadcast:
            if _, ok := h.rooms[m.reference]; ok {
                for _, cl := range h.rooms[m.reference].clients {
                    cl.message <- m
                    if m.sender != cl.recipient {
                        notifications.sendpush(h.emmiter, cl.recipient, fmt.sprintf("new message from %v", cl.username), m.content)
                    }
                }
            }
        }
    }
}

"handler.go"

type handler struct {
    hub            *hub
    chatrepository chat.chatrepository
}

func newhandler(h *hub, chatrepository chat.chatrepository) *handler {
    return &handler{
        hub:            h,
        chatrepository: chatrepository,
    }
}

var upgrader = websocket.upgrader{
    readbuffersize:  1024,
    writebuffersize: 1024,
    checkorigin: func(r *http.request) bool {
        return true
    },
}

func (h *handler) joinroom(c *gin.context) {
    conn, err := upgrader.upgrade(c.writer, c.request, nil)
    if err != nil {
        utils.handleerror(c, nil, "error creating chat connection", http.statusbadgateway)
        return
    }

    reference := c.param("reference")
    sender := c.query("sender")
    username := c.query("username")
    recipient := c.query("recipient")

    if reference == "" || sender == "" || username == "" || recipient == "" {
        utils.handleerror(c, nil, "required parameters missing", http.statusbadgateway)
        return
    }
    if _, ok := h.hub.rooms[reference]; !ok {
        // room doesn't exist, handle accordingly
        _, err1 := h.chatrepository.getchathistory(reference)
        if err1 != nil {
            log.printf("failed to retrieve chat history: %s", err1)
            errx := h.chatrepository.createchat(reference)
            if errx != nil {
                utils.handleerror(c, nil, "error storing connection", http.statusbadgateway)
                return
            }
        }
        h.hub.rooms[reference] = &room{
            id:      reference,
            name:    sender,
            clients: make(map[string]*client),
        }
    }

    cl := &client{
        conn:           conn,
        chatrepository: h.chatrepository,
        message:        make(chan *message, 10),
        id:             sender,
        reference:      reference,
        sender:         sender,
        username:       username,
        recipient:      recipient,
    }

    h.hub.register <- cl
    go cl.writemessage()
    cl.readmessage(h.hub)
}

"route.go"

hub := ws.newhub(events.neweventemitter(conn))
    wshandler := ws.newhandler(hub, pr.newchatrepository(db, client))
    go hub.run()
v1.get("/chat/ws/:reference", g.guard([]string{"user", "admin", "dispatcher"}, nil), wshandler.joinroom)
"chat.model.go"

    type Chat struct {
        ID        string        `json:"id,omitempty" bson:"_id,omitempty"`
        Reference string        `json:"reference" bson:"reference"`
        Messages  []ChatMessage `json:"messages" bson:"messages"`
    }
    type ChatMessage struct {
        ID        string    `json:"id,omitempty" bson:"_id,omitempty"`
        Sender    string    `json:"sender" bson:"sender,omitempty"`
        Timestamp time.Time `json:"timestamp" bson:"timestamp,omitempty"`
        Content   string    `json:"content" bson:"content,omitempty"`
    }

Correct answer


The main reason your code broadcasts a message to everyone in the room with the same reference is that you are in hub ##broadcast The way messages are processed in the channel. In the current implementation, when a message is sent, it is forwarded to every client in the same room (i.e. with the same reference). This is done in the run method of hub:

case m := <-h.broadcast:
    if _, ok := h.rooms[m.reference]; ok {
        for _, cl := range h.rooms[m.reference].clients {
            cl.message <- m
            ...

I hope the score is 1-1. The reference is postid

If

reference is postid and you want one-on-one communication between the shopper (the person who posted the availability post) and each customer, then you need to ensure that each Each chat is uniquely identifiable. p>The unique key for each chat session should be the combination of

postid (reference) and customer id (sender). This ensures each customer has a unique chat session with the shopper in every post.

You can then update the

client structure to have a chatid , which is a combination of reference and sender.

type client struct {
    ...
    chatid string `json:"chat_id"`
}

You can update the

hub to manage chat session maps (identified by chatid) instead of rooms.

type hub struct {
    chats      map[string]*chat
    ...
}

The structure of each

chat is as follows:

type chat struct {
    shopper *client
    customer *client
}

To handle messages, when a customer sends a message, the message is sent to the shopper, and when the shopper replies, the message is sent to the customer. Routing can be done using

chatid.

For example, in your

broadcast logic:

case m := <-h.broadcast:
    chat, ok := h.chats[m.chatid]
    if ok {
        if m.sender == chat.customer.id {
            // message is from customer to shopper
            chat.shopper.message <- m
        } else if m.sender == chat.shopper.id {
            // message is from shopper to customer
            chat.customer.message <- m
        }
    }

m The variable is of type *message and it has no chatid field. To resolve this issue, you should consider adding the
chatid field to the message structure.

First, we modify the

message structure:

type message struct {
    content string `json:"content"`
    chatid  string `json:"chat_id"`
    sender  string `json:"sender"`
}

Then, when you construct a new

message in the readmessage method of client:

msg := &message{
    content:   string(m),
    chatid:    c.chatid,
    sender:    c.sender,
}

When initializing chat:

chatID := reference + "_" + sender

cl := &Client{
    ...
    ChatID: chatID,
}

// If the chat session does not exist, create it.
if _, ok := h.hub.Chats[chatID]; !ok {
    h.hub.Chats[chatID] = &Chat{
        Customer: cl,
        Shopper: nil, // That will be set when the shopper joins.
    }
} else {
    // That assumes only the customer or the shopper can join, not both at the same time.
    if h.hub.Chats[chatID].Shopper == nil {
        h.hub.Chats[chatID].Shopper = cl
    } else {
        h.hub.Chats[chatID].Customer = cl
    }
}

Warning: This is just a basic skeleton of the functionality, but it does not take into account additional complexities, such as handling situations where customers or shoppers may have multiple devices, ensuring robust error handling, and more.

The above is the detailed content of Chat one on one with golang socket. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete