ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

即时通讯源码(基于websocket即时通讯源码uniapp)+视频搭建教程

2022-08-24 11:34:02  阅读:220  来源: 互联网

标签:uniapp client err fmt 即时通讯 server 源码 user conn


  即时通讯系统源码服务器端构架目录:

       仓库源码:im.jstxym.top

  1、构建基本服务器
  2、用户在线功能
  3、用户消息广播机制
  4、用户业务层封装
  5、在线用户查询
  6、修改用户名
  7、超时推送功能
  8、私聊功能
  即时通讯系统源码客户端构架目录:
  1、客户端类型定义和链接
  2、解析命令行
  3、菜单显示
  4、更新用户名
  5、公共聊天模式
  6、私聊模式
  即时通讯系统 - 服务器
  项目架构图:

 


  1、构建基本服务器
  其中包括以下内容:
  定义服务器结构,包括IP和端口字段
  NewServer(ip string, port int)创建服务器对象的方法
  (s *Server) Start()启动服务器服务的方法
  (s *Server) Handler(conn net.Conn)处理连接服务

package main

import (
    "fmt"
    "net"
)

type Server struct {
    Ip   string
    Port int
}

//Create a server interface
func NewServer(ip string, port int) *Server {
    server := &Server{
        Ip:   ip,
        Port: port,
    }
    return server
}

func (s *Server) Handler(conn net.Conn) {
    //Currently connected services
    fmt. Println ("connection established successfully!")
}

//Start the interface of the server
func (s *Server) Start() {
    // socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
    if err != nil {
        fmt.Println("net.Listen err: ", err)
        return
    }
    // close listen socket
    defer listener.Close()

    for {
        // accpet
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listener accept err: ", err)
            continue
        }
        // do handler
        go s.Handler(conn)
    }

}

        启动我们写的服务器:

package main

func main() {
    server := NewServer("127.0.0.1", 8888)
    server.Start()
}

  以下命令在Linux或MacOS下运行,与windows略有不同
  同时编译的两个文件:go build -o server main.go server.go
  然后运行编译后的文件:./server
  收听我们使用命令构建的服务:nc 127.0.0.1 8888
  2、用户在线功能
  NewUser(conn net.Conn) *User创建用户对象
  (u *User) ListenMessage()收听用户对应的频道消息

  添加了在线地图和消息属性
  在处理客户端的处理程序中创建和添加用户
  新的广播消息方法
  收听广播消息的新频道方法
  使用 goroutine 分别监听消息

type Server struct {
    Ip   string
    Port int

    //List of online users
    OnlineMap map[string]*User
    mapLock   sync.RWMutex

    //Message broadcast channel
    Message chan string
}

//Create a server interface
func NewServer(ip string, port int) *Server {
    server := &Server{
        Ip:        ip,
        Port:      port,
        OnlineMap: make(map[string]*User),
        Message:   make(chan string),
    }
    return server
}

//Monitor the goroutine of the channel of the message broadcast message. Once there is a message, it will be sent to all online users
func (s *Server) ListenMessager() {
    for {
        msg := <-s.Message
        //Send msg to all online users
        s.mapLock.Lock()
        for _, cli := range s.OnlineMap {
            cli.C <- msg
        }
        s.mapLock.Unlock()
    }
}

//Method of broadcasting message
func (s *Server) BroadCast(user *User, msg string) {
    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg

    s.Message <- sendMsg
}

func (s *Server) Handler(conn net.Conn) {
    //Currently connected services
    // fmt. Println ("connection established successfully!")

    user := NewUser(conn)

    //The user goes online and adds the user to the onlinemap
    s.mapLock.Lock()
    s.OnlineMap[user.Name] = user
    s.mapLock.Unlock()

    //Broadcast the online message of the current user
    s. Broadcast (user, "online")

    //Current handler blocked
    select {}
}

