golang 创建一个简单的广播echo服务器
package main; import (
"net"
"fmt"
"bufio"
) //里面的代码部分参考cmu440课程
//https://github.com/cmu440/p0 //广播服务器接口
type MultiEchoServer interface {
//开始
Start(port int) error;
//停止
Close();
//当前客户端连接数
Count() int;
} //广播服务器
type multiEchoServer struct {
lis *net.TCPListener;
//当前客户端ID
curClientId int;
//所有客户端
clients map[int]*client;
//广播消息
broadcastMsg chan []byte;
} //客户端
type client struct {
//ID
id int;
//连接
conn net.Conn;
//接收消息
recvMsg chan []byte;
//发送消息
sendMsg chan []byte;
//接收消息是否关闭
isRecvMsgClose chan bool;
//发送消息是否关闭
isSendMsgClose chan bool;
//服务器
mes *multiEchoServer;
} //返回一个广播服务器
func New() *multiEchoServer {
return &multiEchoServer{
curClientId: 0,
clients: make(map[int]*client),
broadcastMsg: make(chan []byte, 1),
};
} //启动服务器
func (m *multiEchoServer) Start(port int) error {
//获取tcp地址
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port));
if err != nil {
return err;
}
//监听端口
m.lis, err = net.ListenTCP("tcp", addr);
if err != nil {
return err;
}
//启一个goroutine处理广播
go m.BroadcastLoop();
//启一个goroutine处理客户端来的连接
go func() {
for {
conn, err := m.lis.Accept();
if err != nil {
continue;
}
cli := &client{
id: m.curClientId,
conn: conn,
recvMsg: make(chan []byte, 1),
sendMsg: make(chan []byte, 1),
isRecvMsgClose: make(chan bool, 1),
isSendMsgClose: make(chan bool, 1),
mes: m,
}
//加客户端加入到服务器clients中
clis := m.clients;
clis[m.curClientId] = cli;
m.clients = clis;
m.curClientId++; //启两个goroutine分别处理客户端的接收与发送消息
go cli.RecvLoop();
go cli.SendLoop();
}
}();
return nil;
} //停止服务器
func (m *multiEchoServer) Close() {
m.lis.Close();
//循环关闭客户端
for _, client := range m.clients {
client.conn.Close();
//这里只需给一个发送消息就好了
client.isRecvMsgClose <- true;
}
} //返回当前客户端连接数
func (m *multiEchoServer) Count() int {
return len(m.clients);
} //处理广播
func (m *multiEchoServer) BroadcastLoop() {
for {
select {
case data := <-m.broadcastMsg:
{
//遍历所有客户端,循环发送消息
for _, client := range m.clients {
client.sendMsg <- data;
}
break;
}
}
}
} //删除客户端
func (m *multiEchoServer) DelClient(c *client) error {
c.conn.Close();
clis := m.clients;
delete(clis, c.id);
m.clients = clis;
return nil;
} //处理客户端接收消息
func (c *client) RecvLoop() {
defer func() {
fmt.Println(c.conn.RemoteAddr().String() + " RecvLoop exit");
}();
for {
read := bufio.NewReader(c.conn);
data, err := read.ReadBytes('\n');
if err != nil {
c.isSendMsgClose <- true;
//这里直接返回,如果不直接返回
//当客户端退出时,这里会运行2次,导致c.isSendMsgClose<-true执行2次造成阻塞
//没有机会运行后面的select,那么一直无法返回,不能回收。
return;
} select {
//接收消息是否关闭
case <-c.isRecvMsgClose:
{
c.isSendMsgClose <- true;
return;
}
//广播消息
case c.mes.broadcastMsg <- data:
{
break;
}
}
}
} //处理客户端发送消息
func (c *client) SendLoop() {
defer func() {
fmt.Println(c.conn.RemoteAddr().String() + " SendLoop exit");
}();
for {
select {
//发送消息关闭,则把客户端从服务中删除
case <-c.isSendMsgClose:
{
c.mes.DelClient(c);
return;
}
//向客户写入要发送的消息
case data := <-c.sendMsg:
{
_, err := c.conn.Write(data);
if err != nil {
return;
}
}
}
}
} func main() {
mes := New();
mes.Start(8888); //循环
select {};
}

