Skip to content

Use SO_EXCLUSIVEADDRUSE instead of SO_REUSEADDR on Windows - #2584

Closed
dqsjqian wants to merge 1 commit into
yhirose:masterfrom
dqsjqian:windows-so-exclusive-addruse
Closed

dqsjqian wants to merge 1 commit into
yhirose:masterfrom
dqsjqian:windows-so-exclusive-addruse

Conversation

@dqsjqian

Copy link
Copy Markdown

Problem

On Windows, SO_REUSEADDR has a very different meaning than on POSIX systems: it allows a socket to steal a bind — a second process can successfully bind to a port that another process is already bound to, and the two sockets then compete for incoming traffic. As a result, starting two Server instances on the same port silently succeeds on Windows, whereas on Linux/macOS the second bind fails with EADDRINUSE as expected.

This was reported in #506, #1144 and #2011. As noted in #506, on Windows "using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE would be a correct answer" — but only the SO_REUSEADDR half of that statement is currently implemented, and it alone produces the stealing behavior.

Fix

In default_socket_options, use SO_EXCLUSIVEADDRUSE on Windows instead of SO_REUSEADDR. SO_EXCLUSIVEADDRUSE is the documented Windows option for exclusive binding: binding to an occupied port fails with WSAEADDRINUSE, matching POSIX expectations while still allowing a server to rebind its own port after restart (the primary use case of SO_REUSEADDR that #506 wants to keep — SO_EXCLUSIVEADDRUSE permits rebinding a port in TIME_WAIT left over by a previous instance of the same server, it only blocks binding over another live socket).

Non-Windows platforms are unchanged. Client sockets are unaffected: SO_EXCLUSIVEADDRUSE only affects bind semantics and cpp-httplib clients connect without binding.

Notes

  • One behavioral consideration: on Windows, a socket with SO_EXCLUSIVEADDRUSE must not later enable SO_REUSEADDR (setsockopt fails with WSAEINVAL). The only in-tree place that touches SO_REUSEADDR on Windows after socket creation is the AF_UNIX workaround in create_socket, which sets it to 0 (disables it), which is compatible.
  • Verified against a downstream project's CI: with this change, a regression test that starts a server on a port already occupied by another process now sees the server exit with a bind error on Windows (previously it silently kept running), matching the Linux/macOS behavior.

On Windows, SO_REUSEADDR is not the TIME_WAIT-reuse knob that POSIX
programmers expect: it enables 'stealing' binds, where a second socket
can successfully bind to a port that another process is already bound
to, with the two sockets then competing for incoming traffic. Two
Server instances listening on the same port silently succeed instead
of the second bind failing with WSAEADDRINUSE.

SO_EXCLUSIVEADDRUSE is the documented Windows option for exclusive
binding and restores the expected EADDRINUSE behavior. It only
affects bind semantics, so client sockets (which connect without
binding) are unaffected.

References: yhirose#506, yhirose#1144, yhirose#2011; Microsoft docs for SO_EXCLUSIVEADDRUSE.
@yhirose

yhirose commented Sep 14, 2026

Copy link
Copy Markdown
Owner

@dqsjqian Thank you for the PR and the detailed write-up.

The current default is intentional. cpp-httplib enables address/port reuse on all platforms: SO_REUSEPORT on Linux and macOS, and SO_REUSEADDR on Windows. So two servers can bind to the same port on Linux and macOS as well, not only on Windows. We actually removed SO_EXCLUSIVEADDRUSE from the Windows path in #2011 to keep the behavior consistent across platforms. Switching Windows alone to exclusive binding would bring that inconsistency back, and changing the default on every platform would affect too many existing users.

If you need exclusive binding, you can replace the default options with set_socket_options:

svr.set_socket_options([](socket_t sock) {
#ifdef _WIN32
  httplib::set_socket_opt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1);
#else
  httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
#endif
});

Thanks for your consideration.

@yhirose yhirose closed this Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants