GoProject--Go语言实现简易聊天室

Go语言实现简易聊天室

项目结构

主程序

main.go

服务器

server.go
user.go

客户端

client.go

服务器端

主程序

main.go

package main

import (
	"fmt"

	"example/server"
)

func main() {
	fmt.Println("服务器开启")
	server := server.NewServer("127.0.0.1", 8888)
	server.Start()
}

server.go

服务器API的属性:

type Server struct {
	Ip   string
	Port int
	//map在线用户列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息广播的channel
	Message chan string
}

包含IP、端口、在线用户信息(以及锁)、用于接收需要广播的消息channel

新建一个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得到go程,一旦有消息,就发送给全部在线的User

func (this *Server) ListenMessage() {
	for {

		msg := <-this.Message
		//将msg发送给全部在线用户

		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

}

当有用户上线时,将用户信息发送到this.Message,进行广播

服务器处理连接

func (this *Server) Handler(conn net.Conn) {
	//当前连接的业务
	fmt.Println("连接建立成功")
	user := NewUser(conn, this)

	//用户上线,将用户加入到OnlineMap中
	user.Online()

	//监听用户是否活跃的channel
	isLive := make(chan bool)

	//广播当前用户上线消息
	//this.BroadCast(user, "当前用户已上线")

	//接收客户端发送的消息
	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 err : ", err)
				return
			}
			msg := string(buf[:n-1])
			//用户针对msg进行消息处理
			user.DoMessage(msg)

			//用户的任意消息,代表当前用户是一个活跃的
			isLive <- true

		}

	}()
	//当前handler阻塞
	for {
		select {
		case <-isLive:
			//当前用户活跃,重置定时器
			//不做任何事情,为了激活select更新下面的定时器
		case <-time.After(time.Second * 60):
			//已经超时
			//将当前客户端强制关闭
			user.SendMessage("超时退出\n")
			//销毁用的资源
			close(user.C)
			//关闭连接
			conn.Close()
			//退出当前handler
			return //runtime.Goexit()

		}
	}

}

做的事情:

  1. (连接建立成功),根据连接的客户端信息,创建一个新的用户API信息,并将其保存到在线列表中(map),广播当前用户的上线信息

  2. 监听连接

​ 1)当接收到数据时,对接收的信息进行处理,并将此连接“保活”

​ 2)当接收大小为0时,说明用户下线了,发送用户下线通知,并退出

  1. 通过select设置定时器,超时退出
for {
		select {
		case <-isLive:
			//当前用户活跃,重置定时器
			//不做任何事情,为了激活select更新下面的定时器
		case <-time.After(time.Second * 60):
			//已经超时
			//将当前客户端强制关闭
			user.SendMessage("超时退出\n")
			//销毁用的资源
			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
	}
	defer listener.Close()

	go this.ListenMessage()//监听Message广播消息channel得到go程,一旦有消息,就发送给全部在线的User

	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("net Accept err:", err)
			continue
		}
		//do handler
		go this.Handler(conn)
	}

	//close listen socket
}

不断接收新连接,当有新连接建立时,开启一个go程进行处理

user.go

用户信息的API属性

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,
	}

	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, "当前用户已下线")
}

给当前用户发消息

func (this *User) SendMessage(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.SendMessage(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.SendMessage("当前用户名被使用\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.SendMessage("您已经更新用户名为:" + this.Name + "\n")
		}
	} else if len(msg) > 4 && msg[:3] == "to|" {
		//消息格式: to|张三|消息
		//1 获取对方用户名字
		remoteName := strings.Split(msg, "|")[1]
		if remoteName == "" {
			this.SendMessage("消息格式错误,请使用 to|姓名|消息内容 \n")
		}
		//2 根据获取的用户名获取对方user对象
		remoteUser, ok := this.server.OnlineMap[remoteName]
		if !ok {
			this.SendMessage("用户 [ " + remoteName + "  ]不存在\n")
			return
		}
		//3 获取消息内容,通过对方的user对象将消息发送
		content := strings.Split(msg, "|")[2]

		if content == "" {
			this.SendMessage("无效内容,请重新发送\n")
			return
		}
		remoteUser.SendMessage(this.Name + "和您说:" + content + "\n")
	} else {
		this.server.BroadCast(this, msg)
	}
}

监听当前user channel ,一旦有消息就直接发送给客户端

func (this *User) ListenMessage() {

	for {

		msg := <-this.C

		this.conn.Write([]byte(msg + "\n"))

	}
}

客户端

client.go

客户端API属性

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int //当前模式
}

要连接的服务器信息,与服务器连接的conn,和模式

创建一个客户端对象

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
		flag:       999,
	}
	//链接服务器
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
	if err != nil {
		fmt.Println("net.Dial err:", err)
		return nil
	}
	client.conn = conn
	//返回对象
	return client
}

处理server回应的消息,直接显示到标准输出即可

func (client *Client) DealResponse() {
	io.Copy(os.Stdout, client.conn) //相当于下列的简写,一旦client.conn有消息,就直接copy到标准输出,永久阻塞监听上
	/*for {
		buf := make([]byte, 4096)
		client.conn.Read(buf)
		fmt.Println(string(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(">>>请输入合法字符<<<\n")
		return false
	}
}

发送who给服务器,查询有哪些在线用户

func (client *Client) selectUser() {
	sendMsg := "who\n"
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("client.conn.Write err:", err)
		return
	}
}

私聊

func (client *Client) PrivateChat() {
	var remoteName string
	var chatMsg string
	client.selectUser()
	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("client.conn.Write err", err)
					break
				}

			}
			chatMsg = ""
			fmt.Println("输入聊天内容,exit退出")
			fmt.Scanln(&chatMsg)
		}
		client.selectUser()
		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
}

根据当前客户端的flag,进入对应模式

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:
			//更新用户名
			//fmt.Println("更新用户名...")
			client.UpdateName()
			break

		}
	}
}
flag

接收命令行参数,以及默认参数

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

	}
	//单独开启一个go程去处理server的回执消息
	go client.DealResponse()
	fmt.Println(">>>连接服务器成功")
	//启动客户端的业务
	//	select {}
	client.Run()
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值