plgd-dev / go-coap

Implementation of CoAP Server & Client in Go

Home Page:https://coap.technology

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to reuse the udp socket as both Server and Client

ihidchaos opened this issue · comments

Hi @jkralik , I want to make the net.NewListenUDP() and the udp.Dial() uses the same socket so that it can have both server and client functionality.

The reason I want to do this is that I need to forward messages from the local end device to the cloud server, or to forward commands from the cloud server to the end device, for two-way communication.

image

I used net.NewListenUDP() and udp.NewServer() to implement CoAP Server and udp.Dial() to implement CoAP Client.

First, I implement Server(server1, localhost:5683) on the PC to listen to messages from the end device and implement Client (client1) to send these messages to the cloud, for example, 192.168.10.10:12345 post to abcd.com:15683, this step is successful.

Then, when the cloud sends commands to the PC, I need to implement a second Server to listen to the commands from the cloud, such as GET/POST messages. So I listened to this address (server2,192.168.10.10:12345) to receive messages from the cloud, and this step was successful.

The problem I encounter is that when I receive a message from the local end device again, I need to use the same socket as the Client (client1) to send it to the cloud server. But this socket (192.168.10.10:12345) is already listened to and used by server2, so I can't post the message to the cloud server again.

Can you help me with how I can implement listen and dial in the same socket?

Thank you in advance!

Why do you need to this, what's the use-case? Is the PC behind a router and you're having issues with NAT? Maybe I'm misunderstanding the question, but some context would be nice.

The remote port on the cloud server doesn't dictate which port you use for the client socket on the PC. Remember, you can have multiple clients, each with their own socket, open toward the cloud server on the PC, if you want to. I would just let the client on the PC pick a random free port for its local socket (they will still talk to the remote socket on port 12345), and set the server on the PC to run on a local socket bound to port 12345.

Why do you need to this, what's the use-case? Is the PC behind a router and you're having issues with NAT? Maybe I'm misunderstanding the question, but some context would be nice.

The remote port on the cloud server doesn't dictate which port you use for the client socket on the PC. Remember, you can have multiple clients, each with their own socket, open toward the cloud server on the PC, if you want to. I would just let the client on the PC pick a random free port for its local socket (they will still talk to the remote socket on port 12345), and set the server on the PC to run on a local socket bound to port 12345.

Hi @JosefWN, Thank you for your reply. My usage scenario is that I have a Thread Border Router (OTBR), Thread has limited bandwidth and the OTBR mounts 100+ sub devices. These sub-devices use IPV6 protocol and are converted to IPV4 addresses on top of the OTBR via NAT64 and communicate with the cloud server through the WAN interface of the OTBR. These sub-devices need to maintain real-time long connections with the cloud server (using heartbeat packets with JSON payloads), which consumes a very large amount of Thread bandwidth and puts a lot of pressure on Thread RCP. So I want to use CoAP service (call it Proxy) on top of OTBR instead of Thread sub-device to send heartbeat messages to maintain NAT channel and only when there are other service related messages to interact with, CoAP service forwards them to Thread sub-device.

My idea is that each Thread sub-device sends a registration request to the CoAP service located at the OTBR to inform the Proxy to proxy heartbeat for it; both the upgoing messages from the device and the downgoing messages from the cloud server will be forwarded by the Proxy in both directions.

The local port 12345 of OTBR I mentioned is chosen randomly, such as Device1:12345<->cloud:5683,Device2:12346<->cloud:5683. this is obtained randomly by using ResolveUDPAddr("udp", "") without specifying the port.

My specific implementation is: at the Server's For loop, when the message is read, I need to deserialize the packet first to determine the type of the packet: if it is a Request packet (get/post/put/delete), it means that the current Proxy is a CoAP Server role, and this packet is a message sent by the cloud server as a CoAP Client to This packet is a message sent by the cloud server as CoAP Client to the Proxy (such as commanding the device to turn off the lights or get the device properties), and should enter the Route handler set by the Proxy.

Otherwise, it is a Response packet, which means that the Proxy has sent a Request packet and received a response from the cloud server, which should not go to the handler but should be forwarded to the program that initiated the request.

I found it difficult to make changes to the current coap library, so I used the original udp to achieve this goal easily. There are two key points.

  1. my Server instead of
    func (s *Server) Serve(l *coapNet.UDPConn) error {
func (device *Device) Serve() {
	buf := make([]byte, 1024)
	for {
		// read from remote cloud server
		nr, addr, err := device.oneNetUdpLocalConn.ReadFromUDP(buf)
		if err != nil {
			if netErr, ok := err.(golangNet.Error); ok && (netErr.Temporary() || netErr.Timeout()) {
				Sleep(5 * time.Millisecond)
				Continue
			}
			oneNetLog.Error(err)
			return
		}
		tmp := make([]byte, nr)
		copy(tmp, buf)

		go func() {
			ctx := context.Background()
			AcquireMessage := device.pool.
			_, err = acquireMessage.Unmarshal(tmp)
			if err != nil {
				oneNetLog.Error(err)
				return
			}
			device.messageID = acquireMessage.MessageID()
			device.ResponseToken = acquireMessage.Token()
			// check message codes type
			switch acquireMessage.Code() {
			GET, codes.POST, codes.PUT, codes.DELETE:
				DELETE: //request packet
				oneNetLog.Infof("request message from %s", addr.String())
				path, err := acquireMessage.Path()
				if err != nil {
					oneNetLog.Error(err)
					return
				}
				if f, ok := device.handler[path]; ok {
					//goto handler
					f(acquireMessage)
				} else {
					device.SendNotFound()
				}
			default:
				// response packet
				device.recvMsg = tmp
				device.recvChan <- true
			}
		}()
	}
}
  1. my Receive instead of ReadFromUDP
func (device *Device) Receive() (*udpMessagePool.Message, error) {
	select {
	// wait until serve loop has response message
	case <-device.recvChan:
		break
	After(time.Second * 5):
		return nil, errors.New("timeout")
	}
	ctx := context.Background()
	resp := device.pool.AcquireMessage(ctx)
	_, err := resp.Unmarshal(device.recvMsg)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

Do you have any good ideas for me to implement using this library? Thanks!

Hi @ihidchaos .

This is an interesting use case so I extended the server about that feature in this PR (branch jkralik/feature/createClientConnAtServer). Please, could you look at if it solves your issue?

Thx

Example usage is in test:
TestServerNewClient

I tested it and it works, thank you!