[16]SOAP/XML在UDP上的实现

18  SOAP/XML Over UDP

UDP is a simple, unreliable datagram protocol: UDP sockets are connectionless. UDP address formats are identical to those used by TCP. In particular UDP provides a port identifier in addition to the normal Internet address format. The UDP port space is separate from the TCP port space (i.e. a UDP port may not be "connected" to a TCP port). In addition broadcast packets may be sent (assuming the underlying network supports this) by using a reserved "broadcast address"; this address is network interface dependent. Client-side messages with SOAP-over-UDP endpoint URLs (soap.udp://...) will be automatically transmitted as datagrams. Server-side applications should set the SOAP_IO_UDP mode flag to accept UDP requests, e.g. using soap_init1 or soap_set_mode. The maximum message length for datagram packets is restricted by the buffer size SOAP_BUFLEN, which is 65536 by default, unless compiled with WITH_LEAN to support small-scale embedded systems. For UDP transport SOAP_BUFLEN must not exceed the maximum UDP packet size 65536 (the size of datagram messages is constrained by the UDP packet size 216=65536 as per UDP standard). You can use gzip compression to reduce the message size, but note that compressed SOAP-over-UDP is a gSOAP-specific feature because it is not part of the SOAP-over-UDP specification. The SOAP-over-UDP specification relies on WS-Addressing. The wsa.h file in the import directory defines the WS-Addressing elements for client and server applications. The gSOAP implementation conforms to the SOAP-over-UDP requirements:

  • SOAP-over-UDP server endpoint URL format: soap.udp://host:port/path
  • Support one-way message-exchange pattern (MEP) where a SOAP envelope is carried in a user datagram.
  • Support request-response message-exchange pattern (MEP) where SOAP envelopes are carried in user datagrams.
  • Support multicast transmission of SOAP envelopes carried in user datagrams.
  • Support both SOAP 1.1 and SOAP 1.2 envelopes.

The following additional features are also available, but are not supported by the SOAP-over-UDP specification:

  • Zlib/gzip message compression (compile -DWITH_GZIP).
  • SOAP with DIME attachments over UDP.
  • SOAP with MIME attachments (SwA) over UDP.
  • Support for IPv6 (compile -DWITH_IPV6)

18.1  Using WS-Addressing with SOAP-over-UDP

A SOAP-over-UDP application MUST use WS-Addressing to control message delivery as per SOAP-over-UDP specification. The wsa.h file in the import directory defines the WS-Addressing elements. To include the WS-Addressing elements in the SOAP Header for messaging, a struct SOAP_ENV__Header structure must be defined in your header file with the appropriate WS-Addressing elements. For example:

#import "wsa.h"
struct SOAP_ENV__Header
{
   mustUnderstand _wsa__MessageID wsa__MessageID 0;
   mustUnderstand _wsa__RelatesTo *wsa__RelatesTo 0;
   mustUnderstand _wsa__From *wsa__From 0;
   mustUnderstand _wsa__ReplyTo *wsa__ReplyTo 0;
   mustUnderstand _wsa__FaultTo *wsa__FaultTo 0;
   mustUnderstand _wsa__To wsa__To 0;
   mustUnderstand _wsa__Action wsa__Action 0;
};


We also included a //gsoap wsa schema import directive in the wsa.h file to enable the generation of WSDL specifications that import (instead of includes) the WS-Addressing elements. Note that the //gsoapopt w directive must not be present in your header file to enable WSDL generation. One-way SOAP-over-UDP messages (see Section 7.3) should be declared to include the wsa:MessageID, wsa:To, and wsa:Action elements in the SOAP Header of the request message as follows:

//gsoap ns service method-header-part: sendString wsa__MessageID
//gsoap ns service method-header-part: sendString wsa__To
//gsoap ns service method-header-part: sendString wsa__Action
int ns__sendString(char *str, void);


Request-response SOAP-over-UDP messages should be declared to include the wsa:MessageID, wsa:To, wsa:Action, and wsa:ReplyTo elements in the SOAP Header of the request message, and the the wsa:MessageID, wsa:To, wsa:Action, and wsa:RelatesTo elements in the SOAP Header of the response message:

//gsoap ns service method-header-part: echoString wsa__MessageID
//gsoap ns service method-header-part: echoString wsa__To
//gsoap ns service method-header-part: echoString wsa__Action
//gsoap ns service method-input-header-part: sendString wsa__ReplyTo
//gsoap ns service method-output-header-part: echoString wsa__RelatesTo
int ns__echoString(char *str, char **res);


For the content requirements of these elements, please consult the SOAP-over-UDP specification and/or read the next sections explaining SOAP-over-UDP unicast, multicast, one-way, and request-response client and server applications.

18.2  Client-side One-way Unicast

This example assumes that the gSOAP header file includes the SOAP Header with WS-Addressing elements and the ns__sendString function discussed in Section 18.1

struct soap soap;
struct SOAP_ENV__Header header; // the SOAP Header
soap_init(&soap);
soap.send_timeout = 1; // 1s timeout
soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header
header.wsa__MessageID = "message ID";
header.wsa__To = "server URL";
header.wsa__Action = "server action";
soap.header = &header; // bind the SOAP Header for transport
// Send the message over UDP:
if (soap_send_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!"))
   soap_print_fault(&soap, stderr); // report error
soap_end(&soap); // cleanup
soap_destroy(&soap); // cleanup
soap_done(&soap); // close connection (should not use soap struct after this)

 

18.3  Client-side One-way Multicast

This example is similar to the one-way unicast example discussed above, but uses a broadcast address and the SO_BROADCAST socket option:

struct soap soap;
struct SOAP_ENV__Header header; // the SOAP Header
in_addr_t addr = inet_addr("1.2.3.4"); // optional
soap_init(&soap);
soap.send_timeout = 1; // 1s timeout
soap.connect_flags = SO_BROADCAST; // required for broadcast
soap.ipv4_multicast_if = &addr; // optional for IPv4: see setsockopt IPPROTO_IP IP_MULTICAST_IF
soap.ipv6_multicast_if = addr; // optional for IPv6: multicast sin6_scope_id
soap.ipv4_multicast_ttl = 1; // optional, see setsockopt IPPROTO_IP, IP_MULTICAST_TTL
soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header
header.wsa__MessageID = "message ID";
header.wsa__To = "server URL";
header.wsa__Action = "server action";
soap.header = &header; // bind the SOAP Header for transport
// Send the message over UDP to a broadcast address:
if (soap_send_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!"))
   soap_print_fault(&soap, stderr); // report error
soap_destroy(&soap); // cleanup
soap_end(&soap); // cleanup
soap_done(&soap); // close connection (should not use soap struct after this)


Please refer to the socket options for IPPROTO_IP IP_MULTICAST_IF to specify the default interface for multicast datagrams to be sent from. This is a struct in_addr (in_addr_t for sin6_scope_id) interface value. Otherwise, the default interface set by the system administrator will be used (if any). Please refer to the socket options for IPPROTO_IP IP_MULTICAST_TTL to limit the lifetime of the packet. Multicast datagrams are sent with a default value of 1, to prevent them to be forwarded beyond the local network. This parameter can be set between 1 to 255.

18.4  Client-side Request-Response Unicast

This example assumes that the gSOAP header file includes the SOAP Header with WS-Addressing elements and the ns__echoString function discussed in Section 18.1

struct soap soap;
struct SOAP_ENV__Header header; // the SOAP Header
struct wsa__EndpointReferenceType replyTo; // (anonymous) reply address
char *res; // server response
soap_init(&soap);
soap.send_timeout = 1; // 1s timeout
soap.recv_timeout = 1; // 1s timeout
soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header
soap_default_wsa__EndpointReferenceType(&soap, &replyTo); // init reply address
replyTo.Address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
header.wsa__MessageID = "message ID";
header.wsa__To = "server URL";
header.wsa__Action = "server action";
header.wsa__ReplyTo = &replyTo;
soap.header = &header; // bind the SOAP Header for transport
// Send and receive messages over UDP:
if (soap_call_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!", &res))
{
   if (soap.error == SOAP_EOF && soap.errnum == 0)
      // Timeout: no response from server (message already delivered?)
   else
      soap_print_fault(&soap, stderr);
}
else
   // UDP server response is stored in 'res'
// check SOAP header received, if applicable
check_header(&soap.header);
soap_destroy(&soap); // cleanup
soap_end(&soap); // cleanup
soap_done(&soap); // close connection (should not use soap struct after this)

 

18.5  Client-side Request-Response Multicast

This example is similar to the request-response unicast example discussed above, but uses a broadcast address and the SO_BROADCAST socket option. Because we expect to receive multiple responses, we also need to use separate request-response messages to send one request and consume multiple responses. In this example we defined a bcastString request and a bcastStringResponse response message, which are essentially declared as one-way messages in the header file:

//gsoap ns service method-header-part: bcastString wsa__MessageID
//gsoap ns service method-header-part: bcastString wsa__To
//gsoap ns service method-header-part: bcastString wsa__Action
//gsoap ns service method-header-part: bcastString wsa__ReplyTo
int ns__bcastString(char *str, void);
//gsoap ns service method-header-part: bcastStringResponse wsa__MessageID
//gsoap ns service method-header-part: bcastStringResponse wsa__To
//gsoap ns service method-header-part: bcastStringResponse wsa__Action
//gsoap ns service method-header-part: bcastStringResponse wsa__RelatesTo
int ns__bcastStringResponse(char *res, void);


To obtain response one-way operations, use the wsdl2h -b option. The client code includes a loop to receive response messages until a timeout occurs:

struct soap soap;
struct SOAP_ENV__Header header;
struct wsa__EndpointReferenceType replyTo;
char *res;
soap_init(&soap);
soap.connect_flags = SO_BROADCAST;
soap.send_timeout = 1; // 1s timeout
soap.recv_timeout = 1; // 1s timeout
soap_default_SOAP_ENV__Header(&soap, &header);
soap.header = &header;
soap_default_wsa__EndpointReferenceType(&soap, &replyTo);
replyTo.Address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
header.wsa__MessageID = "message ID";
header.wsa__To = "server URL";
header.wsa__Action = "server action";
header.wsa__ReplyTo = &replyTo;
if (soap_send_ns__bcastString(&soap, "soap.udp://...", NULL, "hello world!"))
   soap_print_fault(&soap, stderr);
else
{
   for (;;)
   {
      if (soap_recv_ns__bcastStringResponse(&soap, &res))
         break;
      // Got response 'res' from a server
   }
   if (soap.error == SOAP_EOF && soap.errnum == 0)
      // Timeout: no more messages received
   else
      soap_print_fault(&soap, stderr);
}
soap_destroy(&soap); // cleanup
soap_end(&soap); // cleanup
soap_done(&soap); // close connection (should not use soap struct after this)


Note that a server for the bcastString does not need to use two-one way messages. Thus, multicast request-response message pattern can be declared and implemented as request-response operations at the server side.

18.6  SOAP-over-UDP Server

The following example code illustrates a SOAP-over-UDP server for one-way sendString and request-response echoString messages. This example assumes that the gSOAP header file includes the SOAP Header with WS-Addressing elements and the ns__echoString function discussed in Section 18.1.

int main()
{
   struct soap soap;
   soap_init1(&soap, SOAP_IO_UDP); // must set UDP flag
   // bind to host (NULL=current host) and port:
   if (!soap_valid_socket(soap_bind(&soap, host, port, 100)))
   {
      soap_print_fault(&soap, stderr);
      exit(1);
   }
   for (;;)
   {
      if (soap_serve(&soap))
         soap_print_fault(&soap, stderr); // report the problem
      soap_destroy(&soap);
      soap_end(&soap);
   }
   soap_done(&soap); // close connection
}
int ns__echoString(struct soap *soap, char *str, char **res)
{
   if (!soap->header)
      return soap_sender_fault(soap, "No SOAP header", NULL);
   if (!soap->header->wsa__MessageID)
      return soap_sender_fault(soap, "No WS-Addressing MessageID", NULL);
   soap->header->wsa__RelatesTo = (struct wsa__Relationship*)soap_malloc(soap, sizeof(struct wsa__Relationship));
   soap_default_wsa__Relationship(soap, soap->header->wsa__RelatesTo);
   soap->header->wsa__RelatesTo->__item = soap->header->wsa__MessageID;
   // must check for duplicate messages
   if (check_received(soap->header->wsa__MessageID))
   {
      // Request message already received
      return SOAP_STOP; // don't return response
   }
   if (!soap->header->wsa__ReplyTo || !soap->header->wsa__ReplyTo->Address)
      return soap_sender_fault(soap, "No WS-Addressing ReplyTo address", NULL);
   soap->header->wsa__To = soap->header->wsa__ReplyTo->Address;
   soap->header->wsa__MessageID = soap_strdup(soap, soap_int2s(soap, id_count++)) ;
   soap->header->wsa__Action = "http://genivia.com/udp/echoStringResponse";
   *res = str;
   return SOAP_OK;
}
int ns__sendString(struct soap *soap, char *str)
{
   if (!soap->header)
      return SOAP_STOP;
   if (!soap->header->wsa__MessageID)
      return SOAP_STOP;
   // must check for duplicate messages
   if (check_received(soap->header->wsa__MessageID))
      return SOAP_STOP;
   return SOAP_OK;
}
int ns__sendStringResponse(struct soap *soap, char *res)
{ return SOAP_NO_METHOD; } // we don't expect to serve this message


The server binds to a host and port and accepts messages in a tight sequential loop. Because UDP does not have the equivalent of an accept the messages cannot be dispatched to threads, the soap_serve waits for a message and immediately accepts it. You can use a receive timeout to make soap_serve non-blocking. To obtain response one-way operations from a WSDL, use the wsdl2h -b option. This produces additional one-way operations to support asynchronous handling of response messages in the same way requests are handled.

18.7  SOAP-over-UDP Multicast Receiving Server

For UDP multicast support, follow the suggestions in Section 18.6 and change the initialization parts of the code to enable UDP multicast port binding by to telliing the kernel which multicast groups you are interested in:

 

int main()
{
   struct soap soap;
   struct ip_mreq mcast;
   soap_init1(&soap, SOAP_IO_UDP);    if (!soap_valid_socket(soap_bind(&soap, host, port, 100)))
   {
      soap_print_fault(&soap, stderr);
      exit(1);
   }
   mcast.imr_multiaddr.s_addr = inet_addr("put IP multicast address of group here");
   mcast.imr_interface.s_addr = htonl(INADDR_ANY);
   if (setsockopt(soap.master, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast, sizeof(mcast))<0)
      ... error ...

你可能感兴趣的:(SOAP)