initial commit. this is NOT WORKING by a long shot.

git-svn-id: https://protobuf-c.googlecode.com/svn/trunk@104 00440858-1255-0410-a3e6-75ea37f81c3a
This commit is contained in:
lahiker42 2008-12-19 02:41:32 +00:00
parent ddb111cab3
commit 27aed0b2ac
10 changed files with 727 additions and 0 deletions

View File

@ -0,0 +1,98 @@
#include "example-word-funcs-service.h"
static void
word_closure_to_ptr_word (const Example__Word *message,
void *closure_data)
{
const char *rv = message == NULL || message->word == NULL
? "*error*" : message->word;
* (char **) closure_data = strdup (rv);
}
int main (int argc, char **argv)
{
Example__WordFuncs_Service *service = NULL;
const char *name = "word_funcs";
const char *addr = NULL;
dispatch = protobuf_c_sctp_dispatch_new ();
/* Create service (either local or remote) */
for (i = 1; i < argc; i++)
{
if (strcmp (argv[i], "--local") == 0)
{
example_word_funcs_service_init ();
service = example_word_funcs_service;
name = "<local word_funcs>";
}
else if (strncmp (argv[i], "--addr=", 7) == 0)
addr = argv[i] + 7;
else if (strncmp (argv[i], "--name=", 7) == 0)
name = argv[i] + 7;
else
usage (argv[0]);
}
if (service == NULL && addr == NULL)
die ("missing --addr=");
if (service == NULL && addr != NULL)
{
channel = protobuf_c_sctp_channel_ipv4_connect_by_hostport (addr, dispatch);
service = protobuf_c_sctp_channel_add_remote_service (channel,
name,
&example__word_funcs__descriptor);
}
/* main loop: invoke service methods (either local or remote) */
for (;;)
{
fprintf (stderr, "%s >> ", name);
if (fgets (buf, sizeof (buf), stdin) == NULL)
break;
/* parse command */
at = buf;
while (*at && isspace (*at))
at++;
if (*at == 0)
continue;
cmd = at;
while (*at && !isspace (*at))
at++;
if (*at != 0)
{
*at++ = 0;
while (*at && *isspace (*at))
at++;
}
if (strcmp (cmd, "uppercase") == 0
|| strcmp (cmd, "lowercase") == 0)
{
/* functions mapping string => string can be handled via this
mechanism */
char *rv = NULL;
Example__Word input = EXAMPLE__WORD__INIT;
input.word = at;
if (strcmp (cmd, "uppercase") == 0)
example__word_funcs__uppercase (service,
&input,
word_closure_to_ptr_word,
&rv);
else if (strcmp (cmd, "lowercase") == 0)
example__word_funcs__lowercase (service,
&input,
word_closure_to_ptr_word,
&rv);
else
assert (0);
while (rv == NULL)
protobuf_c_sctp_dispatch_run (dispatch);
printf ("%s\n", rv);
fflush (stdout);
free (rv);
}
}
return 0;
}

View File

@ -0,0 +1,33 @@
#include "protobuf-c-sctp.h"
void usage (const char *prog_name)
{
die ("usage: %s --port=PORT [--name=NAME]\n"
"Run an example word-funcs service.\n", prog_name);
}
int main (int argc, char **argv)
{
int port = 0;
ProtobufC_SCTP_LocalService local_services[1] = { { "word_funcs", NULL } };
for (i = 1; i < argc; i++)
{
if (strncmp (argv[i], "--port=", 7) == 0)
port = atoi (argv[i] + 7);
else if (strncmp (argv[i], "--name=", 7) == 0)
local_services[0].name = argv[i] + 7;
else
usage (argv[0]);
}
if (port == 0)
die ("--port=PORT is required");
/* initialize our our actual service implementation */
example_word_funcs_service_init ();
local_services[0].service = example_word_funcs_service;
/* run the server */
protobuf_c_sctp_server_run (1, local_services, 0, NULL, port);
return 0;
}

