简介:
- 实现用户昵称上传以及多用户在线同一房间聊天(单聊天室)
展示
-
1.server端(显示连接客户端ip和用户昵称及聊天室信息)
-
2.client端
-
创建2个客户端client1,client2(客户端代码创建2份)
-
client1(输入昵称,输入发送消息)
-
-
client2
-
技术提升点:
- 1.使用协程处理多客户端连接操作
- 2.理解客户端与服务器之间如何传输数据以及对数据的处理
- 3.使用channel实现多协程之间的数据交互
- 4.熟悉golang语法
结构及其功能:
1.项目结构图
2.服务端
数据部分
- broadcast:存储用户发送的消息,一个Msg类型的channel
- onlineclient:存储用户ip与管道的map
- Msg:存储消息的结构,由user和content组成,
- Client:存储用户信息的结构,由addr和chan组成,
函数部分
- MessageManager:监控broadcast中的信息并转为json格式后传入所有用户的管道
- HandleConnect:客户端管理客户端连接,并将用户信息保存到onlineclient中
- read:读取客户端信息并且保存到broadcast中
- write:将用户管道中的信息返回客户端
3.客户端
1.函数部分
- read:读取服务端返回的信息并且转化为对象存储,然后输出
- write:获取标准输入流中的数据发送给服务端
项目代码
1.server
package main
import (
"encoding/json"
"fmt"
"net"
"strings"
)
// 消息的结构体:注意字段和结构体首字母大写
type Msg struct {
Content string // 因为传递的数据结构为json
User string
}
// 客户信息结构体
type Client struct {
Chan chan []byte //信息管道
Addr string
}
// 广播管道
var broadcast = make(chan Msg)
// 组合标志名使用驼峰命名法
var onlineClient = make(map[string]Client) //此处map可否改为ip与管道对应?
// 昵称map
var nicks = make(map[string]string)
// 消息管理
func MessageManger() {
for {
msg := <-broadcast //不断的从广播管道中读取数据,没有数据则会阻塞
for _, client := range onlineClient {
msgByte, _ := json.Marshal(msg) //使用json格式对数据进行传输
client.Chan <- msgByte
}
}
}
// 读取客户端的消息,放到broadcast中
func read(conn net.Conn) {
for {
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
content := strings.TrimSpace(string(buf[:n])) //对客户端消息处理
broadcast <- Msg{content, nicks[conn.RemoteAddr().String()]}
fmt.Printf("%s的消息:%s\n", nicks[conn.RemoteAddr().String()], content)
}
}
// 对客户管道写入broadcast中的数据
func write(conn net.Conn, client Client) {
for {
msg := <-client.Chan //读取Msg
_, _ = conn.Write(msg) //返回数据
}
}
// 客户连接管理
func HandleConnect(conn net.Conn) {
tip, _ := json.Marshal(Msg{"请输入昵称~~~", "系统消息"})
_, _ = conn.Write(tip)
//服务器接受数据标准流程:1.创建一个缓冲区,读取数据 2.从缓冲区提取数据并转化为string 3.对字符串进行去除左右空白符
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
nick := strings.TrimSpace(string(buf[:n]))
addr := conn.RemoteAddr().String()
nicks[addr] = string(nick)
fmt.Printf("来自客户端%s的连接,昵称:%s\n", addr, nick)
client := Client{Chan: make(chan []byte), Addr: addr}
onlineClient[addr] = client
content := string(nick) + "上线了~~~~"
//tip := Msg{User:"系统消息", Content:content}
//在可以不使用变量存储的操作,一般只省略一步
broadcast <- Msg{Content: content, User: "系统消息"} //此处使用匿名变量代码更简洁
go read(conn)
go write(conn, client)
}
func main() {
fmt.Println("在线聊天室开启~~~~")
//创建一个监听器
listener, err := net.Listen("tcp", "127.0.0.1:8080")
//错误处理
if err != nil {
fmt.Println(err)
return
}
//服务器开启监听广播
go MessageManger()
//接受每个客户端的连接
for {
conn, err := listener.Accept() //获取连接
if err != nil {
fmt.Println("") //连接错误输出错误信息,重新连接
continue
}
//为每个客户端的连接创建协程
go HandleConnect(conn)
}
}
2.client
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
)
type Msg struct {
Content string
User string
}
// 从服务端读取数据
func read(conn net.Conn) {
for {
res := make([]byte, 1024)
n, err := conn.Read(res)
if err != nil {
fmt.Println(err)
return
}
result := res[:n]
var msg Msg
_ = json.Unmarshal(result, &msg) //json转结构体
fmt.Printf("[%s]:%s\n", msg.User, msg.Content)
}
}
// 往服务端写入数据
func write(conn net.Conn) {
for {
reader := bufio.NewReader(os.Stdin)
content, _ := reader.ReadString('\n')
_, _ = conn.Write([]byte(content))
}
}
func main() {
fmt.Println("客户端启动~~~~")
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
fmt.Println(err)
return
}
defer conn.Close() //客户端控制连接关闭
go read(conn)
write(conn)
}
结尾
现在是凌晨三点,从11点30开始写,刚开始想着30分钟完成,12点睡觉,没想到居然拖到现在。写文章的中间总是参杂着许多细碎的事情,不是去洗衣服,就是吃东西,看微信,抖音,这些与写文章无关的事物。唉,做事总是如此,设立目标,却不贯彻执行,面对有点难度的事情,潜意识反应总是逃避,并顺带找一个看似正当的借口麻痹自己(如:好像有点饿,先吃点东西再看看怎么解决吧),以减轻内心的谴责。但回归理性又何尝不明白,逃避并没有解决问题,不断的逃避,问题就会不断累积,总会有一天会陷入无处可逃的境地。在理性与感性中内心不断的徘徊,好在还有一丝毅力,也算是坚持写完。经此一番,有不少感悟:1.定下目标贯彻执行!2.遇到困难直面解决!3.坚持做正确的事情!
哈哈,亲爱的你如果都看到了这,还有什么理由不关注我呢?
最后的最后祝各位都能得偿所愿,期待咱们的下一次相遇!