Skip to content

Commit 9a29de1

Browse files
authored
init
0 parents  commit 9a29de1

File tree

5 files changed

+287
-0
lines changed

5 files changed

+287
-0
lines changed

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cc=gcc
2+
flags=-Wall -Werror
3+
src=src
4+
bin=bin
5+
6+
all: clean $(bin)/server $(bin)/client
7+
8+
clean:
9+
rm -f $(bin)/*
10+
11+
$(bin)/server: $(src)/server.c
12+
$(cc) $(flags) -o $@ $<
13+
14+
$(bin)/client: $(src)/client.c
15+
$(cc) $(flags) -o $@ $<

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# TCP Server in C
2+
3+
This is a simple TCP server written from scratch in C. It is a simple server that listens on a port and echos back whatever it receives.
4+
5+
I've followed the tutorial at [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/html/multi/index.html) to gain an understanding of sockets before writing this server.
6+
7+
Note that currently it only supports one client. Will probably learn and implement an event loop kind of thing to support multiple clients.
8+
9+
## How to run
10+
11+
1. Clone the repository
12+
13+
2. Compile client and server programs (don't worry, there's a Makefile to do this for you)
14+
15+
```bash
16+
make
17+
```
18+
19+
3. Run the server (it will run on port 3000)
20+
21+
```bash
22+
./bin/server
23+
```
24+
25+
4. Run the client in another terminal
26+
27+
```bash
28+
./bin/client
29+
```
30+
31+
5. Type something in the client terminal and press enter. You should see the server echoing back what you typed.
32+
33+
6. Ctrl+C to stop the server and client.

src/client.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include <netdb.h>
2+
#include <stdbool.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include <sys/socket.h>
7+
8+
#include "./shared.h"
9+
10+
void cleanup(void);
11+
void handle_sigint(int sig);
12+
13+
struct addrinfo *server_info;
14+
15+
int main(void) {
16+
atexit(cleanup);
17+
signal(SIGINT, handle_sigint);
18+
19+
// to store the return value of various function calls for error checking
20+
int rv;
21+
22+
struct addrinfo hints = {0}; // make sure the struct is empty
23+
hints.ai_family = AF_UNSPEC; // don't care whether it's IPv4 or IPv6
24+
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
25+
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
26+
27+
// NULL to assign the address of my local host to socket structures
28+
// because the server is also running locally
29+
rv = getaddrinfo(NULL, PORT, &hints, &server_info);
30+
if (rv != 0) {
31+
fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(rv));
32+
return EXIT_FAILURE;
33+
}
34+
35+
// create a socket
36+
int socket_fd = socket(server_info->ai_family, server_info->ai_socktype,
37+
server_info->ai_protocol);
38+
if (socket_fd == -1) {
39+
perror("socket()");
40+
return EXIT_FAILURE;
41+
}
42+
43+
// connect to the server socket as the address specified in server_info
44+
rv = connect(socket_fd, server_info->ai_addr, server_info->ai_addrlen);
45+
if (rv == -1) {
46+
perror("connect()");
47+
return EXIT_FAILURE;
48+
}
49+
50+
char received_msg[1024];
51+
char *line = NULL;
52+
size_t line_cap = 0;
53+
ssize_t line_len;
54+
55+
printf("> ");
56+
while ((line_len = getline(&line, &line_cap, stdin)) > 0) {
57+
line[line_len - 1] = '\0'; // to ignore the newline character at the end
58+
59+
// send the message to the server
60+
int bytes_sent = send(socket_fd, line, line_len, 0);
61+
if (bytes_sent == -1) {
62+
perror("send()");
63+
return EXIT_FAILURE;
64+
}
65+
66+
// receive the message from the server
67+
int bytes_read = recv(socket_fd, received_msg, sizeof received_msg, 0);
68+
if (bytes_read == -1) {
69+
perror("recv()");
70+
return EXIT_FAILURE;
71+
} else if (bytes_read == 0) {
72+
fputs("recv(): server closed the connection\n", stderr);
73+
return EXIT_FAILURE;
74+
}
75+
76+
received_msg[bytes_read] = '\0'; // null-terminate the received message
77+
78+
printf("server says: %s\n", received_msg);
79+
printf("> ");
80+
}
81+
82+
return EXIT_SUCCESS;
83+
}
84+
85+
void cleanup(void) {
86+
freeaddrinfo(server_info); // free the addrinfo linked list
87+
}
88+
89+
void handle_sigint(int sig) {
90+
// call exit manually because atexit() registered the cleanup function
91+
exit(EXIT_SUCCESS);
92+
}

src/server.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#include <arpa/inet.h>
2+
#include <netdb.h>
3+
#include <signal.h>
4+
#include <stdbool.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <sys/socket.h>
9+
#include <sys/types.h>
10+
#include <unistd.h>
11+
12+
#include "./shared.h"
13+
14+
// the max number of connections that can wait in the queue
15+
#define BACKLOG 10
16+
17+
void cleanup(void);
18+
void handle_sigint(int sig);
19+
void handle_client_message(int conn_fd);
20+
21+
// to store the address information of the server
22+
struct addrinfo *server_info;
23+
24+
int main(void) {
25+
atexit(cleanup);
26+
signal(SIGINT, handle_sigint);
27+
28+
// to store the return value of various function calls for error checking
29+
int rv;
30+
31+
struct addrinfo hints = {0}; // make sure the struct is empty
32+
hints.ai_family = AF_UNSPEC; // don't care whether it's IPv4 or IPv6
33+
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
34+
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
35+
36+
// NULL to assign the address of my local host to socket structures
37+
rv = getaddrinfo(NULL, PORT, &hints, &server_info);
38+
if (rv != 0) {
39+
fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(rv));
40+
return EXIT_FAILURE;
41+
}
42+
43+
// create a socket, which apparently is no good by itself because it's not
44+
// bound to an address and port number
45+
int socket_fd = socket(server_info->ai_family, server_info->ai_socktype,
46+
server_info->ai_protocol);
47+
if (socket_fd == -1) {
48+
perror("socket()");
49+
return EXIT_FAILURE;
50+
}
51+
52+
// lose the "Address already in use" error message. why this happens
53+
// in the first place? well even after the server is closed, the port
54+
// will still be hanging around for a while, and if you try to restart the
55+
// server, you'll get an "Address already in use" error message
56+
int yes = 1;
57+
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
58+
perror("setsockopt()");
59+
exit(1);
60+
}
61+
62+
// bind the socket to the address and port number specified in server_info
63+
rv = bind(socket_fd, server_info->ai_addr, server_info->ai_addrlen);
64+
if (rv == -1) {
65+
perror("bind()");
66+
return EXIT_FAILURE;
67+
}
68+
69+
// time to listen for incoming connections
70+
// BACKLOG is the max number of connections that can wait in the queue
71+
rv = listen(socket_fd, BACKLOG);
72+
if (rv == -1) {
73+
perror("listen()");
74+
return EXIT_FAILURE;
75+
}
76+
77+
printf("server is listening on port %s...\n", PORT);
78+
79+
struct sockaddr_storage their_addr;
80+
socklen_t addr_size = sizeof their_addr;
81+
while (true) {
82+
int conn_fd = accept(socket_fd, (struct sockaddr *)&their_addr, &addr_size);
83+
if (conn_fd == -1) {
84+
perror("accept()");
85+
return EXIT_FAILURE;
86+
}
87+
88+
printf("client %d connected\n", conn_fd);
89+
90+
// since this is a blocking function, we'll only be able to handle one
91+
// client at a time
92+
handle_client_message(conn_fd);
93+
}
94+
95+
return EXIT_SUCCESS;
96+
}
97+
98+
/**
99+
* this is a blocking fuction that handles the messages from the client
100+
*/
101+
void handle_client_message(int conn_fd) {
102+
while (true) {
103+
char received_msg[1024];
104+
int bytes_read = recv(conn_fd, received_msg, sizeof received_msg - 1, 0);
105+
if (bytes_read == -1) {
106+
perror("handle_client_message(): recv()");
107+
return;
108+
} else if (bytes_read == 0) {
109+
puts("handle_client_message(): Client disconnected");
110+
return;
111+
}
112+
113+
received_msg[bytes_read] = '\0';
114+
115+
printf("client %d says: %s\n", conn_fd, received_msg);
116+
117+
char reply[1024] = "you said: \0";
118+
119+
// concat the received message to the reply message
120+
strncat(reply, received_msg, sizeof reply - strlen(reply) - 1);
121+
122+
int bytes_sent = send(conn_fd, reply, strlen(reply), 0);
123+
if (bytes_sent == -1) {
124+
perror("handle_client_message(): send()");
125+
return;
126+
}
127+
}
128+
129+
close(conn_fd);
130+
}
131+
132+
void cleanup(void) {
133+
freeaddrinfo(server_info); // free the addrinfo linked list
134+
}
135+
136+
void handle_sigint(int sig) {
137+
// call exit manually because atexit() registered the cleanup function
138+
exit(EXIT_SUCCESS);
139+
}

src/shared.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef SHARED_H
2+
#define SHARED_H
3+
4+
// the PORT number that the server will be listening on and string because,
5+
// well, getaddrinfo() wants a string as the service parameter
6+
const char *PORT = "3000";
7+
8+
#endif

0 commit comments

Comments
 (0)