要通过go实现一个应用场景:

1 建立一个websocket服务

2 维护在线用户的链接

3 推送消息和接受用户的操作

列出需求,很显然的想到了chat模型。于是研究了revel框架提供的samples/chat代码,以及基于gorilla/websocket实现的chat。

他们实现的思路比较类似,大概代码如下:

package main

import (
"github.com/go-martini/martini"
"github.com/gorilla/websocket"
"net/http"
"time"
) const (
readBufferSize =
writeBufferSize =
) type Client struct {
conn *websocket.Conn
} // 常住监听一个链接的读
func (c *Client) readPump() { for { mstype, message, err := c.conn.ReadMessage() // ....... 该处处理接受用户发来的数据
}
}
//监听一个链接的写
func (c *Client) writePump() { for {
c.conn.SetWriteDeadline(time.Now().Add( * time.Second)) // ....... 该处处理推送给用户的数据
}
} // 基于martini实现的websocket服务
func main() {
m := martini.Classic()
m.Get("/ws", func(res http.ResponseWriter, req *http.Request) {
conn, err := websocket.Upgrade(res, req, nil, readBufferSize, writeBufferSize)
if err != nil {
log.Println(err)
return
}
client := &Client{conn: conn} go client.writePump() //建立一个goroutine client.readPump() })
m.Run()
}

上述代码只是用来简述运用go语言实现chat的大概思路,在最后会贴出原代码的出处。

通过看上面代码会发现,每个链接进来就需要2个goroutine,去维护它的读跟写。如果要是1万个链接,相应的就要有2万个常驻而不会释放的goroutine去监听。这样的话,到最后会不会只是维护链接就会耗尽服务器的cpu跟内存。因为只是接触go的时间不长,看完代码我有些质疑这样实现的效率问题。为了佐证我的想法,我查了下c基于epoll对链接读写的维护,代码如下:

struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待I/O事件
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) { //如果是主socket的事件,则表示有新连接进入,需要进行新连接的处理。
client = accept(listener, (struct sockaddr *) &local, &addrlen);
if(client < 0){
perror("accept error");
continue;
}
setnonblocking(client); // 将新连接置于非阻塞模式
ev.events = EPOLLIN | EPOLLET;
//注意这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,
//如果有写操作的话,这个时候epoll是不会返回事件的,
//如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET。
ev.data.fd = client; // 并且将新连接也加入EPOLL的监听队列
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { // 设置好event之后,将这个新的event通过epoll_ctl
//加入到epoll的监听队列里,这里用EPOLL_CTL_ADD
//来加一个新的 epoll事件。可以通过EPOLL_CTL_DEL来减少
//一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。
fprintf(stderr, "epoll set insertion error: fd=%d"0, client);
return -1;
}
} else // 如果不是主socket的事件的话,则代表这是一个用户的socket的事件,
// 则用来处理这个用户的socket的事情是,比如说read(fd,xxx)之类,或者一些其他的处理。
do_use_fd(events[n].data.fd);
}
}

从代码层面对比来看,同是1万个链接,c基于epoll的实现只有一个常驻的线程去监听I/O事件,而go需要2万个goroutine去监听。这2种语言实现方式间会不会存在比较明显的性能差距,这是我开始学习的时候,一直存在的疑问。

不过看了这篇文章时,解除了我很多疑惑,也让我对go的goroutine有了深入一点的认识。

https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.html (goroutine的生老病死)

1 goroutine切换时的上下文信息是保存在结构体的sched域中的。goroutine是轻量级的“线程”或者称为协程,切换时变不必陷入到操作系统内核中,所以保存过程很轻量

2 goroutine创建完成完后,会放入相应的队列

3 go会建多个worker进程,worker做的事情就是不停的去任务队列中取一个任务出来执行

4 goroutine准备好的时候,会获取cpu去执行,执行完了会让出cpu然后挂起

模式大概是这样的:

func M() {
for {
sched.lock.Lock() //互斥地从就绪G队列中取一个g出来运行
if sched.allg > 0 {
g := sched.allg[0]
sched.allg = sched.allg[1:]
sched.lock.Unlock()
g.Run() //运行它
} else {
sched.lock.Unlock()
}
}
}
for i:=0; i<GOMAXPROCS; i++ {
go M()
}

上面这些是刚开始学习的一些疑惑和感悟,希望之后深入学习后,对go有更全面的认识。

参考资料:

1 https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.html

2 https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.3.html

3 http://leejia.blog.51cto.com/4356849/1021066

revel的chat代码

https://github.com/revel/samples

gorilla/websocket的chat代码

https://github.com/gorilla/websocket/tree/master/examples  

最新文章

  1. ZTE and TP-Link RomPager - DoS Exploit
  2. iOS KVO概述
  3. OnItemSelectedListener事件与二级联动
  4. win7下vs2008编译出现C1859错误的处理办法
  5. HW4.15
  6. UWP app HelloWorld 的创建
  7. MySQL事务内幕与ACID
  8. java初学第二章
  9. 如莲开发平台(MIS基础框架、Java技术、B/S结构)
  10. 通过钩子程序跨程序关闭Window
  11. 自己总结的C#编码规范--7.文档下载 &amp; 总结
  12. mysql授权用户,撤销用户,撤销权限基本操作
  13. jenkins使用2----基本实例
  14. 第四百零六节,自定义用户表类来继承Django的用户表类,
  15. Node.js 回调函数 1) 阻塞 ,同步 2) 非阻塞 ,异步.
  16. webpack.dev.conf.js
  17. 图解 SQL-Server新建作业
  18. 初识thinkphp(3)
  19. (转)ldd 查看程序依赖库
  20. 《metasploit渗透测试魔鬼训练营》学习笔记第六章--客户端渗透

热门文章

  1. Scrum Meeting 10.27
  2. Sprint10
  3. 深入理解mybatis
  4. 福大软工 Alpha 事后诸葛亮
  5. 【搜索】POJ-3009 DFS+回溯
  6. C#设置代码只在调试模式下执行
  7. Software Defined Networking(Week 2, part 3)
  8. TCP/IP Illustrated Vol1 Second Edition即英文版第二版,TCP部分个人勘误
  9. jsp 页面和 jsp标记
  10. JPEG图像压缩算法流程详解