//Start the interface of the server
func (s *Server) Start() {
    // socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
    if err != nil {
        fmt.Println("net.Listen err: ", err)
        return
    }
    // close listen socket
    defer listener.Close()

    //Start goroutine for monitoring message
    go s.ListenMessager()

    for {
        // accpet
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listener accept err: ", err)
            continue
        }
        // do handler
        go s.Handler(conn)
    }

  学到的编程思想:
  结构中的channels基本上都需要开一个循环来监听它们的变化(尽量获取值并发给其他channels)
  3、用户消息广播机制
  服务器。go:改进句柄处理业务方法,为当前客户端启动一个读例程

  4、用户业务层封装
  为用户类型添加服务器关联
  添加线上、线下和外卖方式

type User struct {
    Name string
    Addr string
    C    chan string
    conn net.Conn

    server *Server
}

//Create a user API
func NewUser(conn net.Conn, server *Server) *User {
    userAddr := conn.RemoteAddr().String()

    user := &User{
        Name:   userAddr,
        Addr:   userAddr,
        C:      make(chan string),
        conn:   conn,
        server: server,
    }

    //Start goroutine to listen to the current user channel message
    go user.ListenMessage()

    return user
}

//Online business of users
func (u *User) Online() {
    //When the user goes online, add the user to the onlinemap
    u.server.mapLock.Lock()
    u.server.OnlineMap[u.Name] = u
    u.server.mapLock.Unlock()

    //Broadcast the online message of the current user
    u.server. Broadcast (U, "online")
}

//User's offline business
func (u *User) Offline() {
    //When the user goes offline, delete the user from the onlinemap
    u.server.mapLock.Lock()
    delete(u.server.OnlineMap, u.Name)
    u.server.mapLock.Unlock()

    //Broadcast the offline message of the current user
    u.server. Broadcast (U, "offline")
}

//Service for users to process messages
func (u *User) DoMessage(msg string) {
    u.server.BroadCast(u, msg)
}

//The method of listening to the current user channel. Once there is a message, it will be sent directly to the client
func (u *User) ListenMessage() {
    for {
        msg := <-u.C
        u.conn.Write([]byte(msg + "\n"))
    }
}

  server.go:
  将之前的代码替换为用户封装的业务

func (s *Server) Handler(conn net.Conn) {
    //Currently connected services
    // fmt. Println ("connection established successfully!")

    user := NewUser(conn, s)

  //User online
    user.Online()

    //Accept messages sent by clients
    go func() {
        buf := make([]byte, 4096)
        for {
            n, err := conn.Read(buf)
            if n == 0 {
        //User offline
                user.Offline()
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("Conn Read err:", err)
                return
            }

            //Extract user's message (remove '\ n')
            msg := string(buf[:n-1])

            //Broadcast the received message
            user.DoMessage(msg)
        }
    }()

    //Current handler blocked
    select {}
}

  5、在线用户查询
  如果用户输入的消息是who则查询当前在线用户列表。
  用户.go:
  为 sendmsg 提供 API 以向对象客户端发送消息

  func (u *User) SendMsg(msg string) {
  u.conn.Write([]byte(msg))
  }

        在domessage()方法中,增加了“who”指令的处理,返回在线用户信息

func (u *User) DoMessage(msg string) {
    if msg == "who" {
        //Query the current online users
        u.server.mapLock.Lock()
        for _, user := range u.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user. Name + ":" + "online... \ n"
            u.SendMsg(onlineMsg)
        }
        u.server.mapLock.Unlock()
    } else {
        u.server.BroadCast(u, msg)
    }
}

  6、修改用户名
  如果用户输入的消息是Rename Zhang SanChange your name to Zhang San。
  用户.go:
  在指令中添加“doame”

func (u *User) DoMessage(msg string) {
    if msg == "who" {
        //Query the current online users
        u.server.mapLock.Lock()
        for _, user := range u.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user. Name + ":" + "online... \ n"
            u.SendMsg(onlineMsg)
        }
        u.server.mapLock.Unlock()
    } else if len(msg) > 7 && msg[:7] == "rename|" {
        //Message format: Rename | Zhang San
        newName := strings.Split(msg, "|")[1]
        //Determine whether name exists
        _, ok := u.server.OnlineMap[newName]
        if ok {
            u. Sendmsg ("current user name is in use \ n")
        } else {
            u.server.mapLock.Lock()
            delete(u.server.OnlineMap, newName)
            u.server.OnlineMap[newName] = u
            u.server.mapLock.Unlock()

            u.Name = newName
            u. Sendmsg ("you have updated your user name:" + u.name + "\ n")
        }
    } else {
        u.server.BroadCast(u, msg)
    }
}

  7、超时推送功能
  来自用户的任何消息都表明该用户处于活动状态。如果用户长时间不发送消息,则视为超时,然后强制关闭用户连接。
  server.go:

func (s *Server) Handler(conn net.Conn) {
    //Currently connected services
    // fmt. Println ("connection established successfully!")

    user := NewUser(conn, s)

    user.Online()

    //Monitor whether the user is active in the channel
    isLive := make(chan bool)

    //Accept messages sent by clients
    go func() {
        buf := make([]byte, 4096)
        for {
            n, err := conn.Read(buf)
            if n == 0 {
                user.Offline()
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("Conn Read err:", err)
                return
            }

            //Extract user's message (remove '\ n')
            msg := string(buf[:n-1])

            //The user processes messages for MSG
            user.DoMessage(msg)

            //Any message of the user, indicating that the current user is active
            isLive <- true
        }
    }()

    //Current handler blocked
    for {
        select {
        case <-isLive:
            //The current user is active and the timer should be reset
            //Do nothing. In order to activate select, update the timer below
        case <-time. After (time. Second * 10): // trigger the timer after 10s
            //Has timed out
            //Force the current user to close
            user. Sendmsg ("you got kicked.")

            //Destruction of resources
            close(user.C)

            //Close connection
            conn.Close()

            //Exit current handler
            // runtime.Goexit()
            return
        }
    }
}

  8、私聊功能
  留言格式:Hello, I'm
  在domessage()方法中,添加对“三问好”指令的处理:

func (this *User) DoMessage(msg string) {
    if msg == "who" {
        //Query the current online users

        this.server.mapLock.Lock()
        for _, user := range this.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user. Name + ":" + "online... \ n"
            this.SendMsg(onlineMsg)
        }
        this.server.mapLock.Unlock()

    } else if len(msg) > 7 && msg[:7] == "rename|" {
        //Message format: Rename | Zhang San
        newName := strings.Split(msg, "|")[1]

        //Determine whether name exists
        _, ok := this.server.OnlineMap[newName]
        if ok {
            this. Sendmsg ("current user name is in use \ n")
        } else {
            this.server.mapLock.Lock()
            delete(this.server.OnlineMap, this.Name)
            this.server.OnlineMap[newName] = this
            this.server.mapLock.Unlock()

            this.Name = newName
            this. Sendmsg ("you have updated your user name:" + this. Name + "\ n")
        }

    } else {
        this.server.BroadCast(this, msg)
    }
}

  即时通讯系统——客户端
  客户端类型定义和链接

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
}

func NewClient(serverIp string, serverPort int) *Client {
    //Create client object
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
    }
    //Connect to server
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial error:", err)
        return nil
    }
    client.conn = conn
    //Return object
    return client
}

func main() {
    client := NewClient("127.0.0.1", 8888)
    if client == nil {
        fmt. Println ("> > > failed to connect to the server")
        return
    }
    fmt. Println ("> > > successfully connected to the server")

    //Start client service
    select {}
}

  编译说明:go build -o client client.go
  运行编译后的文件:./client
  解析命令行
  在init函数中初始化命令行参数并解析:

var serverIp string
var serverPort int

func init() {
    flag. Stringvar (& ServerIP, "IP", "127.0.0.1", "set server IP address (default is 127.0.0.1)")
    flag. Intvar (& serverport, "port", 8888, "set server port (default is 8888)")

    //Command line parsing
    flag.Parse()
}

  然后,在运行客户端的时候,可以通过命令行传递参数来运行:
  ./client -ip 127.0.0.1 -port 8888
  菜单显示
  向客户端添加标志属性:

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
    Flag int // current client mode
}
添加menu()方法获取用户输入的模式:

