Cause 1: Calling send() or recv() After Socket Closure
The most common reason you'll hit WSAEINVAL is trying to send or receive data on a socket that's already been closed — either by the remote side or by another part of your code. What's actually happening here is the socket's state has changed to SS_CLOSED internally, but your program doesn't check before calling send(), recv(), or WSASend().
I've seen this a lot with poorly handled connection drops. Say you're writing a chat client and the server goes offline. The recv() returns an error, you close the socket, but another thread still tries to send a queued message on that same descriptor. Boom: WSAEINVAL.
The Fix: Check Socket State Before Sending
Before every send() or recv() call, verify the socket descriptor is valid. The dead-simplest way is to wrap your socket operations in a state-checking function. Here's a pattern that works:
// C/C++ example
bool IsSocketValid(SOCKET s) {
if (s == INVALID_SOCKET) return false;
// Optional: check with select() for readability
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(s, &readSet);
struct timeval tv = {0, 0};
return select(0, &readSet, NULL, NULL, &tv) != SOCKET_ERROR;
}
// Then before sending:
if (IsSocketValid(sock)) {
int result = send(sock, buf, len, 0);
if (result == SOCKET_ERROR) {
int err = WSAGetLastError();
// handle error
}
} else {
// don't send, requeue or drop
}
The reason select() works here: it checks whether the socket is still readable/writable without blocking. If the socket is closed, select() returns SOCKET_ERROR with a different error code (usually WSAENOTSOCK or WSAEINVAL itself). But it catches the dead socket early.
If you're using a higher-level library like Python's socket module, the same principle applies — catch OSError with error code 10022 (the Win32 version of WSAEINVAL) and don't assume the socket is still alive:
import socket
import errno
try:
sock.send(data)
except OSError as e:
if e.errno == errno.EINVAL or e.winerror == 10022:
# socket is dead, recreate or close
sock.close()
sock = None
Cause 2: Using Invalid Flags in WSASend or sendto
WSAEINVAL also shows up when you pass MSG_OOB (out-of-band data) flag on a socket that doesn't support it — like a TCP socket that hasn't been set up for OOB. Or using MSG_PEEK on a datagram socket that's already been shut down. The Winsock documentation says these flags are valid, but in practice they're picky about socket states.
I ran into this years ago with a custom protocol that tried to send urgent data on a plain TCP connection. The send() call had the MSG_OOB flag set, but the server hadn't called setsockopt() with SO_OOBINLINE. The result: WSAEINVAL every time.
The Fix: Strip Unsupported Flags
If you don't explicitly need OOB data, just don't pass any flags. A zero flag is the safest bet. If you do need OOB, you must enable it on both sides:
// Enable OOB inline on the socket
int optval = 1;
setsockopt(sock, SOL_SOCKET, SO_OOBINLINE, (char*)&optval, sizeof(optval));
For datagram sockets, avoid MSG_PEEK entirely if the socket might be shut down. The real fix is to check the socket state with getsockopt() and SO_ERROR before calling recvfrom() with special flags.
Cause 3: Invalid Buffer or Length Parameters
Less common, but real: passing a NULL buffer pointer or a length that exceeds the system's maximum message size. For UDP sockets, if you try to send a datagram larger than the path MTU (typically 1500 bytes on Ethernet), the socket may reject it with WSAEINVAL instead of the more general WSAEMSGSIZE. This is a quirk of certain Winsock providers — I've seen it on the Microsoft loopback adapter.
The Fix: Validate Buffer and Size
Check that your buffer pointer isn't null and that the length is within reasonable bounds:
// For TCP, cap at 64KB per send to avoid issues
const int MAX_TCP_SEND = 65536;
if (buf == NULL) {
// handle null buffer
return;
}
if (len > MAX_TCP_SEND) {
// either split into chunks or clamp
len = MAX_TCP_SEND;
}
int result = send(sock, buf, len, 0);
For UDP, never send more than 1472 bytes to stay under standard Ethernet MTU (1500 minus 20 IP header minus 8 UDP header). If you need larger datagrams, set IP_MTU_DISCOVER with IP_PMTUDISC_DO via setsockopt() and handle fragmentation yourself.
Quick-Reference Summary
| Cause | Symptom | Fix |
|---|---|---|
| Socket already closed | send/recv on dead socket | Check with select() or a state flag before I/O |
| Invalid flags | MSG_OOB without setup | Use zero flags or enable SO_OOBINLINE |
| Bad buffer/len | Null pointer or oversized send | Validate buffer, cap at 64KB for TCP, 1472 for UDP |
The takeaway: WSAEINVAL is almost never about the kernel rejecting valid inputs. It's about your code assuming a socket is alive when it's not, or passing flag combinations that the socket state doesn't support. Trace your socket's lifetime carefully, and you'll fix this for good.