即时通讯系统源码服务器端构架目录:
仓库源码: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 以向对象客户端发送消息
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)
}
}