//Menu
func (client *Client) menu() bool {
    var flag int

    fmt. Println ("1. Public chat mode")
    fmt. Println ("2. Private chat mode")
    fmt. Println ("3. Update user name")
    fmt. Println ("0. Exit")

    fmt.Scanln(&flag)

    if flag >= 0 && flag <= 3 {
        client.flag = flag
        return true
    } else {
        fmt. Println ("> > > > please enter the number within the legal range < < <)
        return false
    }
}
添加一个run()主业务循环:

func (client *Client) Run() {
    for client.flag != 0 {
        for !client.menu() {
        }

        //Handle different businesses according to different modes
        switch client.flag {
        case 1:
            //Public chat mode
            fmt. Println ("public chat mode")
        case 2:
            //Private chat mode
            fmt. Println ("private chat mode")
        case 3:
            //Update user name
            fmt. Println ("update user name")
        }
    }
    fmt. Println ("exit!")
}

  更新用户名
  新的 updatename() 用户名​​:

func (client *Client) UpdateName() bool {
    fmt. Println ("> > > > please enter user name:")
    fmt.Scanln(&client.Name)

    sendMsg := "rename|" + client. Name + "\ n" // encapsulation protocol
    _, err := client.conn.Write([]byte(sendMsg))
    if err != nil {
        fmt.Println("conn.Write err: ", err)
        return false
    }

    return true
}
添加服务器回执消息方法 dealresponse()

//Process the messages responded by the server and display them directly to the standard output
func (client *Client) DealResponse() {
    //Once client Conn has data, which is directly copied to stdout standard output to permanently block listening
    io.Copy(os.Stdout, client.conn)
}
在 main 中打开一个 goroutine 来托管 dealresponse() 进程:

func main() {
    client := NewClient(serverIp, serverPort)
    if client == nil {
        fmt. Println ("> > > failed to connect to the server")
        return
    }
    fmt. Println ("> > > successfully connected to the server")

    //Open a goroutine separately to process the receipt message of the server
    go client.DealResponse()

    //Start client service
    client.Run()
}

  公共聊天模式
  添加publicchat()公共聊天模式:

func (client *Client) PublicChat() {
    //Prompt the user for a message
    var chatMsg string

    fmt. Println ("> > > > please enter the chat content and exit.")
    fmt.Scanln(&chatMsg)

    for chatMsg != "exit" {
        //Send to server
        //The message is not empty. Send it now
        if len(chatMsg) != 0 {
            sendMsg := chatMsg + "\n"
            _, err := client.conn.Write([]byte(sendMsg))
            if err != nil {
                fmt.Println("conn Write err: ", err)
                break
            }
        }
        chatMsg = ""
        fmt. Println ("> > > > please enter the chat content and exit.")
        fmt.Scanln(&chatMsg)
    }
}

  私聊模式
  查询当前有哪些用户在线:

func (client *Client) SelectUsers() {
    sendMsg := "who\n"
    _, err := client.conn.Write([]byte(sendMsg))
    if err != nil {
        fmt.Println("conn Write err: ", err)
        return
    }
}
新的私聊业务:

func (client *Client) PrivateChat() {
    var remoteName string
    var chatMsg string

    client.SelectUsers()
    fmt. Println ("> > > > please enter the [user name] of the chat object and exit:")
    fmt.Scanln(&remoteName)

    for remoteName != "exit" {
        fmt. Println ("> > > > please enter the message content, exit:")
        fmt.Scanln(&chatMsg)

        for chatMsg != "exit" {
            //Send if the message is not empty
            if len(chatMsg) != 0 {
                sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"
                _, err := client.conn.Write([]byte(sendMsg))
                if err != nil {
                    fmt.Println("conn Write err: ", err)
                    break
                }
            }
            chatMsg = ""
            fmt. Println ("> > > > please enter the message content, exit:")
            fmt.Scanln(&chatMsg)
        }

        client.SelectUsers()
        fmt. Println ("> > > > please enter the [user name] of the chat object and exit:")
        fmt.Scanln(&remoteName)

    }

}

 

标签:uniapp,client,err,fmt,即时通讯,server,源码,user,conn
来源: https://www.cnblogs.com/zerobeauty/p/16619214.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有