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

       仓库源码:im.jstxym.top

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

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

 

  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()收听用户对应的频道消息

即时通讯源码(基于websocket即时通讯源码uniapp)+视频搭建教程_服务器_02

  添加了在线地图和消息属性
  在处理客户端的处理程序中创建和添加用户
  新的广播消息方法
  收听广播消息的新频道方法
  使用 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 以向对象客户端发送消息

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)

}

}