Go完整即时通讯项目
项目架构:
1 编写基本服务端-Server
server.go
package main
import (
"fmt"
"net"
)
// 定义服务端
type Server struct {
ip string
port int
}
// 创建一个Server
func NewServer(ip string, port int) *Server {
return &Server{
ip: ip,
port: port,
}
}
// 定义Server方法
func (this *Server) Start() {
//根据ip+port监听socket套接字 tcp表明类型【socket listen】
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
//关闭套接字,避免资源浪费
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("listen accept err=", err)
continue
}
//处理连接请求【具体业务:处理客户端请求】
//开启协程处理,避免占用主线程【server一直要监听ip+port】
go this.Handler(conn)
}
}
// 处理连接请求
func (this *Server) Handler(conn net.Conn) {
//当前连接的业务
fmt.Printf("连接建立成功...")
}
通过在main.go中启动一个server并配合
telnet
命令检测代码是否正确
- telnet:可以模拟连接的建立
- telnet 127.0.0.1 8082
main.go:
package main
func main() {
//创建一个server
server := NewServer("127.0.0.1", 8082)
//启动server【监听】
server.Start()
}
//打包代码为exe
go build -o intime.exe .\main.go .\server.go
2 实现用户上线广播机制【用户上线功能】
架构图:Server端存储一个OnlineMap,用于记录在线的用户
- 编写user.go,编写User结构体并实现对user.channel的监听
- 修改server.go,新增OnlineMap和Message属性,在处理的客户端上线的Handler中连接建立成功之后将用户添加到OnlineMap;并新增广播消息方法
- 在server.go中新增监听广播消息channel的方法,同时用一个goroutine单独监听message
//构建代码 生成intime.exe文件
go build -o intime.exe .\main.go .\server.go .\user.go
3 用户消息广播机制
修改server.go,完善一个handle处理业务方法,启动一个专门针对当前用户的goroutine
server.go:
package main
import (
"fmt"
"io"
"net"
"sync"
)
// 定义服务端
type Server struct {
ip string
port int
//定义一个map,用于存储在线用户
OnlineMap map[string]*User
//定义map锁,保证存储map时候的数据正确,避免并发读取
mapLock sync.RWMutex
//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]
Message chan string
}
// 创建一个Server
func NewServer(ip string, port int) *Server {
return &Server{
ip: ip,
port: port,
OnlineMap: make(map[string]*User),
Message: make(chan string),
}
}
// 定义Server方法
func (this *Server) Start() {
//根据ip+port监听socket套接字 tcp表明类型【socket listen】
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
//关闭套接字,避免资源浪费
defer listen.Close()
//启动监听Message的goroutine
go this.ListenMessage()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("listen accept err=", err)
continue
}
//处理连接请求【具体业务:处理客户端请求】
//开启协程处理,避免占用主线程【server一直要监听ip+port】
go this.Handler(conn)
}
}
// 处理连接请求
func (this *Server) Handler(conn net.Conn) {
//当前连接的业务[用户上线之后,发送广播通知]
//fmt.Printf("连接建立成功...")
//1. 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)
user := NewUser(conn)
this.mapLock.Lock()
this.OnlineMap[user.Name] = user
this.mapLock.Unlock()
//2. 将该用户上线消息进行广播
this.Broadcast(user, "已上线")
//3. 接收用户发送的消息,并广播
go func() {
buf := make([]byte, 4096)
//接收用户信息
n, err := conn.Read(buf)
if n == 0 {
this.Broadcast(user, "下线")
return
}
if err != nil && err != io.EOF {
fmt.Println("Conn read err:", err)
return
}
//提取用户消息(去除"\n")
msg := string(buf[:n-1])
//广播消息
this.Broadcast(user, msg)
}()
//4. 阻塞当前handler
select {}
}
// 广播消息
func (this *Server) Broadcast(user *User, message string) {
sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message
//将消息写入广播chan中
this.Message <- sendMsg
}
// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {
for {
msg := <-this.Message
//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
//将广播消息写入用户的channel,等待用户监听读取
cli.C <- msg
}
this.mapLock.Unlock()
}
}
4 用户业务层封装
修改user.go,新增对应方法:
- user中新增一个Server属性
- Online
- Offline
- DoMessage等
替换之前server.go中涉及到user的代码
user.go:
package main
import (
"net"
"unicode/utf8"
)
type User struct {
Name string
Addr string
//管道用于接收服务端的消息
C chan string
//与服务器端的连接
conn net.Conn
//对应连接的Server
server *Server
}
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就应该监听自己chan管道中的消息,如果有就取出
go user.ListenMessage()
return user
}
// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
//从user管道中读取消息,发送给user客户端
对中文进行处理
//msgByte := []byte(msg)
//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)
//if err != nil {
// fmt.Println("simplifiedchinese decoder err=", err)
//}
runes := []rune(msg + "\n")
bytes := make([]byte, len(runes)*4)
for _, v := range runes {
buf := make([]byte, 4)
size := utf8.EncodeRune(buf, v)
bytes = append(bytes, buf[:size]...)
}
//this.conn.Write([]byte(msg + "\n"))
this.conn.Write(bytes)
}
}
// Online:用户上线方法
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.mapLock.Lock()
//根据key删除对应值
delete(this.server.OnlineMap, this.Name)
this.server.mapLock.Unlock()
this.server.Broadcast(this, "下线")
}
// 用户处理消息的业务
func (this *User) DoMessage(msg string) {
this.server.Broadcast(this, msg)
}
server.go:
package main
import (
"fmt"
"io"
"net"
"sync"
)
// 定义服务端
type Server struct {
ip string
port int
//定义一个map,用于存储在线用户
OnlineMap map[string]*User
//定义map锁,保证存储map时候的数据正确,避免并发读取
mapLock sync.RWMutex
//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]
Message chan string
}
// 创建一个Server
func NewServer(ip string, port int) *Server {
return &Server{
ip: ip,
port: port,
OnlineMap: make(map[string]*User),
Message: make(chan string),
}
}
// 定义Server方法
func (this *Server) Start() {
//根据ip+port监听socket套接字 tcp表明类型【socket listen】
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
//关闭套接字,避免资源浪费
defer listen.Close()
//启动监听Message的goroutine
go this.ListenMessage()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("listen accept err=", err)
continue
}
//处理连接请求【具体业务:处理客户端请求】
//开启协程处理,避免占用主线程【server一直要监听ip+port】
go this.Handler(conn)
}
}
// 处理连接请求
func (this *Server) Handler(conn net.Conn) {
//当前连接的业务[用户上线之后,发送广播通知]
//fmt.Printf("连接建立成功...")
// 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)
user := NewUser(conn, this)
user.Online()
// 接收用户发送的消息,并广播
go func() {
buf := make([]byte, 4096)
//接收用户信息
n, err := conn.Read(buf)
if n == 0 {
//用户下线
user.Offline()
return
}
if err != nil && err != io.EOF {
fmt.Println("Conn read err:", err)
return
}
//提取用户消息(去除"\n")
msg := string(buf[:n-1])
//用户针对msg进行消息处理
user.DoMessage(msg)
}()
//4. 阻塞当前handler
select {}
}
// 广播消息
func (this *Server) Broadcast(user *User, message string) {
sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message
//将消息写入广播chan中
this.Message <- sendMsg
}
// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {
for {
msg := <-this.Message
//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
//将广播消息写入用户的channel,等待用户监听读取
cli.C <- msg
}
this.mapLock.Unlock()
}
}
5 用户查询功能
实现,用户在终端输入
who
,查看当前在线用户(修改user.go)
- 添加SendMsg():给客户端发送消息
- 新增判断“who”命令逻辑
package main
import (
"net"
"unicode/utf8"
)
type User struct {
Name string
Addr string
//管道用于接收服务端的消息
C chan string
//与服务器端的连接
conn net.Conn
//对应连接的Server
server *Server
}
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就应该监听自己chan管道中的消息,如果有就取出
go user.ListenMessage()
return user
}
// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
//从user管道中读取消息,发送给user客户端
对中文进行处理
//msgByte := []byte(msg)
//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)
//if err != nil {
// fmt.Println("simplifiedchinese decoder err=", err)
//}
runes := []rune(msg + "\n")
bytes := make([]byte, len(runes)*4)
for _, v := range runes {
buf := make([]byte, 4)
size := utf8.EncodeRune(buf, v)
bytes = append(bytes, buf[:size]...)
}
//this.conn.Write([]byte(msg + "\n"))
this.conn.Write(bytes)
}
}
// Online:用户上线方法
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.mapLock.Lock()
//根据key删除对应值
delete(this.server.OnlineMap, this.Name)
this.server.mapLock.Unlock()
this.server.Broadcast(this, "下线")
}
// 用户处理消息的业务
func (this *User) DoMessage(msg string) {
//添加who命令逻辑:查询当前在线用户
if msg == "who" {
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"
this.SendMsg(onlineMsg)
}
}
this.server.Broadcast(this, msg)
}
// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {
this.conn.Write([]byte(msg))
}
6 修改用户名
定义命令
rename|zhangsan
:将当前用户名修改为张三
- 修改user.go:在DoMessage()方法中判断命令是否为
rename
user.go:
package main
import (
"net"
"strings"
"unicode/utf8"
)
type User struct {
Name string
Addr string
//管道用于接收服务端的消息
C chan string
//与服务器端的连接
conn net.Conn
//对应连接的Server
server *Server
}
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就应该监听自己chan管道中的消息,如果有就取出
go user.ListenMessage()
return user
}
// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
//从user管道中读取消息,发送给user客户端
对中文进行处理
//msgByte := []byte(msg)
//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)
//if err != nil {
// fmt.Println("simplifiedchinese decoder err=", err)
//}
runes := []rune(msg + "\n")
bytes := make([]byte, len(runes)*4)
for _, v := range runes {
buf := make([]byte, 4)
size := utf8.EncodeRune(buf, v)
bytes = append(bytes, buf[:size]...)
}
//this.conn.Write([]byte(msg + "\n"))
this.conn.Write(bytes)
}
}
// Online:用户上线方法
func (this *User) Online() {
//用户上线,将用户添加到OnlineMap中
this.server.mapLock.Lock()
this.server.OnlineMap[this.Name] = this
this.server.mapLock.Unlock()
//广播用户上线信息
this.server.Broadcast(this, "is online")
}
// 用户下线业务
func (this *User) Offline() {
this.server.mapLock.Lock()
//根据key删除对应值
delete(this.server.OnlineMap, this.Name)
this.server.mapLock.Unlock()
this.server.Broadcast(this, "is offline")
}
// 用户处理消息的业务
func (this *User) DoMessage(msg string) {
//添加who命令逻辑:查询当前在线用户
if msg == "who" {
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"
this.SendMsg(onlineMsg)
}
} else if len(msg) > 7 && msg[:7] == "rename|" {
newName := strings.Split(msg, "|")[1]
//判断要修改的name是否已经被占用
_, ok := this.server.OnlineMap[newName]
if ok {
this.SendMsg("the name is already exists...")
} 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("update name success:" + this.Name + "\n")
}
} else {
this.server.Broadcast(this, msg)
}
}
// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {
this.conn.Write([]byte(msg))
}
7 超时强踢功能
如果某个用户长时间不发消息,不活跃,达到一定时间则断开连接,达到强踢效果
- 修改server.go:
①在用户的Hander() goroutine中,添加用户活跃channel,一旦有消息就向该channel发送数据
②在用户的Hander()goroutine中,添加定时器功能,超时则强踢
server.go:
package main
import (
"fmt"
"io"
"net"
"sync"
"time"
)
// 定义服务端
type Server struct {
ip string
port int
//定义一个map,用于存储在线用户
OnlineMap map[string]*User
//定义map锁,保证存储map时候的数据正确,避免并发读取
mapLock sync.RWMutex
//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]
Message chan string
}
// 创建一个Server
func NewServer(ip string, port int) *Server {
return &Server{
ip: ip,
port: port,
OnlineMap: make(map[string]*User),
Message: make(chan string),
}
}
// 定义Server方法
func (this *Server) Start() {
//根据ip+port监听socket套接字 tcp表明类型【socket listen】
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
//关闭套接字,避免资源浪费
defer listen.Close()
//启动监听Message的goroutine
go this.ListenMessage()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("listen accept err=", err)
continue
}
//处理连接请求【具体业务:处理客户端请求】
//开启协程处理,避免占用主线程【server一直要监听ip+port】
go this.Handler(conn)
}
}
// 处理连接请求
func (this *Server) Handler(conn net.Conn) {
//当前连接的业务[用户上线之后,发送广播通知]
//fmt.Printf("连接建立成功...")
// 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)
user := NewUser(conn, this)
user.Online()
//监听用户是否活跃的channel
isLive := make(chan bool)
// 接收用户发送的消息,并广播
go func() {
buf := make([]byte, 4096)
//接收用户信息
n, err := conn.Read(buf)
if n == 0 {
//用户下线
user.Offline()
return
}
if err != nil && err != io.EOF {
fmt.Println("Conn read err:", err)
return
}
//提取用户消息(去除"\n")
msg := string(buf[:n-1])
//用户针对msg进行消息处理
user.DoMessage(msg)
//用户的任意消息代表用户当前是一个活跃的
isLive <- true
}()
//4. 当前handler阻塞【超时强制踢出】
for {
select {
case <-isLive:
//当前用户是活跃的,应该重置定时器
//不做任何事,为了激活select,更新下面的定时器
case <-time.After(time.Second * 10):
//已经超时,将当前的User强制关闭
user.SendMsg("you have been offline")
//销毁用的资源,关闭channel
close(user.C)
//关闭连接
conn.Close()
//退出当前Handler[runtime.Goexit()]
return
}
}
}
// 广播消息
func (this *Server) Broadcast(user *User, message string) {
sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message
//将消息写入广播chan中
this.Message <- sendMsg
}
// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {
for {
msg := <-this.Message
//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
//将广播消息写入用户的channel,等待用户监听读取
cli.C <- msg
}
this.mapLock.Unlock()
}
}
8 私聊功能
消息格式:
to|zhangsan|hello, how are you
- 修改user.go的DoMessage()逻辑,新增私聊消息判断
user.go:
package main
import (
"net"
"strings"
"unicode/utf8"
)
type User struct {
Name string
Addr string
//管道用于接收服务端的消息
C chan string
//与服务器端的连接
conn net.Conn
//对应连接的Server
server *Server
}
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就应该监听自己chan管道中的消息,如果有就取出
go user.ListenMessage()
return user
}
// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
//从user管道中读取消息,发送给user客户端
对中文进行处理
//msgByte := []byte(msg)
//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)
//if err != nil {
// fmt.Println("simplifiedchinese decoder err=", err)
//}
runes := []rune(msg + "\n")
bytes := make([]byte, len(runes)*4)
for _, v := range runes {
buf := make([]byte, 4)
size := utf8.EncodeRune(buf, v)
bytes = append(bytes, buf[:size]...)
}
//this.conn.Write([]byte(msg + "\n"))
this.conn.Write(bytes)
}
}
// Online:用户上线方法
func (this *User) Online() {
//用户上线,将用户添加到OnlineMap中
this.server.mapLock.Lock()
this.server.OnlineMap[this.Name] = this
this.server.mapLock.Unlock()
//广播用户上线信息
this.server.Broadcast(this, "is online")
}
// 用户下线业务
func (this *User) Offline() {
this.server.mapLock.Lock()
//根据key删除对应值
delete(this.server.OnlineMap, this.Name)
this.server.mapLock.Unlock()
this.server.Broadcast(this, "is offline")
}
// 用户处理消息的业务
func (this *User) DoMessage(msg string) {
//添加who命令逻辑:查询当前在线用户
if msg == "who" {
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"
this.SendMsg(onlineMsg)
}
} else if len(msg) > 7 && msg[:7] == "rename|" {
newName := strings.Split(msg, "|")[1]
//判断要修改的name是否已经被占用
_, ok := this.server.OnlineMap[newName]
if ok {
this.SendMsg("the name is already exists...")
} 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("update name success:" + this.Name + "\n")
}
} else if len(msg) > 4 && msg[:3] == "to|" {
//如果是私聊命令 消息格式: to|zhangsan|msg content
//1. 获取对方用户名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
this.SendMsg("the msg format is incorrect, please use the 'to|zhangsan|msg content' to send a msg\n")
return
}
//2. 根据用户名,得到对方的user对象
remoteUser, ok := this.server.OnlineMap[remoteName]
if !ok {
this.SendMsg("the user is not exist")
return
}
//3. 获取消息内容,通过对方的User对象将消息内容发送过去
content := strings.Split(msg, "|")[2]
if content == "" {
this.SendMsg("please do not send a empty msg\n")
return
}
remoteUser.SendMsg(this.Name + "is speak to you:" + content)
} else {
this.server.Broadcast(this, msg)
}
}
// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {
this.conn.Write([]byte(msg))
}
9 客户端实现(过程省略)
9.1 客户端类型定义与链接
9.2 解析命令行
9.3 菜单显示
9.4 更新用户名客户端实现
9.5 公聊模式
9.6 私聊模式
10 最终代码
①main.go
package main
func main() {
server := NewServer("127.0.0.1", 8888)
server.Start()
}
②server.go
package main
import (
"fmt"
"io"
"net"
"sync"
"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
}
//监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (this *Server) ListenMessager() {
for {
msg := <-this.Message
//将msg发送给全部的在线User
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
cli.C <- msg
}
this.mapLock.Unlock()
}
}
//广播消息的方法
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()
//监听用户是否活跃的channel
isLive := make(chan bool)
//接受客户端发送的消息
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
}
//提取用户的消息(去除'\n')
msg := string(buf[:n-1])
//用户针对msg进行消息处理
user.DoMessage(msg)
//用户的任意消息,代表当前用户是一个活跃的
isLive <- true
}
}()
//当前handler阻塞
for {
select {
case <-isLive:
//当前用户是活跃的,应该重置定时器
//不做任何事情,为了激活select,更新下面的定时器
case <-time.After(time.Second * 300):
//已经超时
//将当前的User强制的关闭
user.SendMsg("你被踢了")
//销毁用的资源
close(user.C)
//关闭连接
conn.Close()
//退出当前Handler
return //runtime.Goexit()
}
}
}
//启动服务器的接口
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
}
//close listen socket
defer listener.Close()
//启动监听Message的goroutine
go this.ListenMessager()
for {
//accept
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener accept err:", err)
continue
}
//do handler
go this.Handler(conn)
}
}
③user.go
package main
import (
"net"
"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() {
//用户下线,将用户从onlineMap中删除
this.server.mapLock.Lock()
delete(this.server.OnlineMap, this.Name)
this.server.mapLock.Unlock()
//广播当前用户上线消息
this.server.BroadCast(this, "下线")
}
//给当前User对应的客户端发送消息
func (this *User) SendMsg(msg string) {
this.conn.Write([]byte(msg))
}
//用户处理消息的业务
func (this *User) DoMessage(msg string) {
if msg == "who" {
//查询当前在线用户都有哪些
this.server.mapLock.Lock()
for _, user := range this.server.OnlineMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
this.SendMsg(onlineMsg)
}
this.server.mapLock.Unlock()
} else if len(msg) > 7 && msg[:7] == "rename|" {
//消息格式: rename|张三
newName := strings.Split(msg, "|")[1]
//判断name是否存在
_, ok := this.server.OnlineMap[newName]
if ok {
this.SendMsg("当前用户名被使用\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("您已经更新用户名:" + this.Name + "\n")
}
} else if len(msg) > 4 && msg[:3] == "to|" {
//消息格式: to|张三|消息内容
//1 获取对方的用户名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
this.SendMsg("消息格式不正确,请使用 \"to|张三|你好啊\"格式。\n")
return
}
//2 根据用户名 得到对方User对象
remoteUser, ok := this.server.OnlineMap[remoteName]
if !ok {
this.SendMsg("该用户名不不存在\n")
return
}
//3 获取消息内容,通过对方的User对象将消息内容发送过去
content := strings.Split(msg, "|")[2]
if content == "" {
this.SendMsg("无消息内容,请重发\n")
return
}
remoteUser.SendMsg(this.Name + "对您说:" + content)
} else {
this.server.BroadCast(this, msg)
}
}
//监听当前User channel的 方法,一旦有消息,就直接发送给对端客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
this.conn.Write([]byte(msg + "\n"))
}
}
④client.go
package main
import (
"flag"
"fmt"
"io"
"net"
"os"
)
type Client struct {
ServerIp string
ServerPort int
Name string
conn net.Conn
flag int //当前client的模式
}
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.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
//处理server回应的消息, 直接显示到标准输出即可
func (client *Client) DealResponse() {
//一旦client.conn有数据,就直接copy到stdout标准输出上, 永久阻塞监听
io.Copy(os.Stdout, client.conn)
}
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) 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(">>>>请输入聊天对象[用户名], 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\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) 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) 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) 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() {
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
}
//单独开启一个goroutine去处理server的回执消息
go client.DealResponse()
fmt.Println(">>>>>链接服务器成功...")
//启动客户端的业务
client.Run()
}
11 go的全部生态
参考:
- 资料地址:https://pan.baidu.com/s/1glckD7XGInHDFQQKCRE66g#list/path=%2F