commit 0fe9114117ae07525dec1aadcbd27dc43bf0e783 Author: Aaro Saila Date: Sun Sep 6 17:43:06 2026 +0300 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/ch1/TCPEchoClient4.c b/ch1/TCPEchoClient4.c new file mode 100644 index 0000000..9f042c7 --- /dev/null +++ b/ch1/TCPEchoClient4.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Practical.h" + +int main(int argc, char **argv) { + if (argc < 3 || argc > 4) { + DieWithUserMessage("Parameter(s)", " []"); + } + + const char *server_ip = argv[1]; + const char *echo_str = argv[2]; + const in_port_t server_port = (argc == 4) ? atoi(argv[3]) : 7; // 7: common echo port + + const int sock = socket(AF_INET, SOCK_STREAM, proto_get_num("tcp")); + if (sock < 0) { + DieWithSystemMessage("socket() failed"); + } + + struct sockaddr_in server_addr = {0}; + server_addr.sin_family = AF_INET; + int ret_val = inet_pton(AF_INET, server_ip, &server_addr.sin_addr.s_addr); + if (ret_val == 0) { + DieWithUserMessage("inet_pton() failed", "invalid address string"); + } else if (ret_val < 0) { + DieWithSystemMessage("inet_pton() failed"); + } + server_addr.sin_port = htons(server_port); + + if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { + DieWithSystemMessage("connect() failed"); + } + + const size_t echo_str_len = strlen(echo_str); + + ssize_t num_bytes = send(sock, echo_str, echo_str_len, 0); + if (num_bytes < 0) { + DieWithSystemMessage("send() failed"); + } else if ((size_t) num_bytes != echo_str_len) { + DieWithUserMessage("send()", "sent unexpected number of bytes"); + } + + unsigned int total_bytes_rcvd = 0; + fputs("Received: ", stdout); + while (total_bytes_rcvd < echo_str_len) { + char buffer[BUFSIZE]; + num_bytes = recv(sock, buffer, BUFSIZE - 1, 0); + if (num_bytes < 0) { + DieWithSystemMessage("recv() failed"); + } else if (num_bytes == 0) { + DieWithUserMessage("recv()", "connection closed prematurely"); + } + total_bytes_rcvd += num_bytes; + buffer[num_bytes] = '\0'; + fputs(buffer, stdout); + } + + fputc('\n', stdout); + + close(sock); + + return 0; +} + diff --git a/ch1/TCPEchoServer4.c b/ch1/TCPEchoServer4.c new file mode 100644 index 0000000..1ebd0e7 --- /dev/null +++ b/ch1/TCPEchoServer4.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "Practical.h" +#include "TCPServerUtility.h" + +static const int MAXPENDING = 5; + +int main(int argc, char **argv) { + if (argc != 2) { + DieWithUserMessage("Parameter(s)", ""); + } + + const in_port_t server_port = atoi(argv[1]); + + const int server_sock = socket(AF_INET, SOCK_STREAM, proto_get_num("tcp")); + if (server_sock < 0) { + DieWithSystemMessage("socket() failed"); + } + + struct sockaddr_in server_addr = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = htonl(INADDR_ANY), + }, + .sin_port = htons(server_port) + }; + + int ret_val = bind( + server_sock, + (struct sockaddr *) &server_addr, + sizeof(server_addr) + ); + if (ret_val < 0) { + DieWithSystemMessage("bind() failed"); + } + + ret_val = listen(server_sock, MAXPENDING); + if (ret_val < 0) { + DieWithSystemMessage("listen() failed"); + } else { + char server_name[INET_ADDRSTRLEN]; + const char *ntop_res = inet_ntop( + AF_INET, + &server_addr.sin_addr.s_addr, + server_name, + sizeof(server_name) + ); + if (ntop_res != NULL) { + printf("Listening on %s/%d...\n", server_name, server_port); + } else { + puts("Unable to get server address"); + } + } + + while (1) { + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + int client_sock = accept( + server_sock, + (struct sockaddr *) &client_addr, + &client_addr_len + ); + if (client_sock < 0) { + DieWithSystemMessage("accept() failed"); + } + + char client_name[INET_ADDRSTRLEN]; + const char *ntop_res = inet_ntop( + AF_INET, + &client_addr.sin_addr.s_addr, + client_name, + sizeof(client_name) + ); + if (ntop_res != NULL) { + printf( + "Handling client %s/%d\n", + client_name, + ntohs(client_addr.sin_port) + ); + } else { + puts("Unable to get client address"); + } + + HandleTCPClient(client_sock); + } + + return 0; +} diff --git a/ch1/TCPServerUtility.c b/ch1/TCPServerUtility.c new file mode 100644 index 0000000..3919f4e --- /dev/null +++ b/ch1/TCPServerUtility.c @@ -0,0 +1,29 @@ +#include +#include + +#include "Practical.h" + +void HandleTCPClient(const int client_sock) { + char buffer[BUFSIZE]; + + ssize_t num_bytes_rcvd = recv(client_sock, buffer, BUFSIZE, 0); + if (num_bytes_rcvd < 0) { + DieWithSystemMessage("recv() failed"); + } + + while (num_bytes_rcvd > 0) { + ssize_t num_bytes_sent = send(client_sock, buffer, num_bytes_rcvd, 0); + if (num_bytes_sent < 0) { + DieWithSystemMessage("send() failed"); + } else if (num_bytes_sent != num_bytes_rcvd) { + DieWithUserMessage("send()", "sent unexpected number of bytes"); + } + + num_bytes_rcvd = recv(client_sock, buffer, BUFSIZE, 0); + if (num_bytes_rcvd < 0) { + DieWithSystemMessage("recv() failed"); + } + } + + close(client_sock); +} diff --git a/ch1/TCPServerUtility.h b/ch1/TCPServerUtility.h new file mode 100644 index 0000000..25bf050 --- /dev/null +++ b/ch1/TCPServerUtility.h @@ -0,0 +1,3 @@ +#pragma once + +void HandleTCPClient(const int client_sock); diff --git a/ch1/meson.build b/ch1/meson.build new file mode 100644 index 0000000..6f41ae6 --- /dev/null +++ b/ch1/meson.build @@ -0,0 +1,20 @@ +executable( + 'client', + 'TCPEchoClient4.c', + common_src, + include_directories: [ + common_include + ], + c_args: c_args +) + +executable( + 'server', + 'TCPEchoServer4.c', + 'TCPServerUtility.c', + common_src, + include_directories: [ + common_include + ], + c_args: c_args +) diff --git a/common/DieWithMessage.c b/common/DieWithMessage.c new file mode 100644 index 0000000..0f386f3 --- /dev/null +++ b/common/DieWithMessage.c @@ -0,0 +1,16 @@ +#include +#include + +void DieWithUserMessage(const char *msg, const char *detail) { + fputs(msg, stderr); + fputs(": ", stderr); + fputs(detail, stderr); + fputc('\n', stderr); + exit(1); +} + +void DieWithSystemMessage(const char *msg) { + perror(msg); + exit(1); +} + diff --git a/common/Practical.c b/common/Practical.c new file mode 100644 index 0000000..47085c5 --- /dev/null +++ b/common/Practical.c @@ -0,0 +1,12 @@ +#include +#include + +#include "Practical.h" + +int proto_get_num(const char *name) { + struct protoent *protoent = getprotobyname(name); + if (protoent == NULL) { + DieWithSystemMessage("getprotobyname"); + } + return protoent->p_proto; +} diff --git a/common/Practical.h b/common/Practical.h new file mode 100644 index 0000000..b259f65 --- /dev/null +++ b/common/Practical.h @@ -0,0 +1,7 @@ +#pragma once + +#define BUFSIZE 512 + +void DieWithUserMessage(const char *msg, const char *detail); +void DieWithSystemMessage(const char *msg); +int proto_get_num(const char *name); diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..74bf2d4 --- /dev/null +++ b/meson.build @@ -0,0 +1,25 @@ +project( + 'tcp-ip-sockets-in-c', + 'c', + default_options: [ + 'buildtype=debug' + ], +) + +c_args = [ + '-std=c99', + '-Wall', + '-Wextra', + '-pedantic' +] + +common_src = files( + 'common/DieWithMessage.c', + 'common/Practical.c', +) + +common_include = [ + include_directories('common') +] + +subdir('ch1')