Socket Application Protocols
Unix Sockets
Unix sockets: API to allow programs to communicate over networks.
- Designed to work with many underlying protocols
A socket is a "communications endpoint" that appears to process as a file descriptor (think of a port). Important kinds of sockets:
- server socket: used by server to accept connections from clients (not used for exchange of data)
- client socket: used to exchange data between client and server systems.
System Calls
socket: creates an unconnected socket.
bind: associate a socket with a network interface identified by a network address. This is because a system typically has multiple network interfaces.
listen: (server only) makes a socket a server socket (to allow incoming connections).
accept: (server only) wait for an incoming connection from a client system. This is called repeatedly.
connect: (client only) initiate connection to a remote system
Socket Addresses
A Socket API is desigend to work with many underlying network technologies.
struct sockaddr is the "supertype" for all network addresses.
- A "type" filed is at the beginning of struct to distinguish variants
- E.g., if type field contains
AF_INET, it is an IP address.
E.g., struct sockaddr_in is the "subtype" for IP addresses (IPv4)
Example
Create a Server Socket:
int create_server_socket(int port) {
struct sockaddr_in serveraddr = {0}; // create a socket address ipv4 interface
int ssock_fd = socket(AF_INET, SOCK_STREAM, 0); // defines for ip address family with TCP
if (ssock_fd < 0)
fatal("socket failed");
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // "wildcard"; listen for all available network interfaces
serveraddr.sin_port = htons((unsigned short)port); // associated with which applications we want to connect with
if (bind(ssock_fd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
fatal("bind failed");
if (listen(ssock_fd, 5) < 0) // 5 is the "backlog", defining how many requests can be queued before refusing connections
fatal("listen failed");
return ssock_fd;
}
- Common TCP ports: 88 (HTTP), 443 (HTTPS), 22 (SSH)
- Found in
csapp.c
Wait for incoming connection:
int accept_connection(int ssock_fd, struct sockaddr_in* clientaddr) {
unsigned clientlen = sizeof(clientaddr);
int childfd = accept(ssock_fd, (struct sockaddr *) clientaddr, &clientlen);
if (childfd < 0)
fatal("accept failed");
return childfd;
}
clientaddrdefines client's IP address so that the server can know. Use cases: restrict which IP address that can be connect.- By using
accept, we block all connections until a connection request arrives.
Server Loop: (terrible example, but bare minimum)
int main(int argc, char **argv) {
char buf[256];
int port = atoi(argv[1]);
int ssock_fd = create_server_socket(port);
while (1) {
struct sockaddr_in clientaddr;
int clientfd = accept_connection(ssock_fd, &clientaddr);
ssize_t rc = read(clientfd, buf, sizeof(buf)); // read some data
if (rc > 0) {
write(clientfd, buf, rc);
}
close(clientfd);
}
}
- This program reads the incoming data and sends it right back.
Testing the server: Run with:
$ gcc -Wall -o server server.c
$ ./server 30000
Test using telnet program (test text based protocols) (or use nc)
$ telnet localhost 30000
- Port numbers < 1024 can only be used by admin users on Linux
localhostis the loopback network Output:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hey there!
hey there!
Connection closed by foreign host.
When writing network applications keep in mind:
- reading from socket (endpoint of a TCP connection) can return fewer bytes than requested, so you generally need to call
readandwritein a loop.rio_readnandrio_writenhelp with this exact problem
- network connections can be broken (so you need to check the result of
readandwrite, error often indicates that the connection no longer exists.)
Host Names
DNS = Domain Name Service
It assigns meaningful names (e.g., ugradx.cs.jhu.edu) to network addresses (such as 128.220.224.100)
getaddrinfo lookup network address for hostname.
csapp
The textbook Computer Systems: A Programmer's Perspective provides a library of convenient functions for writing network applications. It includes routines that reduce the complexity of implementing network applications in C and C++.
open_listenfdopens a sever socket given port name as stringopen_clientfdsimplified interface for connecting to a server by specifying host name (or address) and port.rio_functionsare robust I/O routines, handle looping for short reads/writes and interruptions from signals automatically.rio_t: data type wrapping a file descriptor and allowing buffered inputrio_readnreadnbytes from ario_t.rio_readlinebread a line of input from ario_t
Application Protocols
Application Protocols determine how data is exchanged by instances of an application program.
- Usually a server and a client
- Another possibility: peer to peer (p2p) applications.
- E.g., HTTP (HyperText Transport Protocol) is used by web browsers and web servers.
Synchronous: the connected peers take turns talking. Asynchronous protocols: possible, but much more complicated to implement.
Client/server protocol: client sends request, server sends response
Message format: both peers must be able to determine where each message starts and end. Each peer must also be able to determine the meaning of each recieved message.
Text-based protocols: common because they are easy to debut and reason about.
HTTP
HTTP is a synchronous client/server protocol used by web browsers, web servers, web clients, and web services.
Clients send request to server, and server sends back a response.
- The request specifies a verb (GET, POST, PUT, etc.) and a name of a resource.
Requests and responses may have a body containing data.
- The content type specifies what kind of data the body contains.
Example of a HTTP request:
GET /1024/768 HTTP/1.1
Host: placekitten.com
User-Agent: curl/7.58.0
Accept: */*
- This request is sent via TCP port 80
Response sent by placekitten.com
HTTP/1.1 200 OK
Date: Wed, 13 Nov 2019 12:33:20 GMT
Content-Type: image/jpeg
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=de2a22cdd3ed939398e0a56f41ce0e4a31573648400; expires=T
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=86400
Expires: Thu, 31 Dec 2020 20:00:00 GMT
CF-Cache-Status: HIT
Age: 51062
Server: cloudflare
CF-RAY: 5350c608682a957e-IAD
- Headers were followed by a body containing 40,473 bytes of binary data.
Full Example
This example reads integer values, computes, the sum, sends sum back to client.
- The client can send a
quitmessage
Server Code:
int main(int argc, char *argv[]) {
if (argc != 2) { fatal("Usage: ./arithserver <port>"); }
int server_fd = open_listenfd(argv[1]);
if (server_fd < 0) { fatal("Couldn't open server socket\n"); }
int keep_going = 1;
while (keep_going) {
int client_fd = Accept(server_fd, NULL, NULL);
if (client_fd > 0) {
keep_going = chat_with_client(client_fd);
close(client_fd); // close the connection
}
}
close(server_fd); // close server socket
return 0;
}
- It usees
open_listenfdto create a server socket - In the main loop: we call
Acceptto wait for client to connect, then we callchat_with_clientto read and decode request, do computation, and then send response back to cliet chat_with_clientcan return 0 to end the loop and shut down the server
Server chat_with_client function:
int chat_with_client(int client_fd) {
rio_t rio; int sum = 0, val;
rio_readinitb(&rio, client_fd);
// Read line from client
char buf[1024];
ssize_t rc = rio_readlineb(&rio, buf, sizeof(buf));
if (rc < 0) { return 1; } // error reading data from client
if (strcmp(buf, "quit\n") == 0 || strcmp(buf, "quit\r\n") == 0) {
return 0;
} else {
FILE *in = fmemopen(buf, (size_t) rc, "r");
while (fscanf(in, "%d", &val) == 1) { sum += val; }
fclose(in);
snprintf(buf, sizeof(buf), "Sum is %d\n", sum);
rio_writen(client_fd, buf, strlen(buf));
return 1;
}
}
- Uses
rio_tobject andrio("robust I/O") functions for I/O - buffered reads (e.g.,
rio_readlinebto read a complete input line). - It reads message from client, scans for integer values, and sends sum as response.
- More relistic implementation would have a loop to allow clients to send multiple requests.
Test the server with telnet:
Run with ./arithserver 40000
Test with telnet localhost 40000
Client implementation
int main(int argc, char *argv[]) {
if (argc != 4) { fatal("Usage: ./arithclient <hostname> <port> <message>"); }
int fd = open_clientfd(argv[1], argv[2]);
if (fd < 0) { fatal("Couldn't connect to server"); }
rio_writen(fd, argv[3], strlen(argv[3])); // send message to server
rio_writen(fd, "\n", 1);
rio_t rio; // read response from server
rio_readinitb(&rio, fd);
char buf[1000];
ssize_t n = rio_readlineb(&rio, buf, sizeof(buf));
if (n > 0) { // print response
printf("Received from server: %s", buf);
}
close(fd);
return 0;
}
open_clientfdconnects to serverrio_writensends data to serverrio_readinitbrecieve response from server- Received data is not NUL-terminated
Linked from Computer System Fundamentals