View File

@ -0,0 +1,60 @@
#include "protobuf-c-sctp.h"
typedef struct _StringFuncsImpl StringFuncsImpl;
struct _StringFuncsImpl
{
Example__WordFuncs_Service base_service;
};
void usage (const char *prog_name)
{
die ("usage: %s --port=PORT [--name=NAME]\n"
"Run an example word-funcs service.\n", prog_name);
}
typedef struct _ChannelConfigData ChannelConfigData;
struct _ChannelConfigData
{
const char *name;
ProtobufCService *service;
};
/* Set up newly-accepted connection */
static void
init_channel (ProtobufC_SCTP_Channel *channel,
void *data)
{
ChannelConfigData *config = data;
protobuf_c_sctp_channel_add_local_service (channel,
config->name,
config->service);
}
int main (int argc, char **argv)
{
int port = 0;
StringFuncsImpl impl;
ChannelConfigData config = { "word_funcs", &impl.base_service.base };
for (i = 1; i < argc; i++)
{
if (strncmp (argv[i], "--port=", 7) == 0)
port = atoi (argv[i] + 7);
else if (strncmp (argv[i], "--name=", 7) == 0)
config.name = argv[i] + 7;
else
usage (argv[0]);
}
if (port == 0)
die ("--port=PORT is required");
/* initialize our our actual service implementation */
example__word_funcs__init (&impl.base_service);
impl.base_service.uppercase = implement_uppercase;
listener = protobuf_c_sctp_listener_new_ipv4 (NULL, port, dispatch,
init_channel, &config);
for (;;)
protobuf_c_sctp_dispatch_run (dispatch, -1);
return 0;
}

View File

@ -0,0 +1,61 @@
#include "example-word-funcs-service.h"
/* note: the implementations here only handle ascii characters--
this is a tutorial about RPC,
not utf8 encoding or unicode normalization */
Example__WordFuncs_Service *example_word_funcs_service;
static Example__WordFuncs_Service word_funcs_service;
static void
implement_uppercase (Example__WordFuncs_Service *service,
const Example__Word *input,
Example__Word_Closure closure,
void *closure_data)
{
char *rv = strdup (input->word);
Example__Word output = EXAMPLE__WORD__INIT;
char *at;
for (at = rv; *at; at++)
if ('a' <= *at && *at <= 'z')
*at -= ('a' - 'A');
output.word = rv;
closure (&output, closure_data);
free (rv);
}
static void
implement_lowercase (Example__WordFuncs_Service *service,
const Example__Word *input,
Example__Word_Closure closure,
void *closure_data)
{
char *rv = strdup (input->word);
Example__Word output = EXAMPLE__WORD__INIT;
char *at;
for (at = rv; *at; at++)
if ('A' <= *at && *at <= 'Z')
*at += ('a' - 'A');
output.word = rv;
closure (&output, closure_data);
free (rv);
}
#if 0 /* you could consider an init function like this instead */
void example_word_funcs_service_init (void)
{
example_word_funcs_service = &word_funcs_service;
example__word_funcs__init (&word_funcs_service);
word_funcs_service.uppercase = implement_uppercase;
word_funcs_service.lowercase = implement_lowercase;
}
#endif
/* TODO: provide a c99 example */
Example__WordFuncs_Service example__word_funcs__service__global =
{
EXAMPLE__WORD_FUNCS__SERVICE__BASE_INIT,
implement_uppercase,
implement_lowercase
};

View File

@ -0,0 +1,8 @@
#include "generated/example.pb-c.h"
extern Example__WordFuncs_Service example__word_funcs__service__global;
/* extern ProtobufCService *example__word_funcs__service; */
#define example__word_funcs__service \
((ProtobufCService *) (&example__word_funcs__service__global))

View File

