pserv/src/socket.c
Godmar Back 7cbe3af927 updates for summer 2020
this update adds the unit tests as part of the student distribution
2020-08-03 23:33:16 -04:00

158 lines
4.8 KiB
C

/*
* Support functions for dealing with sockets.
*
* Note: these functions cannot be used out of the box.
* In particular, support for protocol independent programming
* is not fully implemented. See below.
*
* Written by G. Back for CS 3214 Spring 2018.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "socket.h"
#include "main.h"
/*
* Find a suitable IPv4 address to bind to, create a socket, bind it,
* invoke listen to get the socket ready for accepting clients.
*
* This function does not implement proper support for protocol-independent/
* dual-stack binding. Adding this is part of the assignment.
*
* Returns -1 on error, setting errno.
* Returns socket file descriptor otherwise.
*/
int
socket_open_bind_listen(char * port_number_string, int backlog)
{
struct addrinfo *info, *pinfo;
struct addrinfo hint;
memset(&hint, 0, sizeof hint);
hint.ai_flags = AI_PASSIVE | // we're looking for an address to bind to
AI_NUMERICSERV; // service port is numeric, don't look in /etc/services
hint.ai_protocol = IPPROTO_TCP; // only interested in TCP
int rc = getaddrinfo(NULL, port_number_string, &hint, &info);
if (rc != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
return -1;
}
char printed_addr[1024];
for (pinfo = info; pinfo; pinfo = pinfo->ai_next) {
assert (pinfo->ai_protocol == IPPROTO_TCP);
int rc = getnameinfo(pinfo->ai_addr, pinfo->ai_addrlen,
printed_addr, sizeof printed_addr, NULL, 0,
NI_NUMERICHOST);
if (rc != 0) {
fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(rc));
return -1;
}
/* Uncomment this to see the address returned
printf("%s: %s\n", pinfo->ai_family == AF_INET ? "AF_INET" :
pinfo->ai_family == AF_INET6 ? "AF_INET6" : "?",
printed_addr);
*/
/* Skip any non-IPv4 addresses.
* Adding support for protocol independence/IPv6 is part of the project.
*/
if (pinfo->ai_family != AF_INET)
continue;
int s = socket(pinfo->ai_family, pinfo->ai_socktype, pinfo->ai_protocol);
if (s == -1) {
perror("socket");
return -1;
}
// See https://stackoverflow.com/a/3233022 for a good explanation of what this does
int opt = 1;
setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
rc = bind(s, pinfo->ai_addr, pinfo->ai_addrlen);
if (rc == -1) {
perror("bind");
close(s);
return -1;
}
rc = listen(s, backlog);
if (rc == -1) {
perror("listen");
close(s);
return -1;
}
freeaddrinfo(info);
return s;
}
fprintf(stderr, "No suitable address to bind port %s found\n", port_number_string);
return -1;
}
/**
* Accept a client, blocking if necessary.
*
* Returns file descriptor of client accepted on success, returns
* -1 on error.
*/
int
socket_accept_client(int accepting_socket)
{
/* The address passed into accept must be large enough for either IPv4 & IPv6.
* Using a struct sockaddr is too small to hold a full IPv6 address and accept()
* would not return the full address.
*/
struct sockaddr_storage peer;
socklen_t peersize = sizeof(peer);
int client = accept(accepting_socket, (struct sockaddr *) &peer, &peersize);
if (client == -1) {
perror("accept");
return -1;
}
/* Performance tuning. Turn off Nagle's algorithm.
* Otherwise, when the servers sends a reply to the client, and the reply
* is small relative to the MSS size, there would be a delay of 40ms
* during which the OS would hope in vain for more data to be sent.
* See tcp(7)
*/
int i = 1;
setsockopt(client, IPPROTO_TCP, TCP_NODELAY, (void *)&i, sizeof(i));
/* The following will help with debugging your server.
* Adjust and/or remove as you see fit.
*/
if (!silent_mode) {
char peer_addr[1024], peer_port[10];
int rc = getnameinfo((struct sockaddr *) &peer, peersize,
peer_addr, sizeof peer_addr, peer_port, sizeof peer_port,
NI_NUMERICHOST | NI_NUMERICSERV);
if (rc != 0) {
fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(rc));
return -1;
}
fprintf(stderr, "Accepted connection from %s:%s\n", peer_addr, peer_port);
}
return client;
}