Go完整即时通讯项目及Go的生态介绍

这篇具有很好参考价值的文章主要介绍了Go完整即时通讯项目及Go的生态介绍。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Go完整即时通讯项目

项目架构:
Go完整即时通讯项目及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

Go完整即时通讯项目及Go的生态介绍

2 实现用户上线广播机制【用户上线功能】

架构图:Server端存储一个OnlineMap,用于记录在线的用户

Go完整即时通讯项目及Go的生态介绍

  1. 编写user.go,编写User结构体并实现对user.channel的监听
  2. 修改server.go,新增OnlineMap和Message属性,在处理的客户端上线的Handler中连接建立成功之后将用户添加到OnlineMap;并新增广播消息方法
  3. 在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的全部生态

Go完整即时通讯项目及Go的生态介绍

参考:文章来源地址https://www.toymoban.com/news/detail-462039.html

  • 资料地址:https://pan.baidu.com/s/1glckD7XGInHDFQQKCRE66g#list/path=%2F

到了这里,关于Go完整即时通讯项目及Go的生态介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • php workerman 即时通讯聊天系统

    进入tp目录 启动tp框架 执行 composer install 安装依赖 使用命令 php think run 进入 workman目录 启动workman 服务 执行 composer install 安装依赖 使用命令 php .ws_formal.php start 浏览器 进入 http://127.0.0.1:8000/ 模拟客户 进入 http://127.0.0.1:8000/admin/login/login 后台登录页面 用户名 admin 密码 123456

    2024年02月08日
    浏览(43)
  • uniapp使用WebSocket实现即时通讯

    UniApp是一个基于Vue.js的跨平台应用开发框架,它允许开发者使用HTML、CSS和JavaScript构建多平台的移动应用程序。要使用WebSocket在UniApp中实现即时通讯功能,你可以按照以下步骤进行操作: 在UniApp项目的根目录下,找到 manifest.json 文件并打开它。 在 manifest.json 文件中,添加以下

    2024年02月06日
    浏览(33)
  • 即时通讯开发中的性能优化技巧

    即时通讯开发在如今的数字化社会中扮演着重要角色,然而,随着用户对即时通讯应用的需求不断增长,开发者们面临着使其应用保持高性能和可靠性的挑战。本文将探讨即时通讯开发中关键的性能优化技巧,帮助开发者们提升应用的用户体验和响应速度。 1. 建立高效的消息

    2024年02月10日
    浏览(29)
  • 基于python的安全即时通讯系统

    资源下载地址:https://download.csdn.net/download/sheziqiong/85596120 资源下载地址:https://download.csdn.net/download/sheziqiong/85596120 设计完成简易的安全即时通讯系统,实现类似于 QQ 的聊天软件; 功能需求 聊天客户端 注册:用户与集中服务器通信完成注册,包括用户名、密码、邮箱、性别

    2024年02月16日
    浏览(39)
  • IM即时通讯开发MQ消息队列

    消息是互联网信息的一种表现形式,是人利用计算机进行信息传递的有效载体,比如即时通讯网坛友最熟悉的即时通讯消息就是其具体的表现形式之一。   消息从发送者到接收者的典型传递方式有两种:     1)一种我们可以称为即时消息:即消息从一端发出后(消息发送者

    2024年02月12日
    浏览(65)
  • 即时通讯聊天软件DDOS攻击怎么防护

    即时通讯聊天软件DDOS攻击怎么防护?随着即时通讯聊天软件的普及,DDoS攻击也越来越常见。DDoS攻击是一种利用大量计算机发起的分布式拒绝服务攻击,它可以让即时通讯聊天软件无法正常运行,导致用户无法正常使用。针对这种情况,即时通讯聊天软件需要采取一系列措施来

    2023年04月27日
    浏览(38)
  • Web端即时通讯技术(SEE,webSocket)

    服务端和客户端应该怎么通信才能实现客户端能获取服务端最新消息让用户有更好的交互体验,如果是正常的发送一个请求首先要建立TCP连接然后等到服务器返回,如果是开发者可以通过发包情况就能知道建立连接成功与否,是否是在等待服务器响应,但是做为非开发者的普

    2024年02月15日
    浏览(40)
  • SSE技术和WebSocket技术实现即时通讯

    当涉及到实现实时通信的Web应用程序时,两种常见的技术选择是服务器发送事件(Server-Sent Events,SSE)和WebSocket,本文将详细讲讲这两种技术,并比较它们的异同点。 1.1 什么是SSE 服务器发送事件 SSE(Server-Sent Events)是一种基于HTTP的 单向通信机制 ,用于实现 服务器主动向

    2024年02月14日
    浏览(32)
  • uniapp使用uni自带websocket进行即时通讯

    最近再办一个uniapp做的即时通讯,把其中思路记载一下。 技术栈采用uniapp+uview+vue2进行开发。 下面的从uniapp官网截图的Api  uni.connectSocket() :这个方法可以让我们创建一个webSocket的连接,里面包含几个参数,url是写ws的连接地址,没有的话肯定是连接不上服务器,其他的没用

    2024年02月01日
    浏览(31)
  • Netty+springboot开发即时通讯系统笔记(二)

    1.数据库设计,在csdn上搞定了 2.用户单表增删改查。 全局异常处理:RestControllerAdvice在类上,ExceptionHandler在方法上,里面有个value值,可以写java提供的异常以及自定义异常 3.好友: 弱好友关系,关注粉丝 强好友关系:qq,正常数据库中写一条就可以了,但是查询的时候很麻

    2024年02月12日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包