#include <sys/socket.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
void become_daemon (void)
{
switch (fork()) { // fork off the parent process
case -1: { printf ("error: fork()\n"); exit(0); }
case 0: break;
default: exit (0);
}
umask (0); // change the file mode mask
setsid(); // creates a new session
chdir ("/"); // change the current working directory
close (STDIN_FILENO); close (STDOUT_FILENO); close (STDERR_FILENO); // close out the standard file descriptors
}
void tcp_keepalive(int tcp_fd)
{
// keepalive probe packets: no data and ACK set tcp packets
int keepAlive = 1;
if (setsockopt (tcp_fd,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1) { syslog(LOG_INFO, "error: setsockopt() SO_KEEPALIVE\n"); exit (0); }
int keepIdle = 1; // The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes
if (setsockopt (tcp_fd,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1) { syslog(LOG_INFO, "error: setsockopt() TCP_KEEPIDLE\n"); exit(0); }
int keepInterval = 1; // The time (in seconds) between individual keepalive probes
if (setsockopt (tcp_fd,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1) { syslog(LOG_INFO, "error: setsockopt() TCP_KEEPINTVL\n"); exit(0); }
int keepCount = 3; // The maximum number of keepalive probes TCP should send before dropping the connection
if (setsockopt (tcp_fd,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1) { syslog(LOG_INFO, "error: setsockopt() TCP_KEEPCNT\n"); exit(0); }
}
void close_clean(int fd)
{
// clean
close (fd);
return;
}
void do_use_fd (int fd, unsigned char fin_flag)
{
#define BUF_SIZE 1500
unsigned char buf[BUF_SIZE+1]; bzero (buf, BUF_SIZE+1);
int len;
len = read (fd, buf, BUF_SIZE);
if (len == -1) { // rst
shutdown(fd, SHUT_RDWR); close_clean (fd);
return;
}
if (len == 0) { // fin
shutdown(fd, SHUT_RDWR); close_clean (fd);
return;
}
while (len > 0) {
// rx data hander begin
if (sendto(fd, buf, len, MSG_DONTWAIT, NULL, 0) == -1) {
if((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
shutdown(fd, SHUT_RDWR); close_clean (fd); return;
}
syslog(LOG_INFO,"error: sendto() %d\n", errno); exit (0);
}
// rx data hander end
len = -1;
len = read (fd, buf, BUF_SIZE);
if (len == -1) {
if((errno == EAGAIN) || (errno == EWOULDBLOCK)) { // Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading
break;
} else { syslog(LOG_INFO,"error: read() errno != EAGAIN\n"); exit (0); }
} else if (len == 0) {
fin_flag = 1;
}
}
if (fin_flag > 0) { // fin
shutdown(fd, SHUT_RDWR); close_clean (fd);
return;
}
return;
}
int main ()
{
become_daemon ();
int listen_fd;
if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { syslog(LOG_INFO,"error: socket()\n"); exit (0); }
struct sockaddr_in acceptAddress;
acceptAddress.sin_family = PF_INET;
acceptAddress.sin_addr.s_addr = htonl (INADDR_ANY);
#define LISTEN_PORT 2000
acceptAddress.sin_port = htons (LISTEN_PORT);
if (bind (listen_fd, (struct sockaddr *)&acceptAddress, sizeof(struct sockaddr)) != 0) { syslog(LOG_INFO,"error: bind()\n"); exit (0); }
listen (listen_fd, 10000);
tcp_keepalive(listen_fd);
int flag = 1;
if (setsockopt(listen_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)) == -1) { syslog(LOG_INFO,"error: setsockopt(TCP_NODELAY)\n"); exit(0); } // Disable the Nagle (TCP No Delay) algorithm
signal(SIGPIPE, SIG_IGN);
int epoll_fd;
epoll_fd = epoll_create(1);
struct epoll_event ev, *events;
#define MAX_EVENTS 100
events = (struct epoll_event *) malloc(MAX_EVENTS*sizeof(struct epoll_event));
bzero (events, MAX_EVENTS*sizeof(struct epoll_event));
/*
syn --> EPOLLIN
data --> EPOLLIN
fin --> EPOLLIN|EPOLLRDHUP read() return len(rx_buf)
rst --> EPOLLIN read() return -1
*/
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
ev.data.fd = listen_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) != 0) { syslog(LOG_INFO,"error: epoll_ctl() listen_fd\n"); exit(0); }
for(;;) {
int n_fds;
n_fds = epoll_wait (epoll_fd, events, MAX_EVENTS, -1);
int i;
for(i=0; i<n_fds; i++) {
if (events[i].events & (EPOLLERR | EPOLLHUP)) { // error
if (events[i].data.fd == listen_fd) { syslog(LOG_INFO,"error: listen_fd\n"); exit(0); }
close_clean (events[i].data.fd); // close(fd) cause fd to be removed from all epoll sets automatically
} else if (events[i].events & EPOLLRDHUP) { // fin
do_use_fd(events[i].data.fd, 1);
} else if (events[i].events & EPOLLIN) {
if (events[i].data.fd == listen_fd) { // syn
struct sockaddr_in clientAddr;
bzero(&clientAddr, sizeof(struct sockaddr_in));
int client_fd, len;
len = sizeof (struct sockaddr_in);
client_fd = accept (listen_fd, (struct sockaddr *)&clientAddr, (socklen_t *)&len); // accept() creates a new connected socket file descriptor
if(client_fd < 0) { continue; }
if (fcntl(client_fd, F_SETFL, O_NONBLOCK) != 0) { syslog(LOG_INFO,"error: fcntl(). exit\n"); exit (0); } // set client_fd nonblocking
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) { syslog(LOG_INFO,"error: epoll_ctl\n"); exit(0); }
} else { do_use_fd(events[i].data.fd, 0); } // data | rst
} else { syslog(LOG_INFO,"unknown event\n"); exit(0); }
}
}
close (listen_fd);
close (epoll_fd);
free (events);
return 0;
}