Skip to content

Commit dc150c1

Browse files
authored
feat: support Conn.SendTo for UDP (#694)
Fixes #686
1 parent fe237e2 commit dc150c1

18 files changed

+407
-155
lines changed

.github/workflows/cross-compile-bsd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
strategy:
3232
fail-fast: false
3333
matrix:
34-
go: ['1.20', '1.23']
34+
go: ['1.20', '1.24']
3535
os:
3636
- ubuntu-latest
3737
name: Go ${{ matrix.go }} @ ${{ matrix.os }}

.github/workflows/test.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ jobs:
4747
cache: false
4848

4949
- name: Setup and run golangci-lint
50-
uses: golangci/golangci-lint-action@v6
50+
uses: golangci/golangci-lint-action@v7
5151
with:
52-
version: v1.64.8
53-
args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot --timeout 5m
52+
version: v2.1.5
53+
args: -v -E gocritic -E misspell -E revive -E godot --timeout 5m
5454
test:
5555
needs: lint
5656
strategy:
5757
fail-fast: false
5858
matrix:
59-
go: ['1.20', '1.23']
59+
go: ['1.20', '1.24']
6060
os:
6161
- ubuntu-latest
6262
- macos-latest

.github/workflows/test_gc_opt.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ jobs:
4747
cache: false
4848

4949
- name: Setup and run golangci-lint
50-
uses: golangci/golangci-lint-action@v6
50+
uses: golangci/golangci-lint-action@v7
5151
with:
52-
version: v1.64.8
53-
args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot --timeout 5m
52+
version: v2.1.5
53+
args: -v -E gocritic -E misspell -E revive -E godot --timeout 5m
5454
test:
5555
needs: lint
5656
strategy:
5757
fail-fast: false
5858
matrix:
59-
go: ['1.20', '1.23']
59+
go: ['1.20', '1.24']
6060
os:
6161
- ubuntu-latest
6262
- macos-latest

.github/workflows/test_poll_opt.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ jobs:
4646
cache: false
4747

4848
- name: Setup and run golangci-lint
49-
uses: golangci/golangci-lint-action@v6
49+
uses: golangci/golangci-lint-action@v7
5050
with:
51-
version: v1.64.8
52-
args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot
51+
version: v2.1.5
52+
args: -v -E gocritic -E misspell -E revive -E godot
5353
test:
5454
needs: lint
5555
strategy:
5656
fail-fast: false
5757
matrix:
58-
go: ['1.20', '1.23']
58+
go: ['1.20', '1.24']
5959
os: [ubuntu-latest, macos-latest]
6060
name: Go ${{ matrix.go }} @ ${{ matrix.os }}
6161
runs-on: ${{ matrix.os }}

.github/workflows/test_poll_opt_gc_opt.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ jobs:
4646
cache: false
4747

4848
- name: Setup and run golangci-lint
49-
uses: golangci/golangci-lint-action@v6
49+
uses: golangci/golangci-lint-action@v7
5050
with:
51-
version: v1.64.8
52-
args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot
51+
version: v2.1.5
52+
args: -v -E gocritic -E misspell -E revive -E godot
5353
test:
5454
needs: lint
5555
strategy:
5656
fail-fast: false
5757
matrix:
58-
go: ['1.20', '1.23']
58+
go: ['1.20', '1.24']
5959
os: [ubuntu-latest, macos-latest]
6060
name: Go ${{ matrix.go }} @ ${{ matrix.os }}
6161
runs-on: ${{ matrix.os }}

client_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ func startGnetClient(t *testing.T, cli *Client, network, addr string, multicore,
533533
c, err = cli.DialContext(network, addr, handler)
534534
}
535535
require.NoError(t, err)
536-
defer c.Close()
536+
defer c.Close() //nolint:errcheck
537537
err = c.Wake(nil)
538538
require.NoError(t, err)
539539
rspCh := handler.rspCh
@@ -688,7 +688,7 @@ func TestClientReadOnEOF(t *testing.T) {
688688

689689
ln, err := net.Listen("tcp", "127.0.0.1:9999")
690690
assert.NoError(t, err)
691-
defer ln.Close()
691+
defer ln.Close() //nolint:errcheck
692692

693693
go func() {
694694
for {
@@ -733,7 +733,7 @@ func TestClientReadOnEOF(t *testing.T) {
733733
}
734734

735735
func process(conn net.Conn) {
736-
defer conn.Close() //noliint:errcheck
736+
defer conn.Close() //nolint:errcheck
737737
buf := make([]byte, 8)
738738
n, err := conn.Read(buf)
739739
if err != nil {

client_unix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (cli *Client) Enroll(c net.Conn) (Conn, error) {
163163

164164
// EnrollContext is like Enroll but also accepts an empty interface ctx that can be obtained later via Conn.Context.
165165
func (cli *Client) EnrollContext(c net.Conn, ctx any) (Conn, error) {
166-
defer c.Close()
166+
defer c.Close() //nolint:errcheck
167167

168168
sc, ok := c.(syscall.Conn)
169169
if !ok {
@@ -242,7 +242,7 @@ func (cli *Client) EnrollContext(c net.Conn, ctx any) (Conn, error) {
242242
}}
243243
err = cli.el.poller.Trigger(queue.HighPriority, cli.el.register, ccb)
244244
if err != nil {
245-
gc.Close()
245+
_ = gc.Close()
246246
return nil, err
247247
}
248248

connection_unix.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"github.com/panjf2000/gnet/v2/pkg/buffer/elastic"
3030
errorx "github.com/panjf2000/gnet/v2/pkg/errors"
3131
gio "github.com/panjf2000/gnet/v2/pkg/io"
32-
"github.com/panjf2000/gnet/v2/pkg/logging"
3332
"github.com/panjf2000/gnet/v2/pkg/netpoll"
3433
bsPool "github.com/panjf2000/gnet/v2/pkg/pool/byteslice"
3534
"github.com/panjf2000/gnet/v2/pkg/queue"
@@ -158,11 +157,7 @@ loop:
158157
}
159158
return
160159
}
161-
if err := c.loop.close(c, os.NewSyscallError("write", err)); err != nil {
162-
logging.Errorf("failed to close connection(fd=%d,remote=%+v) on conn.write: %v",
163-
c.fd, c.remoteAddr, err)
164-
}
165-
return 0, os.NewSyscallError("write", err)
160+
return 0, c.loop.close(c, os.NewSyscallError("write", err))
166161
}
167162
data = data[sent:]
168163
if isET && len(data) > 0 {
@@ -206,11 +201,7 @@ loop:
206201
}
207202
return
208203
}
209-
if err := c.loop.close(c, os.NewSyscallError("writev", err)); err != nil {
210-
logging.Errorf("failed to close connection(fd=%d,remote=%+v) on conn.writev: %v",
211-
c.fd, c.remoteAddr, err)
212-
}
213-
return 0, os.NewSyscallError("writev", err)
204+
return 0, c.loop.close(c, os.NewSyscallError("writev", err))
214205
}
215206
pos := len(bs)
216207
if remaining -= sent; remaining > 0 {
@@ -280,11 +271,20 @@ func (c *conn) asyncWritev(a any) (err error) {
280271
return
281272
}
282273

283-
func (c *conn) sendTo(buf []byte) error {
284-
if c.remote == nil {
285-
return unix.Send(c.fd, buf, 0)
274+
func (c *conn) sendTo(buf []byte, addr unix.Sockaddr) (n int, err error) {
275+
defer func() {
276+
if err != nil {
277+
n = 0
278+
}
279+
}()
280+
281+
if addr != nil {
282+
return len(buf), unix.Sendto(c.fd, buf, 0, addr)
283+
}
284+
if c.remote == nil { // connected UDP socket of client
285+
return len(buf), unix.Send(c.fd, buf, 0)
286286
}
287-
return unix.Sendto(c.fd, buf, 0, c.remote)
287+
return len(buf), unix.Sendto(c.fd, buf, 0, c.remote) // unconnected UDP socket of server
288288
}
289289

290290
func (c *conn) resetBuffer() {
@@ -389,14 +389,24 @@ func (c *conn) Discard(n int) (int, error) {
389389

390390
func (c *conn) Write(p []byte) (int, error) {
391391
if c.isDatagram {
392-
if err := c.sendTo(p); err != nil {
393-
return 0, err
394-
}
395-
return len(p), nil
392+
return c.sendTo(p, nil)
396393
}
397394
return c.write(p)
398395
}
399396

397+
func (c *conn) SendTo(p []byte, addr net.Addr) (int, error) {
398+
if !c.isDatagram {
399+
return 0, errorx.ErrUnsupportedOp
400+
}
401+
402+
sa := socket.NetAddrToSockaddr(addr)
403+
if sa == nil {
404+
return 0, errorx.ErrInvalidNetworkAddress
405+
}
406+
407+
return c.sendTo(p, sa)
408+
}
409+
400410
func (c *conn) Writev(bs [][]byte) (int, error) {
401411
if c.isDatagram {
402412
return 0, errorx.ErrUnsupportedOp
@@ -462,7 +472,7 @@ func (c *conn) SetKeepAlivePeriod(d time.Duration) error {
462472

463473
func (c *conn) AsyncWrite(buf []byte, callback AsyncCallback) error {
464474
if c.isDatagram {
465-
err := c.sendTo(buf)
475+
_, err := c.sendTo(buf, nil)
466476
// TODO: it will not go asynchronously with UDP, so calling a callback is needless,
467477
// we may remove this branch in the future, please don't rely on the callback
468478
// to do something important under UDP, if you're working with UDP, just call Conn.Write

connection_windows.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,23 @@ func (c *conn) Write(p []byte) (int, error) {
222222
return c.pc.WriteTo(p, c.remoteAddr)
223223
}
224224

225+
func (c *conn) SendTo(p []byte, addr net.Addr) (int, error) {
226+
if c.pc == nil {
227+
return 0, errorx.ErrUnsupportedOp
228+
}
229+
230+
if addr == nil {
231+
return 0, errorx.ErrInvalidNetworkAddress
232+
}
233+
234+
return c.pc.WriteTo(p, addr)
235+
}
236+
225237
func (c *conn) Writev(bs [][]byte) (int, error) {
238+
if c.pc != nil { // not available for UDP
239+
return 0, errorx.ErrUnsupportedOp
240+
}
241+
226242
if c.rawConn != nil {
227243
bb := bbPool.Get()
228244
defer bbPool.Put(bb)
@@ -443,6 +459,10 @@ func (c *conn) AsyncWrite(buf []byte, cb AsyncCallback) error {
443459
}
444460

445461
func (c *conn) AsyncWritev(bs [][]byte, cb AsyncCallback) error {
462+
if c.pc != nil {
463+
return errorx.ErrUnsupportedOp
464+
}
465+
446466
buf := bbPool.Get()
447467
for _, b := range bs {
448468
_, _ = buf.Write(b)

eventloop_unix_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,7 @@ type benchmarkServerGC struct {
127127
func (s *benchmarkServerGC) OnBoot(eng Engine) (action Action) {
128128
s.eng = eng
129129
go func() {
130-
for {
131-
if s.eng.eng.eventLoops.len() == s.elNum && s.eng.CountConnections() == s.elNum*int(s.initConnCount) {
132-
break
133-
}
130+
for s.eng.eng.eventLoops.len() != s.elNum || s.eng.CountConnections() != s.elNum*int(s.initConnCount) {
134131
time.Sleep(time.Millisecond)
135132
}
136133
close(s.initOk)

gnet.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,13 @@ type Writer interface {
212212
io.Writer // not concurrency-safe
213213
io.ReaderFrom // not concurrency-safe
214214

215+
// SendTo transmits a message to the given address, it's not concurrency-safe.
216+
// It is available only for UDP sockets, an ErrUnsupportedOp will be returned
217+
// when it is called on a non-UDP socket.
218+
// This method should be used only when you need to send a message to a specific
219+
// address over the UDP socket, otherwise you should use Conn.Write() instead.
220+
SendTo(buf []byte, addr net.Addr) (n int, err error)
221+
215222
// Writev writes multiple byte slices to remote synchronously, it's not concurrency-safe,
216223
// you must invoke it within any method in EventHandler.
217224
Writev(bs [][]byte) (n int, err error)

0 commit comments

Comments
 (0)