For network programming, generally a client will create a local socket, and optionally bind it to a known address.
Then, the client will send many packets from the same socket. If it wasn't already bound to a port, the UDP implementation will bind to a randomly chosen free port, and the socket will then stay bound to that port.
The implementation in WiFiUDP is entirely wrong:
int WiFiUDP::beginPacket(IPAddress ip, uint16_t port)
{
ip_addr_t addr;
addr.addr = ip;
if (_ctx)
_ctx->unref();
_ctx = new UdpContext;
return (_ctx->connect(addr, port)) ? 1 : 0;
}
This will create a new socket, that binds to a new port, for every single packet!
This breaks how UDP is supposed to work, because the other end will call recvfrom() to receive an incoming packet, and then return the response to the IP/port found as the source of that packet. If the ESP8266 has sent another packet to anyone, that port is no longer bound.
Also, if I begin with a port number, the socket should stay bound to that port until I dispose it. Trying to return a datagram that I received on this port should not mysteriously unbind the server and make it unable to receive more datagrams!
Similarly, trying to create a new UDP instance to return packets received from a remote end will break, because the remote end likely has a NAT gateway that expects the return traffic to come from the same IP/port that it sent the data to.