golang 创建一个简单的广播echo服务器的更多相关文章
- golang 创建一个简单的连接池,减少频繁的创建与关闭
一.连接池的描述图片如下: 二.连接池代码如下: package main; import ( "time" "sync" "errors" ...
- golang 创建一个简单的资源池,重用资源,减少GC负担
package main; import ( "sync" "errors" "fmt" ) //代码参考<Go语言实战>中第7 ...
- Golang学习-第二篇 搭建一个简单的Go Web服务器
序言 由于本人一直从事Web服务器端的程序开发,所以在学习Golang也想从Web这里开始学起,如果对Golang还不太清楚怎么搭建环境的朋友们可以参考我的上一篇文章 Golang的简单介绍及Wind ...
- 创建一个简单的 http 服务器
创建一个简单的 http 服务器 直接在 目录下运行 当前的目录即可是root 目录 默认端口8000 应该可以加参数修改端口号 Python2 python -m SimpleHTTPServer ...
- Express 的基本使用(创建一个简单的服务器)
Express 的基本使用(创建一个简单的服务器) const express = require('express') // 创建服务器应用程序 // 相当于 http.creatServer co ...
- 使用 CodeIgniter 创建一个简单的 Web 站点
原文:使用 CodeIgniter 创建一个简单的 Web 站点 参考源自: http://www.ibm.com/developerworks/cn/web/wa-codeigniter/index ...
- [转帖] Linux 创建一个简单的私有CA、发证、吊销证书
原创帖子地址: https://blog.csdn.net/mr_rsq/article/details/71001810 Linux 创建一个简单的私有CA.发证.吊销证书 2017年04月30 ...
- CodeIgniter框架——创建一个简单的Web站点(include MySQL基本操作)
目标 使用 CodeIgniter 创建一个简单的 Web 站点.该站点将有一个主页,显示一些宣传文本和一个表单,该表单将发布到数据库表中. 按照 CodeIgniter 的术语,可将这些需求转换为以 ...
- 如何创建一个简单 APT 仓库
0. 无废话版本 需求: 有一堆 .deb 包,想把它们做成一个 APT 仓库,这样就可以用apk install pkgname进行安装了,这样一方面自己可以规避 dpkg -i xxx.deb 时 ...
随机推荐
- U3D 贴图通道分离后为什么能减小体积
原理上,分离与否,不会减小图片原始体积,还可能增大了. RGBA32 分离后 = RGB24 + A8,这种情况下大小没变 但压缩后就不一样了,因为RGBA32整张图的压缩过程中,每个像素是否可以压缩 ...
- checkbox中jQuery对数组和对象的操作
------------------------------------------------------------------------------------------ 来段小例子,jQu ...
- ArcGIS案例学习笔记4_1_水文分析
ArcGIS案例学习笔记4_1_水文分析 联系方式:谢老师,135_4855_4328,xiexiaokui#139.com 概述 计划时间:第4天上午 教程: pdf page478 数据:实验数据 ...
- tabel 选中行变色和取当前选中行值等问题
先把代码贴出来 $("#tableId tbody tr").mousedown(function () { $('#tableId tr').each(funct ...
- 根据获取的窗口句柄遍历窗口Edit控件
网上说遍历窗口控件有两种方法: 1),使用EnumChildWindows,没有深究, 学习网址如下:http://blog.sina.com.cn/s/blog_60ac1c4b010116 ...
- 常见异常代码oracle
exception oracle error sqlcode value condition no_data_found ora-01403 +100 select into 语句没有符合条件的记录返 ...
- OTU(operational taxonomic units),即操作分类单元
转自http://www.dxy.cn/bbs/topic/35655953 1.OTU是什么? OTU(operational taxonomic units),即操作分类单元.通过一定的距离度量方 ...
- 2.7、CDH 搭建Hadoop在安装(使用向导设置群集)
步骤7:使用向导设置群集 完成“ 群集安装”向导后,“ 群集设置”向导将自动启动.以下部分将指导您完成向导的每个页面: 选择服务 分配角色 设置数据库 查看更改 首次运行命令 恭喜! 选择服务 “ 选 ...
- 云笔记项目-测试时无法连接MySQL Server
事情起因:用Mac提交云笔记项目到SVN后,使用台式机import SVN上的云笔记代码,发现到了台式机上,进行junit测试时无法连接Mysql数据库服务器,而Mac上是可以的.以下是报警内容和报警 ...
- Bootstrap 轮播
[Bootstrap 轮播] 1.要设置一个轮播界面,需要注意以下几点: 1)根div 必须为 class="carousel slide" 2)根div下含有三块子div a)& ...