即时通讯源码(基于websocket即时通讯源码uniapp)+视频搭建教程
作者:互联网
即时通讯系统源码服务器端构架目录:
仓库源码: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