一、目的

运用Go语言中的goroutine和通道实现一个简单的一个服务器端对多个客户端的在线聊天

软件环境:Goland,Go1.9

代码仓库链接

二、设计思路

与一对一的设计思路类似,就是加了个线程的操作。

1,服务器端声明一个map,并打开监听端口;

2,客户端打开监听端口,同时连入服务器端;

3,在客户端上给自己起一个昵称,并输出,同时启动一个线程;

4,服务器端接收一个昵称,并存入map;

5,声明一个空的字符串,并写入要群发的消息;

6,服务器端解析发送的消息(msg_str[0]的值):

  • nick:使该客户端加入聊天室并广播连上服务器端的所有其他客户端;
  • say:广播客户端发出的消息;
  • quit:使该客户端退出,断开与服务器端的连接,并将退出消息广播给其他连上服务器端的所有其他客户端;

三、Go代码

Server端

// one sever to more client chat room
//This is chat sever
package main import (
"fmt"
"net"
"strings"
) var ConnMap map[string]net.Conn = make(map[string]net.Conn) //声明一个集合 //ConnMap := make(map[string]net.Conn) func main() {
listen_socket, err := net.Listen("tcp", "127.0.0.1:8000") //打开监听接口
if err != nil {
fmt.Println("server start error")
} defer listen_socket.Close()
fmt.Println("server is wating ....") for {
conn, err := listen_socket.Accept() //收到来自客户端发来的消息
if err != nil {
fmt.Println("conn fail ...")
}
fmt.Println(conn.RemoteAddr(), "connect successed") go handle(conn) //创建线程
}
} func handle(conn net.Conn) {
for {
data := make([]byte, ) //创建字节流 (此处同 一对一 通信)
msg_read, err := conn.Read(data) //声明并将从客户端读取的消息赋给msg_read 和err
if msg_read == || err != nil {
continue
} //解析协议
msg_str := strings.Split(string(data[:msg_read]), "|") //将从客户端收到的字节流分段保存到msg_str这个数组中 switch msg_str[] {
case "nick": //加入聊天室
fmt.Println(conn.RemoteAddr(), "-->", msg_str[]) //nick占在数组下标0上,客户端上写的昵称占在数组下标1上
for k, v := range ConnMap { //遍历集合中存储的客户端消息
if k != msg_str[] {
v.Write([]byte("[" + msg_str[] + "]: join..."))
}
}
ConnMap[msg_str[]] = conn
case "say": //转发消息
for k, v := range ConnMap { //k指客户端昵称 v指客户端连接服务器端后的地址
if k != msg_str[] { //判断是不是给自己发,如果不是
fmt.Println("Send "+msg_str[]+" to ", k) //服务器端将消息转发给集合中的每一个客户端
v.Write([]byte("[" + msg_str[] + "]: " + msg_str[])) //给除了自己的每一个客户端发送自己之前要发送的消息
}
}
case "quit": //退出
for k, v := range ConnMap { //遍历集合中的客户端昵称
if k != msg_str[] { //如果昵称不是自己
v.Write([]byte("[" + msg_str[] + "]: quit")) //给除了自己的其他客户端昵称发送退出的消息,并使Write方法阻塞
}
}
delete(ConnMap, msg_str[]) //退出聊天室
}
}
}

Client端

// one sever to more client chat room
//This is chat client
package main import (
"fmt"
"net"
) var nick string = "" //声明聊天室的昵称 func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8000") //打开监听端口
if err != nil {
fmt.Println("conn fail...")
}
defer conn.Close()
fmt.Println("client connect server successed \n") //给自己取一个聊天室的昵称
fmt.Printf("Make a nickname:")
fmt.Scanf("%s", &nick) //输入昵称
fmt.Println("hello : ", nick) //客户端输出
conn.Write([]byte("nick|" + nick)) //将信息发送给服务器端 go Handle(conn) //创建线程 var msg string
for {
msg = "" //声明一个空的消息
fmt.Scan(&msg) //输入消息
conn.Write([]byte("say|" + nick + "|" + msg)) //三段字节流 say | 昵称 | 发送的消息
if msg == "quit" { //如果消息为quit
conn.Write([]byte("quit|" + nick)) //将quit字节流发送给服务器端
break //程序结束运行
}
}
} func Handle(conn net.Conn) { for { data := make([]byte, ) //创建一个字节流
msg_read, err := conn.Read(data) //将读取的字节流赋值给msg_read和err
if msg_read == || err != nil { //如果字节流为0或者有错误
break
} fmt.Println(string(data[:msg_read])) //把字节流转换成字符串
}
}

四、参考资料

Split

五、总结与感受

着重关注收发消息的判定,收消息后的解包过程和开多线程;注意发消息与收消息时字节流与字符串的转换。

从初学Go到一对一再到一对多,我已经逐渐体会到使用Go语言做服务器端的方便与强大。

六、补充:还存在的问题

昨天把代码发给服务器主程大佬看,他看过后提出以下需要考虑和完善的问题,先忽略程序设计上的问题:

程序正确性无法保证

  1. Read可能一次性收到两个包,也可能收到半包。出现以上两种情况的时候协议解析都会出现问题。
  2. Write不保证一次调用时全部写完,存在短写的情况。
  3. ConnMap非线程安全。func handle(conn net.Conn)是多线程环境运行的。
  4. 连接出错及正常短开的情况未处理。

Go语言实践_实现一(服务器端)对多(客户端)在线聊天室的更多相关文章

  1. Go语言实践_实现一(客户端)对一(服务器端)聊天室

    一.目的 使用Go语言实现一个服务器端与客户端的聊天室. 软件:Goland,Go1.9 代码仓库地址 二.思路 1,首先启动服务器端,使用listen_socket函数监听IP地址上的客户端连接: ...

  2. Go语言学习之9 网络协议TCP、Redis与聊天室

    主要内容 1. Tcp编程2. redis使用 1. Tcp编程 (1)简介       Golang是谷歌设计开发的语言,在Golang的设计之初就把高并发的性能作为Golang的主要特性之一,也是 ...

  3. 实践:Backbone作前端,Django+Tastypie作后端的简单Web在线聊天室

    一.界面设计: 二.数据模型设计 id 每个发言都有一个独立的id由tastypie自动生成 content 发言的内容 username 发言者 date 发言时间 三.前端制作 这里没有用到Bac ...

  4. memcached vs MySQL Memory engine table 速度比较_XMPP Jabber即时通讯开发实践_百度空间

    memcached vs MySQL Memory engine table 速度比较_XMPP Jabber即时通讯开发实践_百度空间 memcached vs MySQL Memory engin ...

  5. 提高mysql memory(heap) engine内存性能的开源补丁_XMPP Jabber即时通讯开发实践_百度空间

    提高mysql memory(heap) engine内存性能的开源补丁_XMPP Jabber即时通讯开发实践_百度空间 提高mysql memory(heap) engine内存性能的开源补丁

  6. 《程序设计语言——实践之路》【PDF】下载

    程序设计语言--实践之路>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382240 内容简介 本书在美国大学已有使用了十余年,目前被欧 ...

  7. 《程序设计语言——实践之路(英文第三版)》【PDF】下载

    <程序设计语言--实践之路(英文第三版)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382234 内容简介 <程序设计语 ...

  8. 《程序设计语言——实践之路【PDF】下载

    <程序设计语言--实践之路[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382240 内容简介 <程序设计语言--实践之路(第3版 ...

  9. R语言︱H2o深度学习的一些R语言实践——H2o包

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言H2o包的几个应用案例 笔者寄语:受启发 ...

随机推荐

  1. u3d 鼠标点击位置,物体移动过去。 U3d mouse clicks position, objects move past.

    u3d 鼠标点击位置,物体移动过去. U3d mouse clicks position, objects move past. 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱: ...

  2. C#种将String类型转换成int型

    API: 有一点是需要注意的,那就是必须保证该String类型内全为数字,能确保转换正确: 1.int.Parse(str); 2.TryParse(str, out intA); 3. Conver ...

  3. AngularJS中获取数据源的几种方式

    在AngularJS中,可以从$rootScope中获取数据源,也可以把获取数据的逻辑封装在service中,然后注入到app.run函数中,或者注入到controller中.本篇就来整理获取数据的几 ...

  4. copy unicode HTML to clipboard

    How to copy unicode HTML code to the clipboard in html format, so it can be pasted into Writer, Word ...

  5. android:碎片的生命周期

    和活动一样,碎片也有自己的生命周期,并且它和活动的生命周期实在是太像了,我相 信你很快就能学会,下面我们马上就来看一下. 4.3.1    碎片的状态和回调 还记得每个活动在其生命周期内可能会有哪几种 ...

  6. 解决springboot druid 数据库批量更新错误问题

    原文:https://www.2cto.com/kf/201712/706399.html springboot druid 数据库多SQL错误multi-statement not allow Ca ...

  7. java实现八种排序算法并测试速度

    速度测试: (1) 随机数范围:0-100希尔排序: => Time is 38600基数排序: => Time is 53300快速排序: => Time is 46500堆  排 ...

  8. AMBA APB总线

    前面分析了AHB总线协议.接下来分析APB总线协议. (一) APB总线接口: PCLK APB总线时钟. PRESETn APB总线复位.低有效. PADDR 地址总线. PSELx 从设备选择. ...

  9. 全排列(Perm)的递归实现算法

    https://blog.csdn.net/zhi_jin/article/details/69267230 什么是全排列] 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个 ...

  10. 【GPU编解码】GPU硬解码---DXVA (转)

    前面介绍利用NVIDIA公司提供的CUVID库进行视频硬解码,下面将介绍利用DXVA进行硬解码. 一.DXVA介绍 DXVA是微软公司专门定制的视频加速规范,是一种接口规范.DXVA规范制定硬件加速解 ...