基于Golang的即时通信系统

基于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()
}

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值