我目前正在开发需要使用UDP进行通信的服务器和应用程序。我的服务器使用Golang,相关代码如下:
import (
"flag"
"fmt"
"net"
"os"
)
var (
UDP_SOCKET_PORT = flag.Int("udp_socket_server_port", 10001, "Socket sckServer port")
udpSocketAddrArr = []net.Addr{}
_udpConn net.PacketConn
_broadcastAddr *net.UDPAddr
)
func StartUDPSocket() {
_udpConn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
if err != nil {
panic(err)
}
defer _udpConn.Close()
fmt.Printf("conn: %s\n", _udpConn.LocalAddr())
_broadcastAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", getBroadcastIP(), *UDP_SOCKET_PORT))
if err != nil {
panic(err)
}
fmt.Printf("UDP serving at %s:%d...\n", _broadcastAddr.IP, _broadcastAddr.Port)
for {
buf := make([]byte, 2048)
_, addr, err := _udpConn.ReadFrom(buf)
if err != nil {
panic(err)
}
udpSocketAddrArr = append(udpSocketAddrArr, addr)
messageTxt := string(buf)
checkUDPPayload(messageTxt, _udpConn, _broadcastAddr)
}
}
func UDPSend(payload string) {
for _, el := range udpSocketAddrArr {
udpConnWrite(payload, GetUDPConn(), el)
}
}
func UDPBroadcast(payload string) {
udpConnWrite(payload, _udpConn, _broadcastAddr)
}
func udpConnWrite(payload string, conn net.PacketConn, addr net.Addr) {
bytesWritten, err := conn.WriteTo([]byte(payload), addr)
if err != nil {
panic(err)
}
fmt.Printf("UDP Wrote %d bytes to %s\n", bytesWritten, addr.String())
}
func getBroadcastIP() string {
return "255.255.255.255"
}
func GetCurrentIP() string {
//find current ip
mIpV4 := ""
host, _ := os.Hostname()
addrs, _ := net.LookupIP(host)
for _, addr := range addrs {
if ipv4 := addr.To4(); ipv4 != nil {
mIpV4 = ipv4.String()
}
}
return mIpV4
}
func GetUDPConn() net.PacketConn {
return _udpConn
}
func GetUDPBroadcastConn() *net.UDPAddr {
return _broadcastAddr
}
然后在
checkUDPPayload()
里面,我有一个如下所示的代码:
func checkUDPPayload(payload string, conn net.PacketConn, addr net.Addr) {
...
udpConnWrite(
"some string",
conn,
addr,
)
}
它可以工作,它从我的Android应用程序接收有效负载(由于某种原因,让它在我的家庭网络上工作是一件痛苦的事情,并且在工作中工作相当容易)。但是我的应用程序无法接收服务器发送的数据包,并且我的应用程序可以接收它自己的数据包(我想这是有道理的)。我的 Android 代码如下所示:
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255")
private lateinit var localBroadcastAddr: InetAddress
private lateinit var connectedToServerSocket: DatagramSocket
private lateinit var appSocket: DatagramSocket
val socketRespObservers = mutableListOf<(String) -> Unit>()
private var socketResp: String by Delegates.observable("") { _, _, newVal ->
socketRespObservers.forEach { it(newVal) }
}
private lateinit var ctx: FragmentActivity
fun start(ctx: FragmentActivity) {
[email protected] = ctx
//socket communication
ctx.lifecycleScope.launch(Dispatchers.IO) {
try {
connectedToServerSocket = DatagramSocket()
appSocket = DatagramSocket(UDP_SERVER_PORT)
connectedToServerSocket.connect(getBroadcastAddr(), UDP_SERVER_PORT)
connectedToServerSocket.broadcast = true
Log.i(MainActivity.TAG, "connectedToServerSocket connected: ${connectedToServerSocket.isConnected}")
Log.i(MainActivity.TAG, "appSocket connected: ${appSocket.isConnected}")
sendToUDP("some string", getLocalBroadcastAddr())
while (true) {
receiveFromUDP()
receiveFromServerUDP()
}
} catch (e: SocketException) {
Log.e(TAG, "Socket Error:", e)
close()
}catch (e: SecurityException) {
Log.e(TAG, "Sec Error:", e)
close()
} catch (e: IOException) {
Log.e(TAG, "IO Error:", e)
close()
} catch (e: Exception) {
Log.e(TAG, "Error:", e)
close()
}
}
}
fun sendToUDP(payload: String, srvAddr: InetAddress) {
try {
val sendPacket =
DatagramPacket(
payload.toByteArray(),
payload.length,
srvAddr,
UDP_SERVER_PORT
)
appSocket.send(sendPacket)
}catch (e : java.lang.Exception){
Log.e(TAG, "err sending udp: ${e.localizedMessage}")
}
}
private fun receiveFromUDP() {
val receiveData = ByteArray(1024)
val receivePacket = DatagramPacket(receiveData, receiveData.size)
appSocket.receive(receivePacket)
socketResp = String(receivePacket.data, 0, receivePacket.length)
}
private fun receiveFromServerUDP() {
val receiveData = ByteArray(1024)
val receivePacket = DatagramPacket(receiveData, receiveData.size)
connectedToServerSocket.receive(receivePacket)
socketResp = String(receivePacket.data, 0, receivePacket.length)
}
fun getBroadcastAddr() : InetAddress {
return broadcastAddr
}
fun getLocalBroadcastAddr() : InetAddress {
if(!::localBroadcastAddr.isInitialized){
val currentIp = Utils.instance.getIpv4HostAddress()
Log.i(TAG, "currentIp: $currentIp")
val currentIpArr = currentIp.split(".")
localBroadcastAddr = InetAddress.getByName("${currentIpArr[0]}.${currentIpArr[1]}.${currentIpArr[2]}.255")
}
return localBroadcastAddr
}
fun close() {
appSocket.close()
}
companion object {
const val TAG = "SocketHelper"
val instance: SocketHelper by lazy {
SocketHelper()
}
const val UDP_SERVER_PORT = 10001
}
工作中运行的代码使用
255.255.255.255
进行发送,而 DatagramSocket
不需要端口。我使用 DatagramSocket
来发送和接收。正如你所看到的,代码更丑陋,因为我尝试有 2 个 DatagramSocket
来查看是否有不同的配置 DatagramSocket
,我可能会收到服务器对我的“消息”的响应,就像现在,在我的家庭网络中,应用程序无法接收服务器的数据包,只能接收应用程序本身发送的数据包。 connectedToServerSocket.isConnected
返回true,但我看到一个答案,它是为TCP
准备的,即使它说它已连接,它似乎对我没有任何用处。
更新:
我今天做了一些测试并继续做了一些研究,但我仍然无法完成这项工作。希望当我回到工作岗位时不会有任何问题。我对双方都做了一些小改动。现在我在想,也许这个问题是由我家里的本地网络引起的。我的 ISP 有点蹩脚,就像目前我的 NAT 设置为严格,我认为这是由于我没有固定 IP 造成的(因为它的成本几乎与我当前的预付费互联网计划一样多),所以我怀疑我的调制解调器的配置可能会影响我本地网络上的 UDP 通信。所以我买了一个路由器,认为如果我将我的电脑和手机连接到它的网络,我至少会有更多的控制权,至少允许 UDP 端口,这样我的系统就可以工作,但这似乎并不那么容易。我已经激活了 UPnP,但我的 golang 服务器似乎没有包含在允许或可以使用 UPnP 的应用程序中。
更新:
刚下班回家,我的更改确实破坏了系统在我工作网络上的工作。然后我检查了我以前的工作代码,至少在android方面,它是最简单的。它看起来像这样:
private lateinit var basicSocket: DatagramSocket
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255")
basicSocket = DatagramSocket()
basicSendToUDP("some string", getBroadcastAddr())
while (true) {
basicReceiveFromUDP()
}
fun basicSendToUDP(payload: String, iAddr: InetAddress) {
Thread {
val sendPacket =
DatagramPacket(
payload.toByteArray(),
payload.length,
iAddr,
UDP_SERVER_PORT
)
basicSocket.send(sendPacket)
}.start()
}
private fun basicReceiveFromUDP() {
val receiveData = ByteArray(1024)
val receivePacket = DatagramPacket(receiveData, receiveData.size)
basicSocket.receive(receivePacket)
socketResp = String(receivePacket.data, 0, receivePacket.length)
}
fun getBroadcastAddr(): InetAddress {
return broadcastAddr
}
然后这是 golang 方面的样子,它与我之前发布的没有什么不同:
import (
"flag"
"fmt"
"net"
"os"
)
var (
UDP_SOCKET_PORT = flag.Int("udp_socket_server_port", 10001, "Socket sckServer port")
udpSocketAddrArr = []net.Addr{}
_udpServer net.PacketConn
_broadcastAddr *net.UDPAddr
)
func StartUDPSocket() {
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
if err != nil {
panic(err)
}
_udpServer = conn//don't remove this; this seems to be quite important
defer _udpServer.Close()
fmt.Printf("UDP serving at %v...\n", _udpServer.LocalAddr().String())
_broadcastAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", getBroadcastIP(), *UDP_SOCKET_PORT))
if err != nil {
panic(err)
}
for {
buf := make([]byte, 2048)
_, addr, err := _udpServer.ReadFrom(buf)
if err != nil {
panic(err)
}
udpSocketAddrArr = append(udpSocketAddrArr, addr)
messageTxt := string(buf)
checkUDPPayload(messageTxt, _udpServer, addr)
}
}
func UDPSend(payload string) {
for _, el := range udpSocketAddrArr {
udpConnWrite(payload, GetUDPConn(), el)
}
}
func UDPBroadcast(payload string) {
udpConnWrite(payload, GetUDPConn(), GetUDPBroadcastConn())
}
func udpConnWrite(payload string, conn net.PacketConn, addr net.Addr) {
if conn == nil {
fmt.Println("udpConnWrite conn nil")
}
if addr == nil {
fmt.Println("udpConnWrite addr nil")
}
bytesWritten, err := conn.WriteTo([]byte(payload), addr)
if err != nil {
panic(err)
}
fmt.Printf("UDP Wrote %d bytes to %s\n", bytesWritten, addr.String())
}
func getBroadcastIP() string {
return "255.255.255.255"
}
func GetCurrentIP() string {
//find current ip
mIpV4 := ""
host, _ := os.Hostname()
addrs, _ := net.LookupIP(host)
for _, addr := range addrs {
if ipv4 := addr.To4(); ipv4 != nil {
mIpV4 = ipv4.String()
}
}
return mIpV4
}
func GetUDPConn() net.PacketConn {
return _udpServer
}
func GetUDPBroadcastConn() *net.UDPAddr {
return _broadcastAddr
}
func GetUDPConnLst() []net.Addr {
return udpSocketAddrArr
}
func ClearUDPConnLst() {
udpSocketAddrArr = nil
}
那么,当最简单的代码在我的工作网络上完美运行时,我的家庭网络可能会出现什么问题?
在 Golang 服务器代码中,您使用相同的端口来侦听传入消息和广播(由 UDP 等无连接协议支持)。
// Check if UDP Server is listening at the correct address and port
func StartUDPSocket() {
// Consider printing this information to confirm it is what you expect.
fmt.Printf("Listening on UDP port: %d\n", *UDP_SOCKET_PORT)
// ...
}
确保端口在您的本地网络中已打开且可访问。您还可以检查数据包连接是否正确设置。
另外,在更新的 Go 代码中,缓冲区
buf
的大小被硬编码为 2048 字节。如果传入数据包较大,导致截断,或者如果传入数据包较小,则可能导致不必要的内存分配。
并且您当前的代码使用
panic(err)
来处理错误,这不是生产代码中的最佳方法,因为它会导致整个应用程序崩溃。
for {
buf := make([]byte, 2048)
_, addr, err := _udpServer.ReadFrom(buf)
if err != nil {
panic(err)
}
// other logic
}
您可以考虑,例如:
const maxUDPSize = 65507 // The maximum UDP packet size for IPv4
func StartUDPSocket() {
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
if err != nil {
log.Fatalf("Could not start UDP server: %v", err) // Replaced panic with log
return
}
_udpServer = conn
defer _udpServer.Close()
// (other code)
for {
buf := make([]byte, maxUDPSize) // Allocate buffer with maximum UDP size
n, addr, err := _udpServer.ReadFrom(buf)
if err != nil {
log.Printf("Error reading UDP packet: %v", err) // Logging instead of panicking
continue // Skip to next iteration instead of stopping the entire application
}
udpSocketAddrArr = append(udpSocketAddrArr, addr)
messageTxt := string(buf[:n]) // Only consider received bytes
checkUDPPayload(messageTxt, _udpServer, addr)
}
}
我将 2048 字节的硬编码缓冲区长度更改为 65507 字节,这是 IPv4 网络中 UDP 数据包的最大大小。这可确保您不会错过传入数据包的任何部分。此外,我将缓冲区切片为
buf[:n]
以仅包含实际接收到的字节。
我还将
panic(err)
替换为 log.Printf()
以记录错误。这样,如果发生错误,应用程序就不会崩溃。我添加了一个 continue
语句,以便在出现错误时跳到下一次迭代。
在您的 Android 代码中,您正在将数据发送到
255.255.255.255
。这是一个“有限广播地址”,它将数据包广播到本地网络中的所有主机。但是,这可能并不总是有效,具体取决于您的网络配置。
// Using 255.255.255.255 may not always work.
// You might want to use the exact server's IP address to debug.
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255");
我会在函数中添加一些调试信息
receiveFromServerUDP
:
private fun receiveFromServerUDP() {
val receiveData = ByteArray(1024);
val receivePacket = new DatagramPacket(receiveData, receiveData.length);
// Log a message before trying to receive to confirm that the code reaches this point
Log.i(TAG, "Waiting to receive UDP packet");
connectedToServerSocket.receive(receivePacket);
// Log received data
socketResp = new String(receivePacket.getData(), 0, receivePacket.getLength());
Log.i(TAG, "Received UDP packet: " + socketResp);
}
检查您的本地防火墙设置是否可能。既然您提到您的家庭网络有严格的 NAT,这可能是导致您问题的原因之一。 您提到您的 ISP 不提供固定 IP,并且您怀疑调制解调器的配置可能会影响 UDP 通信。检查您的调制解调器或路由器是否有可能影响 UDP 流量的设置。此外,UPnP 设置通常与本地网络通信无关,因此启用或禁用它不会对您的具体情况产生影响(您尝试调试的代码,其中涉及 Golang 服务器和 Android 客户端之间的直接 UDP 通信) ).