├── makefile.rules ├── makefile ├── README.md ├── .github └── workflows │ └── codesee-arch-diagram.yml ├── .gitignore ├── inc.h ├── LICENSE ├── client.c └── server.c /makefile.rules: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | WARNING_FLAGS = -Wall -Werror 3 | OPTIMIZATION_FLAGS = -O3 -fshort-enums 4 | ARGS = $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) 5 | BUILD = $(CC) $(ARGS) -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | include makefile.rules 2 | 3 | all: build 4 | 5 | build: server client 6 | 7 | server: server.o 8 | $(BUILD) -o server server.o 9 | 10 | client: client.o 11 | $(BUILD) -o client client.o 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cRPC 2 | Remote Procedure Call Demo Written in Pure C. Check out [this blog post](https://iqdevs.github.io/Remote-Procedure-Calls-in-C/) if you're interested in a code walk-through. 3 | 4 | # Getting Started 5 | 1. Build the binaries 6 | ```bash 7 | $ make 8 | ``` 9 | 2. Run the server 10 | ```bash 11 | $ ./server 12 | ``` 13 | 3. Start a new Terminal session and run the client 14 | ```bash 15 | $ ./client 16 | ``` 17 | 18 | # Author 19 | [Fadi Hanna Al-Kass](https://github.com/alkass) 20 | -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | codesee-url: https://app.codesee.io 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | client 55 | server 56 | -------------------------------------------------------------------------------- /inc.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_H 2 | #define INC_H 3 | 4 | #define ACK 0xA 5 | 6 | typedef char byte; 7 | 8 | typedef enum { 9 | ADD = 0, 10 | SUB, 11 | MUL, 12 | DIV 13 | } OpType; 14 | 15 | typedef struct { 16 | byte ack; 17 | byte id; 18 | OpType op; 19 | byte params[2]; 20 | } Request; 21 | 22 | typedef struct { 23 | byte ack; 24 | byte id; 25 | byte status; 26 | byte data; 27 | } Response; 28 | 29 | void printRequest(Request req) { 30 | printf("Request: ACK: %d, ID: %d, OP: %d, PARAMS: [%d, %d]\n", 31 | req.ack, 32 | req.id, 33 | req.op, 34 | req.params[0], 35 | req.params[1] 36 | ); 37 | } 38 | 39 | void printResponse(Response res) { 40 | printf("Response: ACK: %d, ID: %d, STATUS: %d, DATA: %d\n", 41 | res.ack, 42 | res.id, 43 | res.status, 44 | res.data 45 | ); 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fadi Hanna Al-Kass 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "inc.h" 9 | 10 | int main() 11 | { 12 | struct sockaddr_in servaddr; 13 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 14 | bzero(&servaddr, sizeof servaddr); 15 | servaddr.sin_family = AF_INET; 16 | servaddr.sin_port = htons(22000); 17 | 18 | // Set IP address in servaddr to “127.0.0.1” (computers way of saying myself) 19 | // since our server is also on the same machine . The address in servaddr 20 | // needs to be in integer format , hence the function inet_pton. 21 | inet_pton(AF_INET, "127.0.0.1", &(servaddr.sin_addr)); 22 | 23 | // Connect to the device whose address and port number is specified in servaddr. 24 | connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 25 | 26 | Request req = { 27 | .ack = ACK, 28 | .id = 0, 29 | .op = ADD, // other options are SUB, DIV, MUL 30 | .params = {8, 2} // You may change these two values to any 8-bit integer values 31 | }; 32 | 33 | Response res = {0}; 34 | 35 | printRequest(req); 36 | 37 | // Send a stream of bytes in the form of a Request structure. 38 | // NOTE: The 'if' expression is used to silence the compiler about 39 | // complaining about an unused result (warn_unused_result). 40 | if (write(sockfd, (byte*)&req, sizeof(Request)) < 0) { 41 | printf("client write fail here\n"); 42 | } 43 | 44 | // Read a stream of bytes in the form of a Response structure. 45 | // NOTE: The 'if' expression is used to silence the compiler about 46 | // complaining about an unused result (warn_unused_result). 47 | if (read(sockfd, (byte*)&res, sizeof(Response)) < 0) { 48 | printf("client read fail here\n"); 49 | } 50 | 51 | printResponse(res); 52 | 53 | close(sockfd); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "inc.h" 11 | 12 | bool handleAdd(const Request* req, Response* res) { 13 | printf("res->data = %d + %d\n", req->params[0], req->params[1]); 14 | res->data = req->params[0] + req->params[1]; 15 | return true; 16 | } 17 | 18 | bool handleSub(const Request* req, Response* res) { 19 | printf("res->data = %d - %d\n", req->params[0], req->params[1]); 20 | res->data = req->params[0] - req->params[1]; 21 | return true; 22 | } 23 | 24 | bool handleMul(const Request* req, Response* res) { 25 | printf("res->data = %d * %d\n", req->params[0], req->params[1]); 26 | res->data = req->params[0] * req->params[1]; 27 | return true; 28 | } 29 | 30 | bool handleDiv(const Request* req, Response* res) { 31 | printf("res->data = %d / %d\n", req->params[0], req->params[1]); 32 | res->data = req->params[0] / req->params[1]; 33 | return true; 34 | } 35 | 36 | bool handleRequest(const Request* req, Response* res) { 37 | switch (req->op) { 38 | case ADD: 39 | return handleAdd(req, res); 40 | case SUB: 41 | return handleSub(req, res); 42 | case MUL: 43 | return handleMul(req, res); 44 | case DIV: 45 | return handleDiv(req, res); 46 | default: 47 | return false; 48 | } 49 | } 50 | 51 | int main() { 52 | struct sockaddr_in servaddr; 53 | int listen_fd = socket(AF_INET, SOCK_STREAM, 0); 54 | 55 | // Clear servaddr 56 | bzero(&servaddr, sizeof(servaddr)); 57 | 58 | // Set Addressing scheme 59 | servaddr.sin_family = AF_INET; 60 | 61 | // Allow any IP to connect 62 | servaddr.sin_addr.s_addr = htons(INADDR_ANY); 63 | 64 | // Listen on port 22000 – htons(22000) 65 | servaddr.sin_port = htons(22000); 66 | 67 | // Prepare to listen for connections from address/port 68 | // specified in sockaddr ( Any IP on port 22000 ) 69 | bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)); 70 | 71 | // Start Listening for connections. 72 | // Keep at the most 1 connection requests waiting. 73 | // If there are more than 10 computers wanting to connect 74 | // at a time, the 11th one fails to. 75 | listen(listen_fd, 1); 76 | 77 | // Accept a connection from any device who is willing to connect. 78 | // If there is no one who wants to connect , wait. A file descriptor 79 | // is returned. This can finally be used to communicate, whatever 80 | // is sent by the device accepted can be read from comm_fd, whatever 81 | // is written to comm_fd is sent to the other device. 82 | int comm_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL); 83 | 84 | Request req = {0}; 85 | Response res = {0}; 86 | 87 | // Read a stream of bytes in the form of a Request structure 88 | // from end-user. 89 | // NOTE: The 'if' expression is used to silence the compiler about 90 | // complaining about an unused result (warn_unused_result). 91 | if (read(comm_fd, (byte*)&req, sizeof(Request)) < 0) { 92 | printf("server read fail here\n"); 93 | } 94 | 95 | // Verify that ACK is set properly 96 | if (req.ack == ACK) { 97 | 98 | printRequest(req); 99 | 100 | res.ack = req.ack; 101 | res.id = req.id; 102 | 103 | // Handle request. 104 | // This will set the value of req.data if applicable. 105 | if (handleRequest(&req, &res)) { 106 | res.status = true; 107 | } 108 | else { 109 | res.status = false; 110 | } 111 | 112 | printResponse(res); 113 | 114 | // Send a stream of bytes back in the form of a Response structure. 115 | // NOTE: The 'if' expression is used to silence the compiler about 116 | // complaining about an unused result (warn_unused_result). 117 | if (write(comm_fd, (byte*)&res, sizeof(Response)) < 0) { 118 | printf("server write fail here\n"); 119 | } 120 | } 121 | else { 122 | printf("Unrecognized ACK (%d). Failing here\n", req.ack); 123 | } 124 | 125 | close(comm_fd); 126 | 127 | return 0; 128 | } 129 | --------------------------------------------------------------------------------