一、目的

运用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. Python3正则表达式(4)

    正则表示式的子模式 使用()表示一个子模式,括号中的内容作为一个整体出现. (red)+  ==> redred, redredred, 等多个red重复的情况 子模式的扩展语法 案例1 tel ...

  2. centos6.9 忘记密码解决方法

    若果忘记了 root 的密码,解决方法如下: 我采用的 linux 版本是 centos-6.9 , 经过亲身实践证明,该方法是 ok 的 在开机启动的时候按键盘上的“E”键会进入如下界面. 选择相应 ...

  3. unity3d脚本语言中的引用类型

    在之前的文文里有说到,值类型和引用类型,那么这会就单独说下引用类型: Unity3D中的C#语言提供了专门的类型来为开发者提供使用C#开发游戏的便利条件: 在该引擎中,使用UnityEngine命名空 ...

  4. v$instance如何生成

    参考:http://www.itpub.net/thread-1284858-1-1.html 1.ORACLE 先创建的x$ 表即RDBMS的内部表 2.然后在X$表的基础上创建了GV$ 视图.  ...

  5. __Linux__操作系统发展史

    常见操作系统win7.Mac.Android.iOS . 操作系统的发展史 1.Unix 1965年之前的时候,电脑并不像现在一样普遍,它可不是一般人能碰的起的,除非是军事或者学院的研究机构,而且当时 ...

  6. Mac下如何设置Eclipse默认浏览器为chrome

    http://stackoverflow.com/questions/5322574/how-can-i-set-chrome-as-default-external-browser-in-eclip ...

  7. PHP的unset究竟会不会释放内存?

    首先让我们看一个例子 var_dump(memory_get_usage());    $a = "laruence";    var_dump(memory_get_usage( ...

  8. 奇怪吸引子---LorenzStenflo

    奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...

  9. 【T07】不要低估tcp的性能

    1.tcp在ip的基础上增加了校验和.可靠性和流量控制的功能,而udp只增加了校验和的功能,看起来udp应该会比tcp快很多, 但事实不是这样,有时候tcp比udp的性能还要好. 2.思考,在什么情况 ...

  10. 最新版本Zabbix3.4+Grafana5 整合部署

    一.Grafana简介 Grafana是一个可视化面板(Dashboard),有着非常漂亮的图表和布局展示,功能齐全的度量仪表盘和图形编辑器,支持Graphite.elasticsearch.zabb ...