|
// server.c |
|
|
|
#include "server.h" |
|
|
|
#include <fcntl.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#ifdef _WIN32 |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#include <mswsock.h> |
|
#ifdef _MSC_VER |
|
#pragma comment(lib, "ws2_32.lib") |
|
#endif |
|
#include <io.h> // _open_osfhandle |
|
typedef int socklen_t; |
|
#else // unix |
|
#include <sys/socket.h> |
|
#include <sys/types.h> |
|
#include <arpa/inet.h> |
|
#include <netdb.h> |
|
#include <unistd.h> |
|
#define SOCKET_ERROR (-1) |
|
#define INVALID_SOCKET ((SOCKET)(~0)) |
|
#define closesocket(fd) close(fd) |
|
#endif |
|
|
|
#define MAX_BACKLOG 5 |
|
|
|
void socket_startup() |
|
{ |
|
#ifdef _WIN32 |
|
WSADATA wsaData; |
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { |
|
WSACleanup(); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
int opt = SO_SYNCHRONOUS_NONALERT; |
|
setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt, sizeof(opt)); |
|
#endif |
|
} |
|
|
|
void socket_cleanup() |
|
{ |
|
#ifdef _WIN32 |
|
WSACleanup(); |
|
#endif |
|
} |
|
|
|
static char *sockaddr_info(const struct sockaddr *sa) |
|
{ |
|
static char buf[NI_MAXHOST + NI_MAXSERV + 2] = {0}; // "HOST:PORT\0" |
|
char hostname[NI_MAXHOST] = {0}; |
|
char servname[NI_MAXSERV] = {0}; |
|
size_t addrlen; |
|
int rc; |
|
|
|
if (sa->sa_family == AF_INET) |
|
addrlen = sizeof(struct sockaddr_in); |
|
else // AF_INET6 |
|
addrlen = sizeof(struct sockaddr_in6); |
|
|
|
rc = getnameinfo(sa, addrlen, |
|
hostname, sizeof(hostname), |
|
servname, sizeof(servname), |
|
NI_NUMERICHOST | NI_NUMERICSERV); |
|
if (rc != 0) { |
|
debug("getnameinfo: %s\n", gai_strerror(rc)); |
|
return NULL; |
|
} |
|
|
|
snprintf(buf, sizeof(buf), "%s:%s", hostname, servname); |
|
|
|
return buf; |
|
} |
|
|
|
int tcp_listen(const char *nodename, const char *service) |
|
{ |
|
struct addrinfo hints; |
|
struct addrinfo *ans = NULL; |
|
int sockfd, err; |
|
|
|
memset(&hints, 0, sizeof(hints)); |
|
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ |
|
hints.ai_flags = AI_PASSIVE; |
|
hints.ai_socktype = SOCK_STREAM; |
|
|
|
err = getaddrinfo(nodename, service, &hints, &ans); |
|
if (err != 0) { |
|
debug("getaddrinfo: %s\n", gai_strerror(err)); |
|
return -1; |
|
} |
|
|
|
sockfd = socket(ans->ai_family, ans->ai_socktype, ans->ai_protocol); |
|
if (sockfd == -1) { |
|
perror("socket"); |
|
return -1; |
|
} |
|
|
|
int yes = 1; |
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&yes, sizeof(yes)); |
|
|
|
if (bind(sockfd, ans->ai_addr, ans->ai_addrlen) == -1) { |
|
perror("bind"); |
|
return -1; |
|
} |
|
if (listen(sockfd, MAX_BACKLOG) == -1) { |
|
perror("listen"); |
|
return -1; |
|
} |
|
|
|
debug("listening %s.\n", sockaddr_info(ans->ai_addr)); |
|
|
|
freeaddrinfo(ans); |
|
|
|
return sockfd; |
|
} |
|
|
|
void start_server(char *service, void (*request_handler)(FILE *, FILE *)) |
|
{ |
|
socket_startup(); |
|
|
|
int listen_socket = tcp_listen(NULL, service); |
|
if (listen_socket == INVALID_SOCKET) { |
|
return; |
|
} |
|
|
|
for (;;) { |
|
struct sockaddr_storage from; |
|
socklen_t fromlen = sizeof(from); |
|
|
|
int accept_socket = accept(listen_socket, (struct sockaddr *)&from, &fromlen); |
|
if (accept_socket == INVALID_SOCKET) { |
|
perror("accept"); |
|
return; |
|
} |
|
|
|
debug("accept %s.\n", sockaddr_info((struct sockaddr *)&from)); |
|
|
|
FILE *r, *w; |
|
#ifdef _WIN32 |
|
int h = _open_osfhandle(accept_socket, _O_RDONLY|_O_BINARY); |
|
r = _fdopen(h, "rb"); |
|
w = _fdopen(h, "wb"); |
|
#else // unix |
|
r = fdopen(accept_socket, "rb"); |
|
w = fdopen(accept_socket, "wb"); |
|
#endif |
|
if (r == NULL || w == NULL) { |
|
perror("fdopen"); |
|
return; |
|
} |
|
|
|
request_handler(r, w); |
|
|
|
shutdown(accept_socket, 2); // SHUT_RDWR |
|
fclose(r); |
|
fclose(w); |
|
closesocket(accept_socket); |
|
} |
|
|
|
closesocket(listen_socket); |
|
socket_cleanup(); |
|
} |