├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples └── pingpong │ └── pingpong.go ├── netchan.go ├── netchan_test.go ├── select.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - tip 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Ahmed W. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netchan [![GoDoc](http://godoc.org/github.com/OneOfOne/netchan?status.svg)](http://godoc.org/github.com/OneOfOne/netchan) [![Build Status](https://travis-ci.org/OneOfOne/netchan.svg?branch=master)](https://travis-ci.org/OneOfOne/netchan) 2 | -- 3 | 4 | netchan is a proof-of-concept channels over network, works exactly like you would expect. 5 | 6 | Create a new channel, connect to remote hosts and/or listen on one or more ports and use it like you would use a normal channel. 7 | 8 | ## Install 9 | 10 | go get github.com/OneOfOne/netchan 11 | 12 | ## Usage 13 | 14 | check `examples/*` 15 | 16 | ## Project Status 17 | This is alpha software, ymmv. 18 | 19 | All contributions and pull requests are also very welcome. 20 | 21 | ## License 22 | 23 | [Apache v2.0](http://opensource.org/licenses/Apache-2.0) 24 | 25 | Copyright 2014 [OneOfOne](https://github.com/OneOfOne/) 26 | 27 | Licensed under the Apache License, Version 2.0 (the "License"); 28 | you may not use this file except in compliance with the License. 29 | You may obtain a copy of the License at 30 | 31 | http://www.apache.org/licenses/LICENSE-2.0 32 | 33 | Unless required by applicable law or agreed to in writing, software 34 | distributed under the License is distributed on an "AS IS" BASIS, 35 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36 | See the License for the specific language governing permissions and 37 | limitations under the License. 38 | -------------------------------------------------------------------------------- /examples/pingpong/pingpong.go: -------------------------------------------------------------------------------- 1 | // this is a simple ping/pong example 2 | // run one instance as a server with: go run pingpong.go -s [-addr 10101] 3 | // then run one (or more) clients with: go run pingpong [-addr 10101] 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "log" 10 | "math/rand" 11 | "os" 12 | "runtime" 13 | "time" 14 | 15 | "github.com/OneOfOne/netchan" 16 | ) 17 | 18 | var ( 19 | servPort = flag.String("addr", "10101", "server port") 20 | serv = flag.Bool("s", false, "if true it will act as a server") 21 | ch = netchan.New(1) 22 | N = runtime.NumCPU() 23 | ) 24 | 25 | func dieIf(err error) { 26 | if err != nil { 27 | fmt.Fprintln(os.Stderr, "error:", err) 28 | os.Exit(1) 29 | } 30 | } 31 | func servLoop(i int) { 32 | for { 33 | id, msg, st := ch.Recv(true) 34 | if st != netchan.StatusOK { 35 | dieIf(fmt.Errorf("invalid recv, dying (%s): [id=%x] %q %s", st, id, msg)) 36 | } 37 | log.Printf("[i=%d] received %q from %x\n", i, msg, id) 38 | ch.Send(true, fmt.Sprintf("pong.%d", i)) 39 | } 40 | } 41 | 42 | func clientLoop(i int) { 43 | for { 44 | time.Sleep(time.Duration(rand.Intn(1000)+1000) * time.Millisecond) 45 | ch.Send(true, fmt.Sprintf("ping.%d", i)) 46 | _, msg, st := ch.Recv(true) 47 | log.Printf("[i=%d] received %q from remote\n", i, msg) 48 | if st != netchan.StatusOK { 49 | os.Exit(0) 50 | } 51 | } 52 | } 53 | 54 | func main() { 55 | runtime.GOMAXPROCS(N) 56 | flag.Parse() 57 | addr := "127.0.0.1:" + *servPort 58 | log.SetFlags(0) 59 | if *serv { 60 | dieIf(ch.ListenAndServe("tcp4", addr)) 61 | for i := 0; i < N; i++ { 62 | go servLoop(i) 63 | } 64 | } else { 65 | dieIf(ch.Connect("tcp4", addr)) 66 | for i := 0; i < N; i++ { 67 | go clientLoop(i) 68 | } 69 | } 70 | select {} //block forever 71 | } 72 | -------------------------------------------------------------------------------- /netchan.go: -------------------------------------------------------------------------------- 1 | package netchan 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/rand" 7 | "encoding/gob" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "log" 12 | "net" 13 | 14 | oerr "github.com/OneOfOne/go-utils/errors" 15 | osync "github.com/OneOfOne/go-utils/sync" 16 | ) 17 | 18 | var ( 19 | // ErrInvalidResponse is returned when the client or server sent something 20 | // unexpected during the handshake. 21 | ErrInvalidResponse = errors.New("invalid response") 22 | ) 23 | 24 | type pktType byte 25 | 26 | const ( 27 | pktInvalid pktType = iota 28 | pktHandshake 29 | pktHandshakeConfirm 30 | pktData 31 | pktStatus 32 | ) 33 | 34 | // ChannelStatus defines the return values from Send/Recv 35 | type ChannelStatus byte 36 | 37 | const ( 38 | // StatusNotFound returned when a valid receiver (or sender) coudln't be found 39 | StatusNotFound ChannelStatus = iota 40 | 41 | // StatusNotReady means all channel buffers were full, try again later 42 | StatusNotReady 43 | 44 | // StatusClosed means the channel is closed and should no longer be used 45 | StatusClosed 46 | 47 | // StatusOK means the message was received (or sent) successfully 48 | StatusOK 49 | ) 50 | 51 | func (i ChannelStatus) String() string { 52 | switch i { 53 | case StatusNotFound: 54 | return "NotFound" 55 | case StatusNotReady: 56 | return "NotReady" 57 | case StatusOK: 58 | return "OK" 59 | case StatusClosed: 60 | return "Closed" 61 | } 62 | return "Invalid" 63 | } 64 | 65 | type pkt struct { 66 | Type pktType 67 | Value interface{} 68 | srcID []byte 69 | } 70 | 71 | func init() { 72 | gob.Register(pkt{}) 73 | } 74 | 75 | func genID() (id []byte) { 76 | id = make([]byte, 16) 77 | rand.Read(id) 78 | return 79 | } 80 | 81 | type connChan struct { 82 | id []byte 83 | net.Conn 84 | r, s chan *pkt 85 | 86 | *gob.Encoder 87 | *gob.Decoder 88 | 89 | ctrl chan *connChan 90 | } 91 | 92 | func newConnChan(conn net.Conn, r, s chan *pkt, ctrl chan *connChan) (ch *connChan) { 93 | ch = &connChan{ 94 | Conn: conn, 95 | 96 | r: r, 97 | s: s, 98 | 99 | Encoder: gob.NewEncoder(conn), 100 | Decoder: gob.NewDecoder(bufio.NewReaderSize(conn, 16)), //using larger buffers make the connection block sometimes. 101 | 102 | ctrl: ctrl, 103 | } 104 | return 105 | } 106 | 107 | func (c *connChan) clientHandshake(id []byte) (err error) { 108 | c.id = id 109 | if err = c.Encode(&pkt{Type: pktHandshake, Value: id}); err != nil { 110 | return 111 | } 112 | p := &pkt{} 113 | if err = c.Decode(p); err != nil { 114 | return 115 | } 116 | if p.Type != pktHandshakeConfirm { 117 | return ErrInvalidResponse 118 | } 119 | go c.receiver() 120 | go c.sender() 121 | return 122 | } 123 | 124 | func (c *connChan) serverHandshake() (err error) { 125 | p := &pkt{} 126 | if err = c.Decode(p); err != nil { 127 | return 128 | } 129 | if p.Type != pktHandshake { 130 | return ErrInvalidResponse 131 | } 132 | c.id = p.Value.([]byte) 133 | p.Type = pktHandshakeConfirm 134 | if err = c.Encode(p); err != nil { 135 | return 136 | } 137 | go c.receiver() 138 | go c.sender() 139 | return 140 | } 141 | 142 | func (c *connChan) receiver() { 143 | L: 144 | for { 145 | p := &pkt{srcID: c.id} 146 | if err := c.Decode(&p); err != nil { 147 | break 148 | } 149 | 150 | switch p.Type { 151 | case pktData: 152 | c.r <- p 153 | case pktStatus: 154 | if st, ok := p.Value.(ChannelStatus); ok && st == StatusClosed { 155 | break L 156 | } 157 | default: //might remove this 158 | panic(fmt.Sprintf("%v", p)) 159 | } 160 | } 161 | c.ctrl <- c 162 | } 163 | 164 | func (c *connChan) sender() { 165 | for { 166 | select { 167 | case p := <-c.s: 168 | if p == nil || p.Type == pktInvalid { // screw it all 169 | break 170 | } 171 | if err := c.Encode(p); err != nil { 172 | c.s <- p 173 | break 174 | } 175 | } 176 | } 177 | } 178 | 179 | func (c *connChan) Send(val interface{}) (st ChannelStatus) { 180 | p := &pkt{Type: pktData, Value: val} 181 | return c.send(p) 182 | } 183 | 184 | func (c *connChan) send(p *pkt) (st ChannelStatus) { 185 | st = StatusOK 186 | if err := c.Encode(p); err != nil { 187 | st = StatusClosed 188 | } 189 | return 190 | } 191 | 192 | func (c *connChan) Close() error { 193 | c.send(&pkt{Type: pktStatus, Value: StatusClosed}) 194 | return c.Conn.Close() 195 | } 196 | 197 | type Channel struct { 198 | id []byte 199 | lk osync.SpinLock 200 | 201 | listeners []net.Listener 202 | 203 | // using a map as a slice because we're that cool, 204 | // also halfway guarenteed random selection 205 | nchans map[*connChan]struct{} 206 | 207 | r, s chan *pkt 208 | 209 | ctrl chan *connChan 210 | log *log.Logger 211 | } 212 | 213 | func (ct *Channel) addChan(ch *connChan) { 214 | ct.lk.Lock() 215 | ct.nchans[ch] = struct{}{} 216 | ct.lk.Unlock() 217 | } 218 | 219 | func (ct *Channel) sucideLine() { 220 | for ch := range ct.ctrl { 221 | ct.printf("channel [id=%x] disconnected from %v", ch.id, ch.RemoteAddr()) 222 | ct.lk.Lock() 223 | delete(ct.nchans, ch) 224 | ct.lk.Unlock() 225 | if ct.IsClosed() { 226 | break 227 | } 228 | } 229 | close(ct.ctrl) 230 | } 231 | 232 | func (ct *Channel) printf(format string, val ...interface{}) { 233 | ct.lk.Lock() 234 | if ct.log != nil { 235 | ct.log.Printf(format, val...) 236 | } 237 | ct.lk.Unlock() 238 | } 239 | 240 | // IsClosed returns if the channel is closed or not 241 | func (ct *Channel) IsClosed() (st bool) { 242 | ct.lk.Lock() 243 | st = len(ct.nchans) == 0 && len(ct.listeners) == 0 244 | ct.lk.Unlock() 245 | return 246 | } 247 | 248 | // Recv returns who sent the message, the message and the status of the channel 249 | // optionally blocks until there's a message to receive. 250 | func (ct *Channel) Recv(block bool) (id []byte, val interface{}, st ChannelStatus) { 251 | if ct.IsClosed() { 252 | st = StatusClosed 253 | return 254 | } 255 | var ( 256 | p *pkt 257 | ok bool 258 | ) 259 | if block { 260 | p, ok = <-ct.r 261 | } else { 262 | select { 263 | case p, ok = <-ct.r: 264 | default: 265 | st = StatusNotReady 266 | } 267 | } 268 | if ok { 269 | st = StatusOK 270 | id = p.srcID 271 | val = p.Value 272 | } else if st == StatusNotFound { 273 | st = StatusClosed 274 | } 275 | return 276 | } 277 | 278 | // Send sends a message over the network, optionally blocks until it finds a receiver. 279 | func (ct *Channel) Send(block bool, val interface{}) (st ChannelStatus) { 280 | if ct.IsClosed() { 281 | return StatusClosed 282 | } 283 | 284 | p := &pkt{Type: pktData, Value: val} 285 | if block { 286 | select { 287 | case ct.s <- p: 288 | st = StatusOK 289 | } 290 | } else { 291 | select { 292 | case ct.s <- p: 293 | st = StatusOK 294 | default: 295 | st = StatusNotReady 296 | } 297 | 298 | } 299 | return 300 | } 301 | 302 | // SendTo like send but tries to send to a specific client, 303 | // returns StatusNotFound if the client doesn't exist anymore. 304 | // note that SendTo always blocks until the message is sent or it errors out 305 | func (ct *Channel) SendTo(id []byte, val interface{}) (st ChannelStatus) { 306 | if ct.IsClosed() { 307 | return StatusClosed 308 | } 309 | st = StatusNotFound 310 | var nch *connChan 311 | 312 | ct.lk.Lock() 313 | for ch := range ct.nchans { 314 | if bytes.Equal(ch.id, id) { 315 | nch = ch 316 | } 317 | } 318 | 319 | ct.lk.Unlock() 320 | if nch != nil { 321 | st = nch.Send(val) 322 | } 323 | return 324 | } 325 | 326 | func (ct *Channel) SendAll(block bool, val interface{}) (st []ChannelStatus) { 327 | ct.lk.Lock() 328 | chs := make([]*connChan, len(ct.nchans)) 329 | st = make([]ChannelStatus, len(chs)) 330 | i := 0 331 | for ch := range ct.nchans { 332 | chs[i] = ch 333 | } 334 | ct.lk.Unlock() 335 | for i, ch := range chs { 336 | st[i] = ch.Send(val) 337 | } 338 | return 339 | } 340 | 341 | // Connect connects to a remote channel 342 | // it can be called multiple times with different addresses 343 | // (or really the same one if you're that kind of a person) 344 | func (ct *Channel) Connect(network, addr string) error { 345 | conn, err := net.Dial(network, addr) 346 | if err != nil { 347 | return err 348 | } 349 | return ct.ConnectTo(conn) 350 | } 351 | 352 | // ConnectTo conencts to a specific net.Conn 353 | func (ct *Channel) ConnectTo(conn net.Conn) error { 354 | ch := newConnChan(conn, ct.r, ct.s, ct.ctrl) 355 | if err := ch.clientHandshake(ct.id); err != nil { 356 | ct.printf("error during handshake to %s: %v", conn.RemoteAddr(), err) 357 | return err 358 | } 359 | ct.addChan(ch) 360 | 361 | ct.printf("channel [id=%x] connected to %s", ch.id, conn.RemoteAddr()) 362 | return nil 363 | } 364 | 365 | // ListenAndServe listens on the specific network and address waiting for remote channels 366 | // it can be called multiple times with different addresses 367 | func (ct *Channel) ListenAndServe(network, addr string) error { 368 | l, err := net.Listen(network, addr) 369 | if err != nil { 370 | return err 371 | } 372 | go ct.Serve(l) 373 | return nil 374 | } 375 | 376 | // Serve well, it serves a listener, ok golint? 377 | func (ct *Channel) Serve(l net.Listener) error { 378 | ct.lk.Lock() 379 | ct.listeners = append(ct.listeners, l) 380 | ct.lk.Unlock() 381 | ct.printf("serving on %s", l.Addr()) 382 | for { 383 | conn, err := l.Accept() 384 | if err != nil { 385 | return err 386 | } 387 | ch := newConnChan(conn, ct.r, ct.s, ct.ctrl) 388 | if err := ch.serverHandshake(); err != nil { 389 | ct.printf("error during handshake from %s: %v", conn.RemoteAddr(), err) 390 | continue 391 | } 392 | ct.addChan(ch) 393 | ct.printf("channel [id=%x] connected from %s", ch.id, conn.RemoteAddr()) 394 | } 395 | } 396 | 397 | // Close closes all the listeners and connected channels 398 | func (ct *Channel) Close() error { 399 | var errs oerr.MultiError 400 | 401 | // kill listeners first if any 402 | ct.lk.Lock() 403 | for _, l := range ct.listeners { 404 | errs.Append(l.Close()) 405 | } 406 | ct.listeners = nil 407 | nchans := make([]*connChan, 0, len(ct.nchans)) 408 | 409 | die := &pkt{} 410 | for ch := range ct.nchans { 411 | ct.s <- die 412 | nchans = append(nchans, ch) 413 | } 414 | ct.lk.Unlock() 415 | for _, ch := range nchans { 416 | if err := ch.Close(); err != nil { 417 | errs.Append(Error{Id: ch.id, Addr: ch.RemoteAddr(), Err: err}) 418 | } 419 | } 420 | select { //because why the hell not 421 | case ct.s <- die: 422 | default: 423 | } 424 | 425 | close(ct.r) 426 | close(ct.s) 427 | //close(ct.ctrl) 428 | return &errs 429 | } 430 | 431 | // Stats returns information about the current connections 432 | func (ct *Channel) Stats() (activeConnections, activeListeners int) { 433 | ct.lk.Lock() 434 | activeConnections, activeListeners = len(ct.nchans), len(ct.listeners) 435 | ct.lk.Unlock() 436 | return 437 | } 438 | 439 | // Logger returns the *log.Logger instance the channel is using. 440 | func (ct *Channel) SetLogger(l *log.Logger) { 441 | ct.lk.Lock() 442 | ct.log = l 443 | ct.lk.Unlock() 444 | } 445 | 446 | // Sender defines a write-only channel 447 | type Sender interface { 448 | // Send sends a message over the network, optionally blocks until it finds a receiver. 449 | Send(block bool, val interface{}) (st ChannelStatus) 450 | 451 | // SendTo like send but tries to send to a specific client, 452 | // returns StatusNotFound if the client doesn't exist anymore. 453 | // note that SendTo always blocks until the message is sent or it errors out 454 | SendTo(id []byte, val interface{}) (st ChannelStatus) 455 | } 456 | 457 | // Receiver defines a read-only channel 458 | type Receiver interface { 459 | // Recv returns who sent the message, the message and the status of the channel 460 | // optionally blocks until there's a message to receive. 461 | Recv(block bool) (id []byte, val interface{}, st ChannelStatus) 462 | } 463 | 464 | // Interface is the common interface for all network channels 465 | type Interface interface { 466 | Sender 467 | Receiver 468 | io.Closer 469 | 470 | // ConnectTo conencts to a remote channel using the specific net.Conn 471 | ConnectTo(conn net.Conn) error 472 | 473 | // Serve well, serves a listener. 474 | // it blocks until the server is terminated. 475 | Serve(l net.Listener) error 476 | 477 | // IsClosed returns true if the channel is not usable anymore 478 | IsClosed() bool 479 | } 480 | 481 | func (ct *Channel) String() string { 482 | ac, al := ct.Stats() 483 | return fmt.Sprintf("Channel [id=%x, active=%d, listeners=%d]", ct.id, ac, al) 484 | } 485 | 486 | // ListenAndServe is an alias for New(initialCap).ListenAndServe(network, addr) 487 | func ListenAndServe(initialCap int, network, addr string) (ct *Channel, err error) { 488 | ct = New(initialCap) 489 | return ct, ct.ListenAndServe(network, addr) 490 | } 491 | 492 | // Connect is an alias for New(1).Connect(network, addr) 493 | func Connect(network, addr string) (ct *Channel, err error) { 494 | ct = New(1) 495 | return ct, ct.Connect(network, addr) 496 | } 497 | 498 | // New returns a new Channel with the initial cap size for the receiver and connection pool 499 | func New(initialCap int) *Channel { 500 | ct := &Channel{ 501 | id: genID(), 502 | 503 | nchans: make(map[*connChan]struct{}, initialCap), 504 | 505 | r: make(chan *pkt, initialCap), 506 | s: make(chan *pkt, initialCap), 507 | 508 | ctrl: make(chan *connChan, 0), 509 | 510 | //log: log.New(os.Stdout, "netchan: ", log.LstdFlags), 511 | } 512 | go ct.sucideLine() 513 | return ct 514 | } 515 | 516 | // don't silently break the api 517 | var _ Interface = (*Channel)(nil) 518 | -------------------------------------------------------------------------------- /netchan_test.go: -------------------------------------------------------------------------------- 1 | package netchan 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "runtime" 8 | "strings" 9 | "sync" 10 | "testing" 11 | ) 12 | 13 | var ( 14 | listenPort = flag.String("addr", "0", "listen port for tests") 15 | sch = New(1) 16 | wg sync.WaitGroup 17 | ) 18 | 19 | func init() { 20 | runtime.GOMAXPROCS(runtime.NumCPU()) 21 | } 22 | 23 | // this is super ugly, t.Parallel didn't work for some reason 24 | func TestServerClient(t *testing.T) { 25 | l, err := net.Listen("tcp", "127.0.0.1:"+*listenPort) 26 | if err != nil { 27 | t.Error(err) 28 | t.FailNow() 29 | } 30 | if *listenPort == "0" { 31 | *listenPort = strings.Split(l.Addr().String(), ":")[1] 32 | } 33 | t.Log("listening on", l.Addr()) 34 | go sch.Serve(l) 35 | wg.Add(20) 36 | go testServerChannel(t) 37 | go testClientChannel(t) 38 | wg.Wait() 39 | sch.Close() 40 | } 41 | func testServerChannel(t *testing.T) { 42 | for i := 0; i < 10; i++ { 43 | go func(i int) { 44 | id, msg, st := sch.Recv(true) 45 | if st != StatusOK { 46 | t.Fatalf("unexpected status : %x %q %s", id, msg, st) 47 | } 48 | t.Logf("sch recieved %q from %x", msg, id) 49 | sch.Send(true, fmt.Sprintf("sch:%d", i)) 50 | wg.Done() 51 | }(i) 52 | } 53 | //wg.Wait() 54 | } 55 | 56 | func testClientChannel(t *testing.T) { 57 | ch, err := Connect("tcp4", "127.0.0.1:"+*listenPort) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | for i := 0; i < 10; i++ { 62 | go func(i int) { 63 | ch.Send(true, fmt.Sprintf("cch:%d", i)) 64 | id, msg, st := ch.Recv(true) 65 | if st != StatusOK { 66 | t.Fatalf("unexpected status : %x %q %s", id, msg, st) 67 | } 68 | t.Logf("cch recieved %q", msg) 69 | wg.Done() 70 | }(i) 71 | } 72 | //wg.Wait() 73 | } 74 | 75 | func TestSelectRecv(t *testing.T) { 76 | nch, lch := New(1), make(chan interface{}, 0) 77 | go func() { 78 | lch <- 10 79 | }() 80 | if _, val := SelectRecv([]Receiver{nch, LocalReceiver(lch)}); val != 10 { 81 | t.Fatalf("expected %v, received %v", 10, val) 82 | } 83 | } 84 | 85 | func TestSelectSend(t *testing.T) { 86 | nch, lch := New(1), make(chan interface{}, 0) 87 | go func() { 88 | if SelectSend([]Sender{nch, LocalSender(lch)}, 10) == nil { 89 | t.Fatal("Select returned nil") 90 | } 91 | 92 | }() 93 | val := <-lch 94 | if val != 10 { 95 | t.Fatalf("expected %v, received %v", 10, val) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /select.go: -------------------------------------------------------------------------------- 1 | package netchan 2 | 3 | import "reflect" 4 | 5 | // SelectRecv loops on multiple receivers until it gets a value, 6 | // it blocks until a value is returned or all channels are closed. 7 | // this function doesn't panic. 8 | func SelectRecv(recvs []Receiver) (r Receiver, val interface{}) { 9 | defer func() { 10 | if recover() != nil { 11 | r = nil 12 | val = nil 13 | } 14 | }() 15 | cases := make([]reflect.SelectCase, len(recvs)) 16 | for i, rch := range recvs { 17 | cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv} 18 | switch rch := rch.(type) { 19 | case *Channel: 20 | cases[i].Chan = reflect.ValueOf(rch.r) 21 | case LocalReceiver: 22 | cases[i].Chan = reflect.ValueOf((<-chan interface{})(rch)) 23 | default: 24 | panic("SelectRecv only supports Channel and/or LocalReceiver") 25 | } 26 | } 27 | i, v, ok := reflect.Select(cases) 28 | if ok { 29 | r = recvs[i] 30 | val = v.Interface() 31 | if val, ok := val.(*pkt); ok { 32 | return r, val.Value 33 | } 34 | return 35 | } 36 | return 37 | } 38 | 39 | // SelectSend loops on multiple senders and returns the first channel that 40 | // was able to send. 41 | // it returns the target that sent the value or returns nil if all channels were closed 42 | // this function doesn't panic. 43 | func SelectSend(targets []Sender, val interface{}) (sch Sender) { 44 | defer func() { 45 | if recover() != nil { 46 | sch = nil 47 | } 48 | }() 49 | cases := make([]reflect.SelectCase, len(targets)) 50 | p := reflect.ValueOf(&pkt{Type: pktData, Value: val}) 51 | for i, sch := range targets { 52 | cases[i] = reflect.SelectCase{Dir: reflect.SelectSend} 53 | switch sch := sch.(type) { 54 | case *Channel: 55 | cases[i].Chan = reflect.ValueOf(sch.s) 56 | cases[i].Send = p 57 | case LocalSender: 58 | cases[i].Chan = reflect.ValueOf((chan<- interface{})(sch)) 59 | cases[i].Send = reflect.ValueOf(val) 60 | default: 61 | panic("SelectSend only supports Channel and/or LocalSender") 62 | } 63 | } 64 | i, _, _ := reflect.Select(cases) 65 | return targets[i] 66 | } 67 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package netchan 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | type Error struct { 9 | Id []byte 10 | Addr net.Addr 11 | Err error 12 | } 13 | 14 | func (err Error) Error() string { 15 | return fmt.Sprintln("Channel [id=%x, remote=%s] error = %v", err.Id, err.Addr, err.Err) 16 | } 17 | 18 | // LocalSender wraps a local channel as a Sender interface, 19 | // keep in mind that it will use defer to prevent panics. 20 | // SendTo will always return StatusNotFound 21 | type LocalSender chan<- interface{} 22 | 23 | func (ls LocalSender) Send(block bool, val interface{}) (st ChannelStatus) { 24 | defer func() { 25 | if recover() != nil { 26 | st = StatusClosed 27 | } 28 | }() 29 | if block { 30 | ls <- val 31 | return StatusOK 32 | } 33 | select { 34 | case ls <- val: 35 | return StatusOK 36 | default: 37 | return StatusNotReady 38 | } 39 | } 40 | 41 | func (ls LocalSender) SendTo(id []byte, val interface{}) (st ChannelStatus) { 42 | return StatusNotFound 43 | } 44 | 45 | // LocalReceiver wraps a local channel as a receiver interface, 46 | // the id will always be nil 47 | type LocalReceiver <-chan interface{} 48 | 49 | func (lr LocalReceiver) Recv(block bool) (id []byte, val interface{}, st ChannelStatus) { 50 | defer func() { 51 | if recover() != nil { 52 | st = StatusClosed 53 | } 54 | }() 55 | if block { 56 | val = <-lr 57 | st = StatusOK 58 | return 59 | } 60 | select { 61 | case val = <-lr: 62 | st = StatusOK 63 | default: 64 | st = StatusNotReady 65 | } 66 | return 67 | } 68 | --------------------------------------------------------------------------------