-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.cpp
More file actions
173 lines (136 loc) · 6.47 KB
/
Copy pathserver.cpp
File metadata and controls
173 lines (136 loc) · 6.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <iostream> // For std::cout, std::cerr, std::endl
#include <cstring> // For strerror()
#include <cerrno> // For errno
//Socket API's
#include <sys/socket.h> // For socket(), bind(), listen(), accept()
#include <netinet/in.h> // For sockaddr_in()
#include <arpa/inet.h> // For inet_ntop(), htons()
#include <signal.h> // For signal()
#include <unistd.h> // For close()
/*
Helper function to print errors and exit
Exit code 1 for errors and 0 for success
strerror() is a C library function that returns a string describing the error
errno is a global variable that stores the error code and these numbers are defined by the OS
*/
static void die(const char* msg) {
std::cerr << msg << ": " << std::strerror(errno) << std::endl;
std::exit(1);
}
int main () {
/* 1. Create a TCP listening socket
socket(domain, type, protocol);
AF_INET = IPv4
SOCK_STREAM = TCP
protocol=0 lets the OS pick TCP for SOCK_STREAM
*/
// Ignore SIGPIPE signal when client closes connection
// SIGNAL CLOSED PIPE Indicator ~ SIGPIPE
signal(SIGPIPE, SIG_IGN);
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1) die("socket");
std::cout << "Socket Created, fd: " << listen_fd << std::endl;
//sleep(60); // Delay the program to keep the socket open
/* 2. Allow quick restarts; use setsockopt(SO_REUSEADDR/SO_REUSEPORT)
When you stop and start the server quickly, the socket will be in a TIME_WAIT state
and you will not be able to bind to the same port again
SO_REUSEADDR/SO_REUSEPORT allows you to reuse the socket immediately
*/
int opt = 1;
if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) die("setsockopt(SO_REUSEADDR)");
if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) die("setsockopt(SO_REUSEPORT)");
/* 3. Bind the socket to an address and port
A socket is just a file desciptor until you bind it to an address and port
Let's bind it to all interfaces(0.0.0.0) on port 8080
*/
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // host to network byte order
addr.sin_addr.s_addr = htonl(INADDR_ANY); // any IP address
if(bind(listen_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) die("bind");
std::cout << "Socket bound to port 8080" << std::endl;
/*4. Listen for incoming connections
listen(socket, backlog)
backlog is the maximum number of connections that can be queued
*/
if(listen(listen_fd, 16) == -1) die("listen");
std::cout << "Socket listening for connections" << std::endl;
/* 5. This is the important part! Accept incoming connections / clients
accept(socket, address, address_len)
address is a pointer to a sockaddr structure
address_len is a pointer to the size of the address structure
accept() is blocking, it will wait until a client connects
We can initiate client connections with netcat or telnet
nc [ip_addr : PORT] or telnet localhost 8080
Client performs a three-way handshake to establish a connection
SYN, SYN-ACK, ACK
SYN = Synchronize Sequence Numbers
SYN-ACK = Synchronize Acknowledgment
ACK = Acknowledgment
Client sends a SYN packet to the server
Server responds with a SYN-ACK packet
Client sends an ACK packet to the server
Connection is established
Kernel creates a new socket for the other incoming connections
listen_fd is still listening for new connections
client_fd is the socket for the client
*/
sockaddr_in client;
socklen_t client_len = sizeof(client);
int client_fd = accept(listen_fd, reinterpret_cast<sockaddr*>(&client), &client_len);
if(client_fd == -1) die("accept");
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client.sin_addr, ip, sizeof(ip));
std::cout << "Client connected from " << ip << ":" << ntohs(client.sin_port) << ", fd = " << client_fd << std::endl;
/* 6. Send and revieve data via the pipe
TCP connection has been established and we can send and receive data
send() and recv() are used to send and receive data
send() -> Application Buffer -> Kernel Buffer -> Network
recv() -> Network -> Kernel Buffer -> Application Buffer
send() - Data is stored in the kernel buffer;
The function copies the data from application buffer to kernel buffer.
The TCP/IP stack will break the data into segments.
TCP adds a header to the data which contains the source and destination port and sequence number.
The IP layer adds a header to the data which contains the source and destination IP address.
The data is then sent to the network.
Output type is int which is the number of bytes sent.
recv() - Data is received from the network;
The function copies the data from kernel buffer to application buffer.
The TCP/IP stack will reassemble the data into the original data.
The data is then returned to the application.
Output type is int which is the number of bytes received.
send() and recv() are blocking functions; they will wait until the data is sent or received.
If the data is not sent or received, the function will block and wait.
buffer + bytes_sent is the pointer to the next byte to send
buffer - pointer to the first byte to send
bytes_sent - number of bytes sent so far
This is pointer arithmetic!
*/
const size_t BUFF_SIZE = 4096; // 4KB
char buffer[BUFF_SIZE];
while(true) {
ssize_t bytes_read = recv(client_fd, buffer, BUFF_SIZE, 0);
if(bytes_read == 0) {
std::cout << "Client Disconnected" << std::endl;
break;
}
if(bytes_read == -1) die("recv");
ssize_t bytes_sent = 0;
while(bytes_sent < bytes_read) {
ssize_t temp = send(client_fd, buffer + bytes_sent, bytes_read - bytes_sent, 0);
if(temp == -1) die("send");
bytes_sent += temp;
}
}
/* 7. Close the socket - close(socket)
Closes the socket and frees the file descriptor
The socket is now closed and can no longer be used
The socket is removed from the file descriptor table
The socket is now available for reuse
The socket is now available for binding to a new address and port
The socket is now available for listening for new connections
*/
close(client_fd);
close(listen_fd);
return 0;
}