server.go

package main

import (
"bufio"
"fmt"
"log"
"net"
) /*
服务端程序中包含 4 个 goroutine,分别是一个主 goroutine 和广播(broadcaster)goroutine,每一个连接里面又包含一个连接处理(handleConn)goroutine 和一个客户写入(clientwriter)goroutine。 广播器(broadcaster)是用于如何使用 select 的一个规范说明,因为它需要对三种不同的消息进行响应。 主 goroutine 的工作是监听端口,接受连接客户端的网络连接,对每一个连接,它将创建一个新的 handleConn goroutine。 完整的示例代码如下所示:
*/
func main() { /*
1.main函数就是获得listener对象,然后不停的获取连接上来的conn对象,最后把这些对象丢给处理连接函数进行处理
2.在使用handleConn方法处理conn对象的时候,对不同的连接都启用一个goroutine去并发处理每个conn这样则无须等待。
3.由于要给所有在线的用户发送消息,而不同的用户的conn对象都在不同的goroutine里面,但是Go语言中有channel来处理
各个不同goroutine之间的消息传递,所以这里我们选择使用channel在各个不同的goroutine之间传递广播消息
*/ //开启一个监听端口
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
//开启广播协程
go broadcaster() //不停的监听请求
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
} //开启处理请求的协程
go handleConn(conn)
} } /*
介绍broadcaster广播器,它使用局部变量clients来记录当前连接客户集合,每个客户唯一记录的信息是其对外发送消息通道的ID,下面是细节
*/
//对外发送消息的通道
type client chan<- string var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string) //所有连接的客户端
) /*
在main函数中使用了一个goroutine开启了一个broadcaster函数来负责广播所有用户发送的信息
这里使用一个字典在保存clients,字典的key是各种连接申明的单向并发队列
*/
func broadcaster() {
clients := make(map[client]bool)
for {
/*
使用select开启一个多路复用:
1.每当有广播消息从messages发送进来,都会循环clients对里面的每个channel发消息
2.每当有消息从entering里面发送过来,就会生成一个新的key-value,相当于给clients里面增加一个新的client
3.每当有消息从leaving里面发过来就删掉这个key-value对,并关闭对应的channel
*/
select {
case msg := <-messages:
//把所有接收到的消息广播给所有客户端
//发送消息通道
for cli := range clients {
cli <- msg
}
//上线
case cli := <-entering:
clients[cli] = true //下线
case cli := <-leaving:
delete(clients, cli)
}
}
} /*
下面再来看下每个用户自己的goroutine
handleConn函数创建一个对外发送消息的新通道,然后通过entering通道通知广播新客户到来,
接着它读取客户发来的每一行文本,通过全局接收消息通道将每一行发送给广播,发送时在每一条消息前面加上发送者ID作为前缀
一旦从客户端读取完毕消息,handleConn通过leaving通道通知客户离开然后关闭连接
*/ /*
handleConn函数会为每一个过来处理的conn创建一个新的channel,开启一个新的goroutine去把发送这个channel的消息写进conn
handleConn函数的执行过程可以简单地总结为一下几步
1.获取连接过来的IP地址和端口号
2.把欢迎信息写进channel返回给客户端
3.生成一条广播消息写进messages里
4.把这个channel加入到客户端集合,也就是entering <- ch
5.监听客户端往conn里写入数据,每扫描到一条就讲这条消息发送到广播channel中
6.如果关闭了客户端,如果关闭了客户端,那么把队列离开写入 leaving 交给广播函数去删除这个客户端并关闭这个客户端;
7.广播通知其他客户端该客户端已关闭;
8.最后关闭这个客户端的连接 Conn.Close()。
*/
func handleConn(conn net.Conn) {
ch := make(chan string) //对外发送客户消息的通道
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
ch <- "欢迎" + who
messages <- who + "上线"
entering <- ch input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ":" + input.Text()
}
//注意input.Err()中可能的错误
leaving <- ch
messages <- who + "下线"
conn.Close()
} //ch <-chan string只写管道
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg) //注意:忽略网络层面的错误
}
}

client.go

package main

import (
"io"
"log"
"net"
"os"
) /*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/ //client 是一个简单的TCP服务器读/写客户端
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) //注意:忽略错误
log.Println("done")
done <- struct{}{} //向主goroutine发出信号
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done //等待后台goroutine完成
} func mustCopy(dst net.Conn, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}

client1.go

package main

import (
"io"
"log"
"net"
"os"
) /*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/ //client 是一个简单的TCP服务器读/写客户端
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) //注意:忽略错误
log.Println("done")
done <- struct{}{} //向主goroutine发出信号
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done //等待后台goroutine完成
} func mustCopy(dst net.Conn, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}

client2.go

package main

import (
"io"
"log"
"net"
"os"
) /*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/ //client 是一个简单的TCP服务器读/写客户端
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) //注意:忽略错误
log.Println("done")
done <- struct{}{} //向主goroutine发出信号
}()
mustCopy2(conn, os.Stdin)
conn.Close()
<-done //等待后台goroutine完成
} func mustCopy2(dst net.Conn, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}

每日一抄 Go语言聊天服务器的更多相关文章

  1. 27.app后端搭建聊天服务器的经历

    现在,聊天功能已经成了社交app的标配了.但是,众多web开发出生的程序员对聊天相关的服务的不了解,带来了很多开发上的困扰.在这篇文章中,根据下面3个方面,谈谈聊天服务. 1.      聊天服务的技 ...

  2. [CareerCup] 8.7 Chat Server 聊天服务器

    8.7 Explain how you would design a chat server. In particular, provide details about the various bac ...

  3. 定制的Server-Sent Events 聊天服务器

    //匿名聊天服务器 //将新的消息POST到/chat地址,或者以GET形式从通一个URL获取文本或事件流 //创建一个GET请求到"/"来返回一个简单的HTML文件,这个文件包括 ...

  4. Socket 基础解析使用ServerSocket建立聊天服务器

    很简单的教程哦! 1.socket 简介 Socket 又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求.ServerSocket 用于 ...

  5. openfire:基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件

    基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件 上一篇文章介绍到怎么在自己的Java环境中搭建openfire插件开发的环境,同时介绍到怎样一步步简单的开发openfir ...

  6. 基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件

    原文:http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html 随笔-150  评论- ...

  7. 使用ServerSocket建立聊天服务器(一)

    -------------siwuxie095                             工程名:TestMyServerSocket 包名:com.siwuxie095.socket ...

  8. 使用ServerSocket建立聊天服务器(二)

    -------------siwuxie095                         工程名:TestMyServerSocket 包名:com.siwuxie095.socket 类名:M ...

  9. 使用 ServerSocket 建立聊天服务器-2

    1. 从serverListener中可以看出,每一个客户端创建新的请求之后,都会把它分配给一个独立的chatsocket ,但是每一个ChatSocket都是相互独立的,他们之间并不能沟通,所以要新 ...

  10. 使用 ServerSocket 建立聊天服务器-1

    1.代码目录 2.ChatSocket.java --------------------------------------------------------------------------- ...

随机推荐

  1. php 关于 json字符串使用json_decode 转数组为空

    项目中遇见一个之前没有遇见的问题,就是 转化一个json字符串为数组时 为空,解决办法是使用 htmlspecialchars_decode 处理一下json字符串 然后 再使用 json_decod ...

  2. 阿里云centos7搭建docker,拉取镜像外网访问不通的问题排查

    最近新买了一个阿里云服务器,用docker安装了mysql,外网死活连接不上我的mysql,最终经过一顿猛百度查问题,找出问题根源.对这次查问题过程中出现的问题也有一些心得,也小小记录一下复盘总结. ...

  3. Vue.js的使用经验

    Vue.js的使用经验   Vue.js的意义 解耦了视图与数据 可复用的组件 前端路由 状态管理 虚拟DOM Vue提供了很多实例属性与方法,都以$开头 $el可以访问挂载Vue实例的元素. $se ...

  4. ubuntu亲测安装opencv和成功解决Makefile:160: recipe for target 'all' failed make: *** [all] Error 2

    1.因为项目需要,我安装的是opencv3.0.0,从github上面下载的opencv包 git clone https://github.com/Itseez/opencv.git git clo ...

  5. 网页识别语音插件annyang可以实现识别中文

    <html> <script src="a.js"></script> <script> if (annyang) { var co ...

  6. Visual Studio 安装时,共享组件、工具和SDK的路径无法更改解决方法

    Visual Studio 安装时,共享组件.工具和SDK的路径无法更改解决方法 解决方案: 找到电脑中Visual Studio 2019或其他版本的VS 的注册表,删除共享组件.工具和 SDK 的 ...

  7. (原创)odoo动态设置树形视图中的字段,每个用户可定制自己要显示的字段

    模块详情

  8. 快速傅里叶变换详解(FFT)

    自己也看了几篇博客,但是对我这种不擅长推导小白来说还是有一点困难,所以自己也写一篇博客也为像我一样的小白提供思路.以下内容包含各种LaTeX渲染,如果哪里有错误欢迎大家评论留言,或者添加本人qq:14 ...

  9. RabbitMQ管理界面使用之手动送数据

    目录 1. 找到相应的队列, 点击进入详情 2. 找到Publish Message项,填写自定义数据 3. 发送数据 4. 发送成功 5. 接收 1. 找到相应的队列, 点击进入详情 2. 找到Pu ...

  10. i春秋云镜 CVE-2022-32991

    第一种方法 第二种延时注入