From f47410ea4b8ae43e19facd378be4cf1073e1813b Mon Sep 17 00:00:00 2001 From: Petteri Aimonen Date: Fri, 13 Sep 2013 12:59:31 +0300 Subject: Move examples into subfolders, add READMEs --- examples/network_server/Makefile | 19 +++++ examples/network_server/README | 60 ++++++++++++++ examples/network_server/client.c | 116 ++++++++++++++++++++++++++ examples/network_server/common.c | 40 +++++++++ examples/network_server/common.h | 9 ++ examples/network_server/fileproto.options | 13 +++ examples/network_server/fileproto.proto | 18 ++++ examples/network_server/server.c | 131 ++++++++++++++++++++++++++++++ 8 files changed, 406 insertions(+) create mode 100644 examples/network_server/Makefile create mode 100644 examples/network_server/README create mode 100644 examples/network_server/client.c create mode 100644 examples/network_server/common.c create mode 100644 examples/network_server/common.h create mode 100644 examples/network_server/fileproto.options create mode 100644 examples/network_server/fileproto.proto create mode 100644 examples/network_server/server.c (limited to 'examples/network_server') diff --git a/examples/network_server/Makefile b/examples/network_server/Makefile new file mode 100644 index 00000000..981f2cf9 --- /dev/null +++ b/examples/network_server/Makefile @@ -0,0 +1,19 @@ +CFLAGS = -ansi -Wall -Werror -g -O0 + +# Path to the nanopb root folder +NANOPB_DIR = ../.. +DEPS = $(NANOPB_DIR)/pb_decode.c $(NANOPB_DIR)/pb_decode.h \ + $(NANOPB_DIR)/pb_encode.c $(NANOPB_DIR)/pb_encode.h $(NANOPB_DIR)/pb.h +CFLAGS += -I$(NANOPB_DIR) + +all: server client + +clean: + rm -f server client fileproto.pb.c fileproto.pb.h + +%: %.c $(DEPS) fileproto.pb.h fileproto.pb.c + $(CC) $(CFLAGS) -o $@ $< $(NANOPB_DIR)/pb_decode.c $(NANOPB_DIR)/pb_encode.c fileproto.pb.c common.c + +fileproto.pb.c fileproto.pb.h: fileproto.proto $(NANOPB_DIR)/generator/nanopb_generator.py + protoc -ofileproto.pb $< + python $(NANOPB_DIR)/generator/nanopb_generator.py fileproto.pb diff --git a/examples/network_server/README b/examples/network_server/README new file mode 100644 index 00000000..7bdcbed5 --- /dev/null +++ b/examples/network_server/README @@ -0,0 +1,60 @@ +Nanopb example "network_server" +=============================== + +This example demonstrates the use of nanopb to communicate over network +connections. It consists of a server that sends file listings, and of +a client that requests the file list from the server. + +Example usage +------------- + +user@host:~/nanopb/examples/network_server$ make # Build the example +protoc -ofileproto.pb fileproto.proto +python ../../generator/nanopb_generator.py fileproto.pb +Writing to fileproto.pb.h and fileproto.pb.c +cc -ansi -Wall -Werror -I .. -g -O0 -I../.. -o server server.c + ../../pb_decode.c ../../pb_encode.c fileproto.pb.c common.c +cc -ansi -Wall -Werror -I .. -g -O0 -I../.. -o client client.c + ../../pb_decode.c ../../pb_encode.c fileproto.pb.c common.c + +user@host:~/nanopb/examples/network_server$ ./server & # Start the server on background +[1] 24462 + +petteri@oddish:~/nanopb/examples/network_server$ ./client /bin # Request the server to list /bin +Got connection. +Listing directory: /bin +1327119 bzdiff +1327126 bzless +1327147 ps +1327178 ntfsmove +1327271 mv +1327187 mount +1327259 false +1327266 tempfile +1327285 zfgrep +1327165 gzexe +1327204 nc.openbsd +1327260 uname + + +Details of implementation +------------------------- +fileproto.proto contains the portable Google Protocol Buffers protocol definition. +It could be used as-is to implement a server or a client in any other language, for +example Python or Java. + +fileproto.options contains the nanopb-specific options for the protocol file. This +sets the amount of space allocated for file names when decoding messages. + +common.c/h contains functions that allow nanopb to read and write directly from +network socket. This way there is no need to allocate a separate buffer to store +the message. + +server.c contains the code to open a listening socket, to respond to clients and +to list directory contents. + +client.c contains the code to connect to a server, to send a request and to print +the response message. + +The code is implemented using the POSIX socket api, but it should be easy enough +to port into any other socket api, such as lwip. diff --git a/examples/network_server/client.c b/examples/network_server/client.c new file mode 100644 index 00000000..e6e9a2e0 --- /dev/null +++ b/examples/network_server/client.c @@ -0,0 +1,116 @@ +/* This is a simple TCP client that connects to port 1234 and prints a list + * of files in a given directory. + * + * It directly deserializes and serializes messages from network, minimizing + * memory use. + * + * For flexibility, this example is implemented using posix api. + * In a real embedded system you would typically use some other kind of + * a communication and filesystem layer. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fileproto.pb.h" +#include "common.h" + +bool printfile_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) +{ + FileInfo fileinfo; + + if (!pb_decode(stream, FileInfo_fields, &fileinfo)) + return false; + + printf("%-10lld %s\n", (long long)fileinfo.inode, fileinfo.name); + + return true; +} + +bool listdir(int fd, char *path) +{ + ListFilesRequest request; + ListFilesResponse response; + pb_istream_t input = pb_istream_from_socket(fd); + pb_ostream_t output = pb_ostream_from_socket(fd); + uint8_t zero = 0; + + if (path == NULL) + { + request.has_path = false; + } + else + { + request.has_path = true; + if (strlen(path) + 1 > sizeof(request.path)) + { + fprintf(stderr, "Too long path.\n"); + return false; + } + + strcpy(request.path, path); + } + + if (!pb_encode(&output, ListFilesRequest_fields, &request)) + { + fprintf(stderr, "Encoding failed.\n"); + return false; + } + + /* We signal the end of request with a 0 tag. */ + pb_write(&output, &zero, 1); + + response.file.funcs.decode = &printfile_callback; + + if (!pb_decode(&input, ListFilesResponse_fields, &response)) + { + fprintf(stderr, "Decode failed: %s\n", PB_GET_ERROR(&input)); + return false; + } + + if (response.path_error) + { + fprintf(stderr, "Server reported error.\n"); + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + int sockfd; + struct sockaddr_in servaddr; + char *path = NULL; + + if (argc > 1) + path = argv[1]; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + servaddr.sin_port = htons(1234); + + if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) + { + perror("connect"); + return 1; + } + + if (!listdir(sockfd, path)) + return 2; + + close(sockfd); + + return 0; +} diff --git a/examples/network_server/common.c b/examples/network_server/common.c new file mode 100644 index 00000000..04a5aa85 --- /dev/null +++ b/examples/network_server/common.c @@ -0,0 +1,40 @@ +/* Simple binding of nanopb streams to TCP sockets. + */ + +#include +#include +#include +#include + +#include "common.h" + +static bool write_callback(pb_ostream_t *stream, const uint8_t *buf, size_t count) +{ + int fd = (intptr_t)stream->state; + return send(fd, buf, count, 0) == count; +} + +static bool read_callback(pb_istream_t *stream, uint8_t *buf, size_t count) +{ + int fd = (intptr_t)stream->state; + int result; + + result = recv(fd, buf, count, MSG_WAITALL); + + if (result == 0) + stream->bytes_left = 0; /* EOF */ + + return result == count; +} + +pb_ostream_t pb_ostream_from_socket(int fd) +{ + pb_ostream_t stream = {&write_callback, (void*)(intptr_t)fd, SIZE_MAX, 0}; + return stream; +} + +pb_istream_t pb_istream_from_socket(int fd) +{ + pb_istream_t stream = {&read_callback, (void*)(intptr_t)fd, SIZE_MAX}; + return stream; +} diff --git a/examples/network_server/common.h b/examples/network_server/common.h new file mode 100644 index 00000000..8dab3b7c --- /dev/null +++ b/examples/network_server/common.h @@ -0,0 +1,9 @@ +#ifndef _PB_EXAMPLE_COMMON_H_ +#define _PB_EXAMPLE_COMMON_H_ + +#include + +pb_ostream_t pb_ostream_from_socket(int fd); +pb_istream_t pb_istream_from_socket(int fd); + +#endif \ No newline at end of file diff --git a/examples/network_server/fileproto.options b/examples/network_server/fileproto.options new file mode 100644 index 00000000..29a2ab0e --- /dev/null +++ b/examples/network_server/fileproto.options @@ -0,0 +1,13 @@ +# This file defines the nanopb-specific options for the messages defined +# in fileproto.proto. +# +# If you come from high-level programming background, the hardcoded +# maximum lengths may disgust you. However, if your microcontroller only +# has a few kB of ram to begin with, setting reasonable limits for +# filenames is ok. +# +# On the other hand, using the callback interface, it is not necessary +# to set a limit on the number of files in the response. + +ListFilesRequest.path max_size:128 +FileInfo.name max_size:128 diff --git a/examples/network_server/fileproto.proto b/examples/network_server/fileproto.proto new file mode 100644 index 00000000..3e70c492 --- /dev/null +++ b/examples/network_server/fileproto.proto @@ -0,0 +1,18 @@ +// This defines protocol for a simple server that lists files. +// +// See also the nanopb-specific options in fileproto.options. + +message ListFilesRequest { + optional string path = 1 [default = "/"]; +} + +message FileInfo { + required uint64 inode = 1; + required string name = 2; +} + +message ListFilesResponse { + optional bool path_error = 1 [default = false]; + repeated FileInfo file = 2; +} + diff --git a/examples/network_server/server.c b/examples/network_server/server.c new file mode 100644 index 00000000..9a9c2644 --- /dev/null +++ b/examples/network_server/server.c @@ -0,0 +1,131 @@ +/* This is a simple TCP server that listens on port 1234 and provides lists + * of files to clients, using a protocol defined in file_server.proto. + * + * It directly deserializes and serializes messages from network, minimizing + * memory use. + * + * For flexibility, this example is implemented using posix api. + * In a real embedded system you would typically use some other kind of + * a communication and filesystem layer. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fileproto.pb.h" +#include "common.h" + +bool listdir_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) +{ + DIR *dir = (DIR*) *arg; + struct dirent *file; + FileInfo fileinfo; + + while ((file = readdir(dir)) != NULL) + { + fileinfo.inode = file->d_ino; + strncpy(fileinfo.name, file->d_name, sizeof(fileinfo.name)); + fileinfo.name[sizeof(fileinfo.name) - 1] = '\0'; + + if (!pb_encode_tag_for_field(stream, field)) + return false; + + if (!pb_encode_submessage(stream, FileInfo_fields, &fileinfo)) + return false; + } + + return true; +} + +void handle_connection(int connfd) +{ + ListFilesRequest request; + ListFilesResponse response; + pb_istream_t input = pb_istream_from_socket(connfd); + pb_ostream_t output = pb_ostream_from_socket(connfd); + DIR *directory; + + if (!pb_decode(&input, ListFilesRequest_fields, &request)) + { + printf("Decode failed: %s\n", PB_GET_ERROR(&input)); + return; + } + + directory = opendir(request.path); + + printf("Listing directory: %s\n", request.path); + + if (directory == NULL) + { + perror("opendir"); + + response.has_path_error = true; + response.path_error = true; + response.file.funcs.encode = NULL; + } + else + { + response.has_path_error = false; + response.file.funcs.encode = &listdir_callback; + response.file.arg = directory; + } + + if (!pb_encode(&output, ListFilesResponse_fields, &response)) + { + printf("Encoding failed.\n"); + } +} + +int main(int argc, char **argv) +{ + int listenfd, connfd; + struct sockaddr_in servaddr; + int reuse = 1; + + listenfd = socket(AF_INET, SOCK_STREAM, 0); + + setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + servaddr.sin_port = htons(1234); + if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0) + { + perror("bind"); + return 1; + } + + if (listen(listenfd, 5) != 0) + { + perror("listen"); + return 1; + } + + for(;;) + { + connfd = accept(listenfd, NULL, NULL); + + if (connfd < 0) + { + perror("accept"); + return 1; + } + + printf("Got connection.\n"); + + handle_connection(connfd); + + printf("Closing connection.\n"); + + close(connfd); + } +} -- cgit 1.2.3-korg