基于Golang的即时通信系统
1.系统架构图
2.server.go (服务端)
1)定义了一个Server结构体,存储服务器的基本信息以及一个连接的用户表;
2)创建了一个NewServer接口,用于创建Server端;
3)Start()函数用于启动服务器接口,并通过Listen()和Accept()方法完成了socket连接,同时为ListenMessage()创建了一个goroutine;
4)Go程ListenMessage()用于监听服务端的channel Message,一旦有消息就发送给所有的在线User;
5)Broadcast()用于向User广播消息;
6)Handle()用于服务端和用户完成Socket连接后,服务端对用户端的处理,包括创建一个go程来提取用户发送的消息进行广播以及超时强踢功能等。
package main
import(
"fmt"
"net"
"sync"
"io"
"time"
)
type Server struct{
Ip string
Port int
//在线用户列表
OnlineMap map[string]*User
//锁,操作全局列表
mapLock sync.RWMutex
//消息广播的channel
Message chan string
}
//创建server接口
func NewServer(ip string , port int) *Server{
server:=&Server{
Ip:ip,
Port:port,
OnlineMap: make (map[string]*User),
Message: make(chan string),
}
return server
}
//广播消息的方法
func (this *Server) BroadCast(user *User,msg string){
sendMsg := "["+user.Addr+"]"+user.Name+":"+msg
this.Message<-sendMsg
}
func (this *Server) Handler(conn net.Conn){
//...当前连接的业务
//fmt.Println("连接建立成功 ... ")
user:=NewUser(conn,this)
//用户上线
user.Online()
//接受客户端发送的消息
isLive:=make(chan bool)
go func(){
buf := make ([]byte ,4096)
for{
n,err:=conn.Read(buf)
if n==0{ //read 方法读到 0 代表对端关闭连接 ,或者发生一些错误
user.Offline()
return
}
if err!= nil&&err !=io.EOF{
fmt.Println("Conn Read err :",err)
return
}
//提取用户的消息(去除'\n')
msg := string(buf[:n-1])
//将得到的消息进行广播
user.DoMessage(msg)
isLive<-true
}
}()
//将当前handler阻塞
for{
//等待60s,用户不活跃强制踢除
select{
case <- isLive:
//当前用户是活跃的,应该重置定时器
//不做任何操作,激活select,更新定时期
case <- time.After(time.Second*60):
//已经超时
//将User强制关闭
user.SendMsg("您已超时 ... ")
//销毁资源
//关闭通信channel
close(user.C)
//关闭和客户端的连接
conn.Close()
//退出当前handler
return //runtime.Goexit()
}
}
}
//监听Message广播消息channel的goroutine,一旦有消息就发送给所有的在线User
func (this *Server)ListenMessage(){
for{
msg:=<-this.Message
//将msg发送给所有在线User
this.mapLock.Lock()
for _,cli :=range this.OnlineMap{
cli.C<-msg
}
this.mapLock.Unlock()
}
}
//启动服务器的接口
func (this *Server) Start(){
//socket listen
listener,err := net.Listen("tcp",fmt.Sprintf("%s:%d",this.Ip,this.Port))
if err !=nil{
fmt.Println("net.Listen err : ",err)
return
}
defer listener.Close()
//启动监听Message的goroutine
go this.ListenMessage()
//accept
for{
conn,err:=listener.Accept()
if err != nil{
fmt.Println("listener accept err :",err)
continue
}
//do handler
go this.Handler(conn)
//close listen socket
}
}
3.user.go (用户端)
1)定义了一个User结构体,用于存储User信息;
2)定义了一个NewUser接口,用来创建User类;
3)Online()完成了用户上线功能;
4)Offline()完成了用户下线功能;
5)SendMsg()完成了给当前用户对应的客户端发消息;
6)ListenMessage()是在创建User类的时候建立的goroutine,用于监听User.channel中的消息,一旦有消息就发送给客户端;
7)DoMessage()用于处理消息,完成相应功能,包括:查询当前用户、更新用户名、广播、私聊。
package main
import(
"net"
//"fmt"
"strings"
)
type User struct{
Name string
Addr string
C chan string
conn net.Conn
server *Server
}
//创建一个用户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,
}
//启动监听当前user channel 消息的goroutine
go user.ListenMessage()
return user
}
//用户的上线业务
func (this *User) Online(){
//用户上线,将用户加到onlineMap中去
this.server.mapLock.Lock()
this.server.OnlineMap[this.Name]=this
this.server.mapLock.Unlock()
//广播当前用户上线消息
this.server.BroadCast(this,"已上线")
}
//用户的下线业务
func (this *User) Offline(){
this.server.BroadCast(this,"下线")
this.server.mapLock.Lock()
delete(this.server.OnlineMap,this.Name)
this.server.mapLock.Unlock()
}
//给当前User对应的客户端发送消息
func (this *User) SendMsg(msg string){
this.conn.Write([]byte(msg))
}
//用户处理消息的业务
func (this *User)DoMessage(msg string){
//查询当前用户
if msg=="*List"{
for _,user := range this.server.OnlineMap{
OnlineList :="["+user.Addr+"]"+user.Name+": 在线 ...\n"
this.SendMsg(OnlineList)
}
}else if len(msg)>8 && msg[:8]=="*Rename|"{
//消息格式:*Rename|13
newName:=strings.Split(msg,"|")[1]
this.server.mapLock.Lock()
//判断name是否存在
_,ok:=this.server.OnlineMap[newName]
if ok{
this.SendMsg("当前用户名被使用\n")
}else{
delete(this.server.OnlineMap,this.Name)
this.server.OnlineMap[newName]=this
this.Name=newName
this.SendMsg("您已经更新用户名: "+this.Name+"\n")
}
this.server.mapLock.Unlock()
}else if len(msg)>4&&msg[:3]=="to|"{
RemoteName:=strings.Split(msg,"|")[1]
SecretMsg:=strings.Split(msg,"|")[2]
RemoteUser,ok:=this.server.OnlineMap[RemoteName]
if RemoteName ==""{
this.SendMsg("消息格式不正确,请使用\"to|张三|hello\"格式 !\n")
}
if !ok{
this.SendMsg("该用户名不存在\n")
return
}
RemoteUser.SendMsg("『私聊』"+"["+this.Name+"] :"+SecretMsg)
}else{
this.server.BroadCast(this,msg)
}
}
//监听当前User channel的方法,一旦有消息,就直接发送给对端客户端
func (this *User) ListenMessage(){
for{
msg:=<-this.C
this.conn.Write([]byte(msg+"\n"))
}
}
4.client.go (客户端)
1)定义了一个Client类,用于存储客户端信息;
2)NewClient()定义了一个新建客户端接口,用于设置客户端信息,并使用net.Dial()将客户端连接到服务端;
3)init()定义了一组命令行参数,可以在运行程序时通过命令行来改变要访问的地址和端口;
4)menu()用于输出用户端可执行的操作,并提供用户选择;
5)UpdateName()用于更新客户端名称;
6)PublicChat()用于实现客户端公聊功能;
7)SelectUsers()用于实现查看当前在线用户功能;
8)PrivateChat()用于实现用户私聊功能;
9)Run()用于启动客户端,让用户选择相应的功能,并执行;
10)main()是主函数,解析命令行,建立并启动客户端,并单独开启一个goroutine去处理server的回执消息;
11)DealResponse()用于处理server回应的消息,直接显示到标准输出。
package main
import (
"fmt"
"net"
"flag" //命令行解析
"io"
"os"
)
type Client struct{
ServerIp string
ServerPort int
Name string
conn net.Conn
flag int
}
func NewClient(serverIp string,serverPort int) *Client{
//创建客户端对象
client:=&Client{
ServerIp: serverIp,
ServerPort:serverPort,
flag :999,
}
//连接server
conn,err:=net.Dial("tcp",fmt.Sprintf("%s:%d",serverIp,serverPort))
if err!=nil{
fmt.Println("net.Dail error:",err)
return nil
}
client.conn=conn;
return client
}
//处理server回应的消息,直接显示到标准输出即可
func (client *Client) DealResponse(){
//一旦client.conn有数据,就直接copy到stdout标准输出上,永久阻塞监听
io.Copy(os.Stdout,client.conn)
/*
上述代码等价于:
for{
buf :=make()
client.conn.Read(buf)
fmt.Println(buf)
}
*/
}
func (client *Client) menu() bool{
var flag int
fmt.Println("1.公聊模式")
fmt.Println("2.私聊模式")
fmt.Println("3.更新用户名")
fmt.Println("0.退出")
fmt.Scanln(&flag)
if flag >=0 && flag <=3 {
client.flag=flag
return true
}else{
fmt.Println(">>>>> 请输入合法范围内的数字 <<<<<")
return false
}
}
// 更新用户名
func (client *Client) UpdateName() bool{
fmt.Println(">>>>> 请输入用户名:")
fmt.Scanln(&client.Name)
sendMsg:="*Rename|"+client.Name+"\n"
_,err:=client.conn.Write([]byte(sendMsg))
if err !=nil{
fmt.Println("conn.Write err :",err)
return false
}
return true
}
//公聊
func(client *Client) PublicChat(){
var chatMsg string
fmt.Println(">>>>> 请输入聊天内容,exit 退出")
fmt.Scanln(&chatMsg)
for chatMsg!="exit"{
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(">>>>> 请输入聊天内容,exit 退出")
fmt.Scanln(&chatMsg)
}
}
//查询在线用户
func(client *Client) SelectUsers(){
sendMsg:="*List\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(">>>>> 请输入聊天对象[用户名],exit退出:")
fmt.Scanln(&RemoteName)
for RemoteName!="exit"{
fmt.Println(">>>>> 请输入聊天内容,exit 退出")
fmt.Scanln(&chatMsg)
for chatMsg!="exit"{
if len(chatMsg)!= 0 {
sendMsg:="to|"+RemoteName+"|"+chatMsg+"\n"
_,err:=client.conn.Write([]byte(sendMsg))
if err!=nil{
fmt.Println("conn Write err:",err)
break
}
}
chatMsg=""
fmt.Println(">>>>> 请输入聊天内容,exit 退出")
fmt.Scanln(&chatMsg)
}
client.SelectUsers()
fmt.Println(">>>>> 请输入聊天对象[用户名],exit退出:")
fmt.Scanln(&RemoteName)
}
}
//启动客户端
func (client *Client) Run(){
for client.flag!=0{
for client.menu()!=true{
}
//根据不同的模式处理不同的业务
switch client.flag{
case 1:
//公聊模式
client.PublicChat()
break
case 2:
//私聊模式
client.PrivateChat()
break
case 3:
//更新用户名
client.UpdateName()
break
}
}
}
var serverIp string
var serverPort int
// ./client -ip 127.0.0.1 -port 8888
func init(){
//四个参数
//1.&serverIp代表传进去自己定义的参数
//2."ip"代表控制命令是ip
//3."127.0.0.1"代表的是默认值
//4.代表的是提示 ./client -h 显示
flag.StringVar(&serverIp,"ip","127.0.0.1","设置服务器IP地址(默认是127.0.0.1)")
flag.IntVar(&serverPort,"port",8888,"设置服务器端口(默认是8888)")
}
func main(){
//命令行解析
flag.Parse()
client:=NewClient(serverIp,serverPort)
if client == nil{
fmt.Println(">>>>> 连接服务器失败 ...")
return
}
fmt.Println(">>>>> 连接服务器成功 ...")
//单独开启一个goroutine去处理server的回执消息
go client.DealResponse()
//启动客户端业务
client.Run()
}
5.main.go (主程序)
主要用于定义服务端端口,并开启服务端。
package main
func main(){
server :=NewServer("127.0.0.1",8888)
server.Start()
}