2015-01-26 2 views
1

У меня есть сервер, который дело соединения Session как этотКак я могу закрыть канал элегантно?

type Session struct { 
    conn *net.TCPConn //the tcp connection from client 
    recvChan  chan []byte 
    closeNotiChan chan bool 
    ok bool 
    lock sync.Mutex 
} 

func (sess *Session) Close() { 
    sess.conn.Close() 
    sess.lock.Lock() 
    if sess.ok { 
     sess.ok = false 
     close(sess.closeNotiChan) 
    } 
    sess.lock.Unlock() 
} 

func (sess *Session) handleRecv() { 
    defer func() { 
     sess.Close() 
    }() 
    ch := sess.recvChan 
    header := make([]byte, 2) 
    for { 
     /**block until recieve len(header)**/ 
     n, err := io.ReadFull(sess.conn, header) 
     if n == 0 && err == io.EOF { 
      //Opposite socket is closed 
      log.Warn("Socket Read EOF And Close", sess) 
      break 
     } else if err != nil { 
      //Sth wrong with this socket 
      log.Warn("Socket Wrong:", err) 
      break 
     } 
     //body lenght 
     size := binary.LittleEndian.Uint16(header) + 4 

     data := make([]byte, size) 

     n, err = io.ReadFull(sess.conn, t.Data) 
     if n == 0 && err == io.EOF { 
      log.Warn("Socket Read EOF And Close", sess) 
      break 
     } else if err != nil { 
      log.Warn("Socket Wrong:", err) 
      break 
     } 
     ch <- t //#1 
    } 
} 

func (sess *Session) handleDispatch() { 
    defer func() { 
     sess.Close() 
     close(sess.recvChan)//#2 
     for msg := range sess.recvChan { 
      //do something 
     } 
    }() 
    for { 
     select { 
     case msg, ok := <-sess.recvChan: 
      if ok { 
       //do something 
      } 
     case <-sess.closeNotiChan: 
      return 
     } 
    } 
} 

func StartClient() {//deal a new connection 
    var client Session 
    client.conn = connection 
    client.recvChan = make(chan []byte, 64) 
    client.closeNotiChan = make(chan bool) 
    client.ok = true 
    go client.handleRecv() 
    client.handleDispatch() 
} 

приема данных и диспетчерские данные находятся в разных goroutines.Now У меня есть проблема. При закрытии соединения, есть гонки данных, #1 и #2, что-то вроде следующего (я не отправляю полный код)

WARNING: DATA RACE 
Write by goroutine 179: 
    runtime.closechan() 
     /usr/local/go/src/runtime/chan.go:257 +0x0 
    sanguo/gameserver.func·004() 
     /data/mygo/src/sanguo/gameserver/session.go:149 +0xc7 
    sanguo/gameserver.(*Session).handleDispatch() 
     /data/mygo/src/sanguo/gameserver/session.go:173 +0x413 
    sanguo/gameserver.(*Session).Start() 
     /data/mygo/src/sanguo/gameserver/session.go:223 +0xc6 
    sanguo/gameserver.handleClient() 
     /data/mygo/src/sanguo/gameserver/gameserver.go:79 +0x43 

Previous read by goroutine 126: 
    runtime.chansend() 
     /usr/local/go/src/runtime/chan.go:83 +0x0 
    sanguo/gameserver.(*Session).handleRecv() 
     /data/mygo/src/sanguo/gameserver/session.go:140 +0x1171 

Итак, моя проблема заключается в том, как я могу закрыть recvChan эффективно и элегантной , без «гонки данных» и panic.

+0

Почему у вас есть отсрочка func() {sess.Close}, а не только отложить sess.Close()? –

+1

помогает всегда думать об этом как «отправитель закрывается». Даже если это не происходит напрямую, отправителю [s] всегда необходимо инициировать закрытие. – JimB

+0

сложно сделать так, как «отправитель закрывается», потому что мне нужно передать остальное сообщение в 'recvChan' в' handleDispatch'. Если я закрою его в 'handleRecv',' handleDispatch' может быть заблокирован в течение нескольких секунд. В моем случае , 'handleRecv' выйдет позже, чем' handleDispatch', иногда, может быть, несколько секунд, когда есть много соединений. –

ответ

0

Каналы синхронизируют goroutines посредством отправки, но close на них не синхронизируется. Что делать, если вы закрываете канал, в то время как другой горутин посылал что-то через него?

У вас должен быть еще один канал, через который handleDispatch уведомляет «владельца» (то есть автора) sess.recvChan, а именно handleRecv, что он хочет закрыть канал. Затем handleRecv должен прекратить отправку с него и закрыть его.

Помимо этого: я не понимаю строку рядом с #2: for msg := range sess.recvChan { Что вы пытаетесь добраться туда от sess.recvChan, если вы только что ее закрыли?

+2

Операция приема всегда будет выполняться на закрытом канале, и вы все равно можете получать все предметы в закрытом, забуферированном канале. – JimB

+0

О, я не осознал вторую часть. Благодарю. –

Смежные вопросы