zoukankan      html  css  js  c++  java
  • go语言从零学起(三) -- chat实现的思考

    要通过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  = 1024
        writeBufferSize = 1024
    )
    
    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(10 * 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
    java之MySQL的使用
    java反射
    java多线程
    java异常
    指针综合
    指向函数的指针变量做函数的参数
    指向函数的指针
    字符串指针法赋值
    字符串冒泡排序和折半查找
  • 原文地址:https://www.cnblogs.com/cornor/p/6146891.html
Copyright © 2011-2022 走看看