@ -0,0 +1,11 @@
package example;
message Word
{
required string word = 1;
};
service WordFuncs {
rpc Uppercase (Word) returns (Word);
rpc Lowercase (Word) returns (Word);
}

View File

@ -0,0 +1,65 @@
#include "../google/protobuf-c/protobuf-c.h"
typedef struct _ProtobufC_SCTP_Dispatch ProtobufC_SCTP_Dispatch;
typedef struct _ProtobufC_SCTP_Channel ProtobufC_SCTP_Channel;
typedef struct _ProtobufC_SCTP_Listener ProtobufC_SCTP_Listener;
/* For servers and clients, you can get at the array of
remote service objects from the closure-data of your local-service's
invocation method. */
ProtobufCService **
protobuf_c_sctp_closure_data_get_remote_services (void *closure_data);
typedef struct {
const char *name;
ProtobufCService *service;
} ProtobufC_SCTP_LocalService;
typedef struct {
const char *name;
ProtobufCServiceDescriptor *descriptor;
} ProtobufC_SCTP_RemoteService;
ProtobufC_SCTP_Config *
protobuf_c_sctp_config_new (size_t n_local_services,
ProtobufC_SCTP_LocalService *local_services,
size_t n_remote_services,
ProtobufC_SCTP_RemoteService *remote_services);
/* a channel: an sctp association (technically sctp allows multiple
streams within a single association (unlike tcp), but we do not
use that ability. when created as a client, this does automatic
reconnecting. */
ProtobufC_SCTP_Channel *
protobuf_c_sctp_client_new_ipv4(const uint8_t *addr,
uint16_t port,
ProtobufC_SCTP_Config *config,
ProtobufC_Dispatch *dispatch);
ProtobufC_SCTP_Channel *
protobuf_c_sctp_client_new_ipv4_dns (const char *host,
uint16_t port,
ProtobufC_SCTP_Config *config,
ProtobufC_Dispatch *dispatch);
ProtobufC_SCTP_Channel *
protobuf_c_sctp_client_new_ipv4_hostport(const char *host_port,
ProtobufC_SCTP_Config *config,
ProtobufC_SCTP_Dispatch *dispatch);
void protobuf_c_sctp_channel_set_error_handler (ProtobufC_SCTP_Channel *channel,
ProtobufC_SCTP_ErrorFunc func,
void *data,
ProtobufC_SCTP_Destroy destroy);
ProtobufCService *
protobuf_c_sctp_channel_peek_remote_service(ProtobufC_SCTP_Channel *channel,
unsigned index);
void protobuf_c_sctp_channel_shutdown (ProtobufC_SCTP_Channel *channel);
void protobuf_c_sctp_channel_destroy (ProtobufC_SCTP_Channel *channel);
/* a listener: a passive sctp socket awaiting new connections */
ProtobufC_SCTP_Server *
protobuf_c_sctp_server_new_ipv4 (const uint8_t *bind_addr,
uint16_t port,
ProtobufC_SCTP_Config *config);
void protobuf_c_sctp_server_destroy (ProtobufC_SCTP_Server *server);

12
src/sctp-rpc/render-rfc Executable file
View File

@ -0,0 +1,12 @@
#! /bin/sh
nroff -t -ms rfc-protobuf-sctp.ms |
perl -e '
undef $/; # Read whole files in a single gulp.
while (<>) { # Read the entire input file.
s/FORMFEED(\[Page\s+\d+\])\s+/ \1\n\f\n/sg;
s/\f\n$//; # Want no formfeed at end?
print; # Print the resultant file.
}' > rfc-protobuf-sctp.txt
groff -t -ms rfc-protobuf-sctp.ms > rfc-protobuf-sctp.ps

View File

