mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
websocket: from 1k to 100k on a simple raspeberry pi 3 model b by using a bit lower level of the new ws lib api and restore the previous sync.Map for server's live connections, relative: https://github.com/kataras/iris/issues/1178
Former-commit-id: 40da148afb66a42d47285efce324269d66ed3b0e
This commit is contained in:
parent
eb22309aec
commit
65c1fbf7f2
|
@ -6,6 +6,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris/websocket2"
|
"github.com/kataras/iris/websocket2"
|
||||||
|
@ -16,7 +17,28 @@ var (
|
||||||
f *os.File
|
f *os.File
|
||||||
)
|
)
|
||||||
|
|
||||||
const totalClients = 1200
|
const totalClients = 100000
|
||||||
|
|
||||||
|
var connectionFailures uint64
|
||||||
|
|
||||||
|
var (
|
||||||
|
disconnectErrors []error
|
||||||
|
connectErrors []error
|
||||||
|
errMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func collectError(op string, err error) {
|
||||||
|
errMu.Lock()
|
||||||
|
defer errMu.Unlock()
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case "disconnect":
|
||||||
|
disconnectErrors = append(disconnectErrors, err)
|
||||||
|
case "connect":
|
||||||
|
connectErrors = append(connectErrors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
var err error
|
||||||
|
@ -26,29 +48,93 @@ func main() {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
for i := 0; i < totalClients/2; i++ {
|
for i := 0; i < totalClients/4; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go connect(wg, 5*time.Second)
|
go connect(wg, 5*time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < totalClients/2; i++ {
|
for i := 0; i < totalClients/4; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
waitTime := time.Duration(rand.Intn(5)) * time.Millisecond
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
go connect(wg, 7*time.Second+waitTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < totalClients/4; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
waitTime := time.Duration(rand.Intn(10)) * time.Millisecond
|
waitTime := time.Duration(rand.Intn(10)) * time.Millisecond
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
go connect(wg, 10*time.Second+waitTime)
|
go connect(wg, 10*time.Second+waitTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < totalClients/4; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
waitTime := time.Duration(rand.Intn(20)) * time.Millisecond
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
go connect(wg, 25*time.Second+waitTime)
|
||||||
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
fmt.Println("ALL OK.")
|
fmt.Println("--------================--------------")
|
||||||
time.Sleep(5 * time.Second)
|
fmt.Printf("execution time [%s]", time.Since(start))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if connectionFailures > 0 {
|
||||||
|
fmt.Printf("Finished with %d/%d connection failures. Please close the server-side manually.\n", connectionFailures, totalClients)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(connectErrors); n > 0 {
|
||||||
|
fmt.Printf("Finished with %d connect errors:\n", n)
|
||||||
|
var lastErr error
|
||||||
|
var sameC int
|
||||||
|
|
||||||
|
for i, err := range connectErrors {
|
||||||
|
if lastErr != nil {
|
||||||
|
if lastErr.Error() == err.Error() {
|
||||||
|
sameC++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sameC > 0 {
|
||||||
|
fmt.Printf("and %d more like this...\n", sameC)
|
||||||
|
sameC = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[%d] - %v\n", i+1, err)
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(disconnectErrors); n > 0 {
|
||||||
|
fmt.Printf("Finished with %d disconnect errors\n", n)
|
||||||
|
for i, err := range disconnectErrors {
|
||||||
|
if err == websocket.ErrAlreadyDisconnected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[%d] - %v\n", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if connectionFailures == 0 && len(connectErrors) == 0 && len(disconnectErrors) == 0 {
|
||||||
|
fmt.Println("ALL OK.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("--------================--------------")
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(wg *sync.WaitGroup, alive time.Duration) {
|
func connect(wg *sync.WaitGroup, alive time.Duration) {
|
||||||
|
|
||||||
c, err := websocket.Dial(nil, url, websocket.ConnectionConfig{})
|
c, err := websocket.Dial(nil, url, websocket.ConnectionConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
atomic.AddUint64(&connectionFailures, 1)
|
||||||
|
collectError("connect", err)
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.OnError(func(err error) {
|
c.OnError(func(err error) {
|
||||||
|
@ -68,7 +154,7 @@ func connect(wg *sync.WaitGroup, alive time.Duration) {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(alive)
|
time.Sleep(alive)
|
||||||
if err := c.Disconnect(); err != nil {
|
if err := c.Disconnect(); err != nil {
|
||||||
panic(err)
|
collectError("disconnect", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
@ -80,6 +166,8 @@ func connect(wg *sync.WaitGroup, alive time.Duration) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Emit("chat", scanner.Text())
|
if text := scanner.Text(); len(text) > 1 {
|
||||||
|
c.Emit("chat", text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,3 @@ Far curiosity incommode now led smallness allowance. Favour bed assure son thing
|
||||||
Windows talking painted pasture yet its express parties use. Sure last upon he same as knew next. Of believed or diverted no rejoiced. End friendship sufficient assistance can prosperous met. As game he show it park do. Was has unknown few certain ten promise. No finished my an likewise cheerful packages we. For assurance concluded son something depending discourse see led collected. Packages oh no denoting my advanced humoured. Pressed be so thought natural.
|
Windows talking painted pasture yet its express parties use. Sure last upon he same as knew next. Of believed or diverted no rejoiced. End friendship sufficient assistance can prosperous met. As game he show it park do. Was has unknown few certain ten promise. No finished my an likewise cheerful packages we. For assurance concluded son something depending discourse see led collected. Packages oh no denoting my advanced humoured. Pressed be so thought natural.
|
||||||
|
|
||||||
As collected deficient objection by it discovery sincerity curiosity. Quiet decay who round three world whole has mrs man. Built the china there tried jokes which gay why. Assure in adieus wicket it is. But spoke round point and one joy. Offending her moonlight men sweetness see unwilling. Often of it tears whole oh balls share an.
|
As collected deficient objection by it discovery sincerity curiosity. Quiet decay who round three world whole has mrs man. Built the china there tried jokes which gay why. Assure in adieus wicket it is. But spoke round point and one joy. Offending her moonlight men sweetness see unwilling. Often of it tears whole oh balls share an.
|
||||||
|
|
||||||
Lose eyes get fat shew. Winter can indeed letter oppose way change tended now. So is improve my charmed picture exposed adapted demands. Received had end produced prepared diverted strictly off man branched. Known ye money so large decay voice there to. Preserved be mr cordially incommode as an. He doors quick child an point at. Had share vexed front least style off why him.
|
|
||||||
|
|
||||||
He unaffected sympathize discovered at no am conviction principles. Girl ham very how yet hill four show. Meet lain on he only size. Branched learning so subjects mistress do appetite jennings be in. Esteems up lasting no village morning do offices. Settled wishing ability musical may another set age. Diminution my apartments he attachment is entreaties announcing estimating. And total least her two whose great has which. Neat pain form eat sent sex good week. Led instrument sentiments she simplicity.
|
|
||||||
|
|
||||||
Months on ye at by esteem desire warmth former. Sure that that way gave any fond now. His boy middleton sir nor engrossed affection excellent. Dissimilar compliment cultivated preference eat sufficient may. Well next door soon we mr he four. Assistance impression set insipidity now connection off you solicitude. Under as seems we me stuff those style at. Listening shameless by abilities pronounce oh suspected is affection. Next it draw in draw much bred.
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/kataras/iris/websocket2"
|
"github.com/kataras/iris/websocket2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const totalClients = 1200
|
const totalClients = 100000
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
|
@ -808,7 +808,7 @@ func DialContext(ctx stdContext.Context, url string, cfg ConnectionConfig) (Clie
|
||||||
ctx = stdContext.Background()
|
ctx = stdContext.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(url, "ws://") || !strings.HasPrefix(url, "wss://") {
|
if !strings.HasPrefix(url, "ws://") && !strings.HasPrefix(url, "wss://") {
|
||||||
url = "ws://" + url
|
url = "ws://" + url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# This is the official list of Iris Websocket authors for copyright
|
|
||||||
# purposes.
|
|
||||||
|
|
||||||
Gerasimos Maropoulos <kataras2006@hotmail.com>
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2017-2018 The Iris Websocket Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Iris nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
stdContext "context"
|
stdContext "context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -267,6 +268,9 @@ type (
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
values ConnectionValues
|
values ConnectionValues
|
||||||
server *Server
|
server *Server
|
||||||
|
|
||||||
|
writer *wsutil.Writer
|
||||||
|
|
||||||
// #119 , websocket writers are not protected by locks inside the gorilla's websocket code
|
// #119 , websocket writers are not protected by locks inside the gorilla's websocket code
|
||||||
// so we must protect them otherwise we're getting concurrent connection error on multi writers in the same time.
|
// so we must protect them otherwise we're getting concurrent connection error on multi writers in the same time.
|
||||||
writerMu sync.Mutex
|
writerMu sync.Mutex
|
||||||
|
@ -304,6 +308,8 @@ func newConnection(conn net.Conn, cfg ConnectionConfig) *connection {
|
||||||
c.defaultMessageType = BinaryMessage
|
c.defaultMessageType = BinaryMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// c.writer = wsutil.NewWriter(conn, c.getState(), c.defaultMessageType)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,17 +356,26 @@ func (c *connection) getState() ws.State {
|
||||||
|
|
||||||
// Write writes a raw websocket message with a specific type to the client
|
// Write writes a raw websocket message with a specific type to the client
|
||||||
// used by ping messages and any CloseMessage types.
|
// used by ping messages and any CloseMessage types.
|
||||||
func (c *connection) Write(websocketMessageType ws.OpCode, data []byte) error {
|
func (c *connection) Write(websocketMessageType ws.OpCode, data []byte) (err error) {
|
||||||
// for any-case the app tries to write from different goroutines,
|
// for any-case the app tries to write from different goroutines,
|
||||||
// we must protect them because they're reporting that as bug...
|
// we must protect them because they're reporting that as bug...
|
||||||
c.writerMu.Lock()
|
c.writerMu.Lock()
|
||||||
|
defer c.writerMu.Unlock()
|
||||||
if writeTimeout := c.config.WriteTimeout; writeTimeout > 0 {
|
if writeTimeout := c.config.WriteTimeout; writeTimeout > 0 {
|
||||||
// set the write deadline based on the configuration
|
// set the write deadline based on the configuration
|
||||||
c.underline.SetWriteDeadline(time.Now().Add(writeTimeout))
|
c.underline.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
err := wsutil.WriteMessage(c.underline, c.getState(), websocketMessageType, data)
|
// 2.
|
||||||
c.writerMu.Unlock()
|
// if websocketMessageType != c.defaultMessageType {
|
||||||
|
// err = wsutil.WriteMessage(c.underline, c.getState(), websocketMessageType, data)
|
||||||
|
// } else {
|
||||||
|
// _, err = c.writer.Write(data)
|
||||||
|
// c.writer.Flush()
|
||||||
|
// }
|
||||||
|
|
||||||
|
err = wsutil.WriteMessage(c.underline, c.getState(), websocketMessageType, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if failed then the connection is off, fire the disconnect
|
// if failed then the connection is off, fire the disconnect
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
|
@ -440,29 +455,125 @@ func (c *connection) isErrClosed(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *connection) startReader() {
|
func (c *connection) startReader() {
|
||||||
|
defer c.Disconnect()
|
||||||
|
|
||||||
hasReadTimeout := c.config.ReadTimeout > 0
|
hasReadTimeout := c.config.ReadTimeout > 0
|
||||||
|
|
||||||
for {
|
controlHandler := wsutil.ControlFrameHandler(c.underline, c.getState())
|
||||||
if c == nil || c.underline == nil || atomic.LoadUint32(&c.disconnected) > 0 {
|
rd := wsutil.Reader{
|
||||||
return
|
Source: c.underline,
|
||||||
}
|
State: c.getState(),
|
||||||
|
CheckUTF8: false,
|
||||||
|
SkipHeaderCheck: false,
|
||||||
|
OnIntermediate: controlHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
if hasReadTimeout {
|
if hasReadTimeout {
|
||||||
// set the read deadline based on the configuration
|
// set the read deadline based on the configuration
|
||||||
c.underline.SetReadDeadline(time.Now().Add(c.config.ReadTimeout))
|
c.underline.SetReadDeadline(time.Now().Add(c.config.ReadTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
data, code, err := wsutil.ReadData(c.underline, c.getState())
|
hdr, err := rd.NextFrame()
|
||||||
if code == CloseMessage || c.isErrClosed(err) {
|
if err != nil {
|
||||||
c.Disconnect()
|
return
|
||||||
|
}
|
||||||
|
if hdr.OpCode.IsControl() {
|
||||||
|
if err := controlHandler(hdr, &rd); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.OpCode&TextMessage == 0 && hdr.OpCode&BinaryMessage == 0 {
|
||||||
|
if err := rd.Discard(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(&rd)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.FireOnError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.messageReceived(data)
|
c.messageReceived(data)
|
||||||
|
|
||||||
|
// 4.
|
||||||
|
// var buf bytes.Buffer
|
||||||
|
// data, code, err := wsutil.ReadData(struct {
|
||||||
|
// io.Reader
|
||||||
|
// io.Writer
|
||||||
|
// }{c.underline, &buf}, c.getState())
|
||||||
|
// if err != nil {
|
||||||
|
// if _, closed := err.(*net.OpError); closed && code == 0 {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// } else if _, closed = err.(wsutil.ClosedError); closed {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// // > 1200 conns but I don't know why yet:
|
||||||
|
// } else if err == ws.ErrProtocolOpCodeReserved || err == ws.ErrProtocolNonZeroRsv {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// } else if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.FireOnError(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.messageReceived(data)
|
||||||
|
|
||||||
|
// 2.
|
||||||
|
// header, err := reader.NextFrame()
|
||||||
|
// if err != nil {
|
||||||
|
// println("next frame err: " + err.Error())
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if header.OpCode == ws.OpClose { // io.EOF.
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// payload := make([]byte, header.Length)
|
||||||
|
// _, err = io.ReadFull(reader, payload)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if header.Masked {
|
||||||
|
// ws.Cipher(payload, header.Mask, 0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.messageReceived(payload)
|
||||||
|
|
||||||
|
// data, code, err := wsutil.ReadData(c.underline, c.getState())
|
||||||
|
// // if code == CloseMessage || c.isErrClosed(err) {
|
||||||
|
// // c.Disconnect()
|
||||||
|
// // return
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// if _, closed := err.(*net.OpError); closed && code == 0 {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// } else if _, closed = err.(wsutil.ClosedError); closed {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// // > 1200 conns but I don't know why yet:
|
||||||
|
// } else if err == ws.ErrProtocolOpCodeReserved || err == ws.ErrProtocolNonZeroRsv {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// } else if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
// c.Disconnect()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.FireOnError(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.messageReceived(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -801,6 +912,16 @@ var ErrBadHandshake = ws.ErrHandshakeBadConnection
|
||||||
//
|
//
|
||||||
// Custom dialers can be used by wrapping the iris websocket connection via `websocket.WrapConnection`.
|
// Custom dialers can be used by wrapping the iris websocket connection via `websocket.WrapConnection`.
|
||||||
func Dial(ctx stdContext.Context, url string, cfg ConnectionConfig) (ClientConnection, error) {
|
func Dial(ctx stdContext.Context, url string, cfg ConnectionConfig) (ClientConnection, error) {
|
||||||
|
c, err := dial(ctx, url, cfg)
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
c, err = dial(ctx, url, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dial(ctx stdContext.Context, url string, cfg ConnectionConfig) (ClientConnection, error) {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = stdContext.Background()
|
ctx = stdContext.Background()
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,9 @@ type (
|
||||||
// Use a route to serve this file on a specific path, i.e
|
// Use a route to serve this file on a specific path, i.e
|
||||||
// app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) })
|
// app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) })
|
||||||
ClientSource []byte
|
ClientSource []byte
|
||||||
connections map[string]*connection // key = the Connection ID.
|
connections sync.Map // key = the Connection ID. // key = the Connection ID.
|
||||||
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
|
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
|
||||||
mu sync.RWMutex // for rooms and connections.
|
mu sync.RWMutex // for rooms.
|
||||||
onConnectionListeners []ConnectionFunc
|
onConnectionListeners []ConnectionFunc
|
||||||
//connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
|
//connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
|
||||||
upgrader ws.HTTPUpgrader
|
upgrader ws.HTTPUpgrader
|
||||||
|
@ -64,7 +64,7 @@ func New(cfg Config) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1),
|
ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1),
|
||||||
connections: make(map[string]*connection),
|
connections: sync.Map{}, // ready-to-use, this is not necessary.
|
||||||
rooms: make(map[string][]string),
|
rooms: make(map[string][]string),
|
||||||
onConnectionListeners: make([]ConnectionFunc, 0),
|
onConnectionListeners: make([]ConnectionFunc, 0),
|
||||||
upgrader: ws.DefaultHTTPUpgrader, // ws.DefaultUpgrader,
|
upgrader: ws.DefaultHTTPUpgrader, // ws.DefaultUpgrader,
|
||||||
|
@ -126,14 +126,19 @@ func (s *Server) Upgrade(ctx context.Context) Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) addConnection(c *connection) {
|
func (s *Server) addConnection(c *connection) {
|
||||||
s.mu.Lock()
|
s.connections.Store(c.id, c)
|
||||||
s.connections[c.id] = c
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getConnection(connID string) (*connection, bool) {
|
func (s *Server) getConnection(connID string) (*connection, bool) {
|
||||||
c, ok := s.connections[connID]
|
if cValue, ok := s.connections.Load(connID); ok {
|
||||||
return c, ok
|
// this cast is not necessary,
|
||||||
|
// we know that we always save a connection, but for good or worse let it be here.
|
||||||
|
if conn, ok := cValue.(*connection); ok {
|
||||||
|
return conn, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapConnection wraps an underline connection to an iris websocket connection.
|
// wrapConnection wraps an underline connection to an iris websocket connection.
|
||||||
|
@ -278,24 +283,34 @@ func (s *Server) leave(roomName string, connID string) (left bool) {
|
||||||
|
|
||||||
// GetTotalConnections returns the number of total connections
|
// GetTotalConnections returns the number of total connections
|
||||||
func (s *Server) GetTotalConnections() (n int) {
|
func (s *Server) GetTotalConnections() (n int) {
|
||||||
s.mu.RLock()
|
s.connections.Range(func(k, v interface{}) bool {
|
||||||
n = len(s.connections)
|
n++
|
||||||
s.mu.RUnlock()
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConnections returns all connections
|
// GetConnections returns all connections
|
||||||
func (s *Server) GetConnections() []Connection {
|
func (s *Server) GetConnections() []Connection {
|
||||||
s.mu.RLock()
|
// first call of Range to get the total length, we don't want to use append or manually grow the list here for many reasons.
|
||||||
conns := make([]Connection, len(s.connections))
|
length := s.GetTotalConnections()
|
||||||
|
conns := make([]Connection, length, length)
|
||||||
i := 0
|
i := 0
|
||||||
for _, c := range s.connections {
|
// second call of Range.
|
||||||
conns[i] = c
|
s.connections.Range(func(k, v interface{}) bool {
|
||||||
|
conn, ok := v.(*connection)
|
||||||
|
if !ok {
|
||||||
|
// if for some reason (should never happen), the value is not stored as *connection
|
||||||
|
// then stop the iteration and don't continue insertion of the result connections
|
||||||
|
// in order to avoid any issues while end-dev will try to iterate a nil entry.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conns[i] = conn
|
||||||
i++
|
i++
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
s.mu.RUnlock()
|
|
||||||
return conns
|
return conns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,8 +332,10 @@ func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
|
||||||
if connIDs, found := s.rooms[roomName]; found {
|
if connIDs, found := s.rooms[roomName]; found {
|
||||||
for _, connID := range connIDs {
|
for _, connID := range connIDs {
|
||||||
// existence check is not necessary here.
|
// existence check is not necessary here.
|
||||||
if conn, ok := s.connections[connID]; ok {
|
if cValue, ok := s.connections.Load(connID); ok {
|
||||||
conns = append(conns, conn)
|
if conn, ok := cValue.(*connection); ok {
|
||||||
|
conns = append(conns, conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,20 +375,32 @@ func (s *Server) emitMessage(from, to string, data []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.mu.RLock()
|
|
||||||
// it suppose to send the message to all opened connections or to all except the sender.
|
// it suppose to send the message to all opened connections or to all except the sender.
|
||||||
for _, conn := range s.connections {
|
s.connections.Range(func(k, v interface{}) bool {
|
||||||
if to != All && to != conn.id { // if it's not suppose to send to all connections (including itself)
|
connID, ok := k.(string)
|
||||||
if to == Broadcast && from == conn.id { // if broadcast to other connections except this
|
if !ok {
|
||||||
// here we do the opossite of previous block,
|
// should never happen.
|
||||||
// just skip this connection when it's suppose to send the message to all connections except the sender.
|
return true
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.writeDefault(data)
|
if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
|
||||||
}
|
if to == Broadcast && from == connID { // if broadcast to other connections except this
|
||||||
s.mu.RUnlock()
|
// here we do the opossite of previous block,
|
||||||
|
// just skip this connection when it's suppose to send the message to all connections except the sender.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// not necessary cast.
|
||||||
|
conn, ok := v.(*connection)
|
||||||
|
if ok {
|
||||||
|
// send to the client(s) when the top validators passed
|
||||||
|
conn.writeDefault(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,9 +424,7 @@ func (s *Server) Disconnect(connID string) (err error) {
|
||||||
// fire the disconnect callbacks, if any.
|
// fire the disconnect callbacks, if any.
|
||||||
conn.fireDisconnect()
|
conn.fireDisconnect()
|
||||||
|
|
||||||
s.mu.Lock()
|
s.connections.Delete(connID)
|
||||||
delete(s.connections, conn.id)
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
err = conn.underline.Close()
|
err = conn.underline.Close()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user