Approach: batch many small messages into a vectorized write (writev/sendmsg) to reduce the number of syscalls and copies. For non-blocking sockets handle EAGAIN/EWOULDBLOCK and partial writes by advancing the iovec offsets and retrying (possibly using epoll/kqueue). Watch OS limits: max iov count and total bytes per send (e.g., UIO_MAXIOV).Pseudo-code (Go-like, using unix.Writev semantics):go
// Pseudo-code (Go)
import "golang.org/x/sys/unix"
// messages: [][]byte small messages to send
func sendMany(fd int, messages [][]byte) error {
// Build iovecs up to OS limit
maxIov := unix.UIO_MAXIOV // platform constant if available
iov := make([]unix.Iovec, 0, min(len(messages), maxIov))
for _, msg := range messages {
if len(iov) == maxIov {
// flush current iovecs
if err := writeVector(fd, iov); err != nil { return err }
iov = iov[:0]
}
iov = append(iov, unix.Iovec{Base: &msg[0], Len: uint64(len(msg))})
}
if len(iov) > 0 { return writeVector(fd, iov) }
return nil
}
func writeVector(fd int, iov []unix.Iovec) error {
// Loop until all bytes from iov are consumed
for len(iov) > 0 {
n, err := unix.Writev(fd, iov)
if err == unix.EINTR { continue }
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
// wait for writable (epoll/kqueue/select) then retry
waitWritable(fd)
continue
}
if err != nil { return err }
// advance iov by n bytes: skip fully-written iovecs, adjust first partial
bytes := n
newIov := make([]unix.Iovec, 0, len(iov))
for _, v := range iov {
if bytes >= int(v.Len) {
bytes -= int(v.Len)
continue
}
// partial: advance Base pointer by bytes, reduce Len
v.Base = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(v.Base)) + uintptr(bytes)))
v.Len -= uint64(bytes)
newIov = append(newIov, v)
break
}
// append remaining iovecs unchanged
// (copy remaining from original after consumed ones)
// set iov = newIov + rest
iov = append(newIov, remaining(iov, /*calculated*/ )...)
}
return nil
}
Key explanations:- Why vectorized writes: writev/sendmsg performs one syscall to transmit multiple buffers, reducing syscall overhead and data copies (kernel can gather from user buffers).- Partial writes: writev may write fewer bytes than total. You must advance iovec pointers by the number of bytes written and retry the remainder.- Non-blocking sockets: on EAGAIN/EWOULDBLOCK wait for socket writable via epoll/kqueue and resume; avoid busy-looping.- Limits & gotchas: OS UIO_MAXIOV limits count of iovecs; some kernels limit aggregate bytes per call; integer overflow when summing lengths; kernel may copy pointers so ensure buffers stay valid until syscall completes. Also consider TCP coalescing/Nagle and MTU-related fragmentation—vectorizing reduces syscalls but doesn't guarantee single packet.