@ -0,0 +1,371 @@
.pl 10.0i
.po 0
.ll 7.2i
.lt 7.2i
.nr LL 7.2i
.nr LT 7.2i
.ds LF Benson
.ds RF FORMFEED[Page %]
.ds CF
.ds LH PROPOSAL
.ds RH 7 December 2008
.ds CH Protobuf over SCTP
.hy 0
.ad l
.in 0
.ce
Proposal for Implementing Protocol-Buffer RPC over SCTP
.ti 0
Status of this Memo
.fi
.in 3
This memo describes a proposed method for the encapsulation of
Protocol-Buffer Datagrams via the Stream Control Transmission Protocol.
This is an experimental, not recommended standard.
Distribution of this memo is unlimited.
.ti 0
Overview and Rationale
Google's Protobuf Buffer package (or "protobuf", for short)
provides a language for
specifying the format binary-data. In that language, a "message"
in that language defines a specific binary-data format.
The language also defines a "service": a service has
an array of named "methods", each of which takes a specific
type of message as input, and gives a specific
type of message as output.
SCTP is a reliable datagram-oriented protocol,
parallel to UDP or TCP, and riding atop the IP layer
(either IPv4 or IPv6).
Protocol-Buffer RPC over SCTP (or protobuf-sctp for short),
defines a way of implementing RPCs to one of a set of named services.
The general inplementation of the server and client
is very similar, since both the server and client can
implement local services and invoke remote services.
Usually, a server provides a local service and the client
accesses it, but we provide a more symmetric model.
.ti 0
Why Services and not Messages?
One possible alternate scheme for using protobuf is to encapsulate messages
without any regard to the request/response cycle.
This could be quite beneficial at times, especially when the RPC does not
need to get a response message. Despite the possibility of eliminating
many round-trips, we do not offer any protocol but the rpc-method technique.
Services are the fundamental unit of RPC in protocol buffers,
so it makes sense to use them as the basis of a protocol.
It is possible that a string name is not the most efficient way
to identify the service, but many languages are well-optimized
for string handling, so it's unclear if base-128 encoding is really
better. In any event, strings are sent on every request for both
the name of the service, and the name of the method.
.ti 0
Why SCTP and not a generalized reliable datagram layer?
In principle, this specification could be generalized
to cover any reliable datagram passing mechanism.
We chose to limit our attention to SCTP for
the following reasons:
.IP
.RS
.IP *
We can analyze the performance of our design choices.
.RE
.RS
.IP *
SCTP boasts a rich set of features, like the ability to
specify ordered or unordered delivery.
.RE
.ti 0
Definitions
.KS
.IP
Datagram
.RS
.IP
a piece of binary-data, an array of bytes.
.RE
.KE
.KS
.IP
SCTP Connection
.RS
.IP
a single stream of datagrams.
.RE
.KE
.KS
.IP
Handshake
.RS
.IP
the sequence of datagrams to establish client/server validity.
.RE
.KE
.KS
.IP
Uninitialized Connection
.RS
.IP
an SCTP connection before the handshake has completed.
.RE
.KE
.KS
.IP
Request ID
.RS
.IP
a 64-bit number allocated by the end that begins an RPC request.
It is recommend that the allocation strategy simply use an incrementing 64-bit
counter.
.RE
.KE
.KS
.IP
Datagram Type
.RS
.IP
is a single byte that is the first byte in every message.
It can be used to distinguish the type of message.
.RE
.KE
.ti 0
Overview of the Protocol
The session always begins with ordered delivery of three
messages, the handshake. Only after the handshake is completed
may unordered messages be sent.
For each remote-procedure call, the caller must allocate a request_id.
The request_id is necessary because we want
to support unordered delivery and because we would like to support
concurrent backend service invocations.
.ti 0
Initial handshake
The first packet is sent by the client,
the active end of the connection.
.KS
.TS
tab(:);
l s
| c | c |
| l | l | .
HANDSHAKE_REQUEST
=
format:name
_
byte:datagram_type (HANDSHAKE_REQUEST=1)
HandshakeRequest:request
_
.TE
.KE
The HandshakeRequest message format is defined
by the following protobuf file fragment:
.DS L
message HandshakeService
{
required string name = 1;
optional string service_type_name = 2;
}
message HandshakeRequest
{
// lists the services provided by the client to the server
repeated HandshakeService services = 1;
}
.DE
Once the request has been received,
the server should respond with a message
.KS
.TS
tab(:);
l s
| c | c |
| l | l | .
HANDSHAKE_RESPONSE
=
format:name
_
byte:datagram_type (HANDSHAKE_RESPONSE=2)
HandshakeResponse:request
_
.TE
.KE
where HandshakeResponse is defined by the
following fragment, which uses the definition of
HandshakeService above:
.DS L
message HandshakeResponse
{
// lists the services provided by the client to the server
repeated HandshakeService services = 1;
}
.DE
This message gives a list of services provided by the
server (which can be called by the client).
Once the response has been received,
the client should respond with the final handshake message:
.KS
.TS
tab(:);
l s
| c | c |
| l | l | .
HANDSHAKE_COMPLETED
=
format:name
_
byte:datagram_type (HANDSHAKE_COMPLETED=3)
_
.TE
.KE
As far as the client is concerned, the handshake is completed
when it received the HandshakeRespond message;
for the server, the handshake is completed slightly later:
once the HandshakeCompleted message is received.
For pipelineing purposes, it is nice to be able to send
requests before completing the handshake.
But until the handshake is complete, all packets
must be sent in ordered fashion, to ensure that the handshake
is the first packet processed. (This is the point of the HandshakeCompleted
message: to ensure that all sends will be ordered until
the client has received the respond.)
.ti 0
The RPC Protocol
The format of a remote-procedure call (RPC) transaction
is that a request is sent from the caller to
the caller. Eventually, the called resource
trasmits a response packet. The protocol specified here
permits responses to be received out of order.
Out-of-order receipt is implemented by requiring the caller
(or, more precisely, its RPC implementation)
to allocate a request_id for each request.
The caller which has multiple outstanding requests must then
match the request id to the appropriate response.
The caller may throw an error if a response with an invalid request_id
is encountered.
The remaining sections will describe the wire-format of the messages
sent for a request/response pair.
.ti 0
Request Format
.KS
.TS
tab(:);
l s
| c | c |
| l | l | .
REQUEST
=
format:name
_
byte:datagram_type (REQUEST=4)
uint64:request_id
NUL-terminated string:service_name
NUL-terminated string:method_name
protobuf:request
_
.TE
.KE
The format of "request" is specified by the service-definition.
.ti 0
Response Format
.KS
.TS
tab(:);
l s
| c | c |
| l | l | .
RESPONSE
=
format:name
_
byte:datagram_type (RESPONSE=5)
uint64:request_id
protobuf:response
_
.TE
.KE
.ti 0
Discussion
Multiple types of service can be provided with a prioritized pecking
order. An additional property is built-in worm detection and
eradication. Because IP only guarantees best effort delivery, loss of
a carrier can be tolerated. With time, the carriers are
self-regenerating. While broadcasting is not specified, storms can
cause data loss. There is persistent delivery retry, until the
carrier drops. Audit trails are automatically generated, and can
often be found on logs and cable trays.
.ti 0
Security Considerations
.in 3
Messages are sent unencrypted, so this encapsulation cannot
be used safely on the broader internet.
.KS
.ti 0
Author's Address
.nf
David Benson
EMail: daveb@ffem.org
.KE
.KS
.ti 0
Appendix: table of datagram types
.TS
tab(:);
c | c
| l | l | .
value:datagram type
=
1:HANDSHAKE_REQUEST
2:HANDSHAKE_RESPONSE
3:HANDSHAKE_COMPLETED
4:REQUEST
5:RESPONSE
_
.TE
.KE

View File

@ -0,0 +1,8 @@
struct _ProtobufC_SCTP_Channel
{
int fd;
protobuf_c_boolean is_passive; /* ie the server */
};