Created
December 9, 2020 01:12
-
-
Save influentcoder/35ea175e90c8ff44454eefc4a581efad to your computer and use it in GitHub Desktop.
Echo TCP Server With Epoll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from datetime import datetime | |
import socket | |
import sys | |
import time | |
def run(): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.connect(('localhost', 4444)) | |
msg = "[{}] Hello world!!".format(str(datetime.now())).encode("UTF-8") | |
print("Sending: " + str(msg)) | |
sock.sendall(msg) | |
received = 0 | |
data = b"" | |
while received < len(msg): | |
buff = sock.recv(16) | |
received += len(buff) | |
data += buff | |
print("Received: " + str(data)) | |
sock.close() | |
if __name__ == "__main__": | |
run() | |
time.sleep(1) | |
run() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <stdio.h> | |
#include <sys/epoll.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <unistd.h> | |
// So we can use accept4() | |
#define _GNU_SOURCE 1 | |
int main() | |
{ | |
int port = 4444; | |
/** | |
* Create a socket in the kernel, and get a file descriptor back so we can refer to | |
* it later. | |
*/ | |
// Internet socket, that binds to a port (as opposed to, for example, a Unix domain | |
//socket which binds to a file). | |
int socketDomain = AF_INET; | |
// In our case, TCP protocol, as opposed to SOCK_DGRAM which is UDP. | |
int socketType = SOCK_STREAM; | |
// No need to specify in our case, with SOCK_STREAM the TCP protocol is | |
// automatically picked up. | |
int protocol = 0; | |
int socketFd = socket(socketDomain, socketType, protocol); | |
if (socketFd == -1) { | |
perror("Error when creating a socket"); | |
exit(EXIT_FAILURE); | |
} | |
/** | |
* Bind the socket to the port. | |
*/ | |
struct sockaddr_in sockAddr; | |
sockAddr.sin_family = AF_INET; | |
sockAddr.sin_port = htons(port); // htons => convert to network-byte order | |
sockAddr.sin_addr.s_addr = INADDR_ANY; | |
if (bind(socketFd, (struct sockaddr *) &sockAddr, sizeof(sockAddr)) < 0) { | |
perror("Error on binding"); | |
exit(EXIT_FAILURE); | |
} | |
/** | |
* Set the socket in listening mode so it can accept connections. | |
*/ | |
// Backlog of unprocessed data that the socket can accept before sending an error to | |
// the client. | |
int backlog = 4096; | |
if (listen(socketFd, backlog) < 0) { | |
perror("Error on listen"); | |
exit(EXIT_FAILURE); | |
} | |
/** | |
* Create a new "epoll" instance in the kernel. | |
* We interact using this epoll instance using its file descriptor. | |
*/ | |
int epollFlags = 0; | |
int epollFd = epoll_create1(epollFlags); | |
if (epollFd == -1) { | |
perror("Error when creating epoll FD"); | |
exit(EXIT_FAILURE); | |
} | |
/** | |
* Register an interest on the listening socket, so that we are notified when data | |
* comes in. | |
*/ | |
int op = EPOLL_CTL_ADD; // We want to register interest on a file descriptor. | |
struct epoll_event ev; | |
ev.events = EPOLLIN; // For read operations. | |
ev.data.fd = socketFd; | |
if (epoll_ctl(epollFd, op, socketFd, &ev) == -1) { | |
perror("Error on epoll_ctl"); | |
exit(EXIT_FAILURE); | |
} | |
printf("Listening on %d...\n", port); | |
int n, nfds, connSockFd, readSize, sentSize, readBufSize = 1024, timeout = -1, | |
maxEvents = 10; | |
int readBuf[readBufSize]; | |
struct epoll_event events[maxEvents]; | |
for (;;) { | |
// This is a blocking call. We have registered interests in listening in some | |
// file descriptors, and when data is available, this will return the number of | |
// file descriptors having available data. The file descriptors themselves are | |
// populated in "events". | |
nfds = epoll_wait(epollFd, events, maxEvents, timeout); | |
if (nfds == -1) { | |
perror("Error on epoll_wait"); | |
exit(EXIT_FAILURE); | |
} | |
// Process each file descriptor having available data. | |
for (n = 0; n < nfds; ++n) { | |
// If the data is available on the listening socket, this means we have a | |
// new incoming connection with a client. We then accept the connection, | |
// which creates a dedicated socket to talk to this client. We then need to | |
// register this dedicated socket in epoll as we are interested to see when | |
// data is available, i.e. the client sent some data on the socket. | |
if (events[n].data.fd == socketFd) { | |
connSockFd = accept4(socketFd, NULL, 0, SOCK_NONBLOCK); | |
if (connSockFd == -1) { | |
perror("Error on accept"); | |
exit(EXIT_FAILURE); | |
} | |
ev.data.fd = connSockFd; | |
// Register interest on the dedicated socket. | |
if (epoll_ctl(epollFd, op, connSockFd, &ev) == -1) { | |
perror("Error on epoll_ctl"); | |
exit(EXIT_FAILURE); | |
} | |
} else { | |
// If the file descriptor is not the listening socket, it must be a | |
// dedicated socket for a specific client. | |
// Read the data from the socket, up to "readBufSize" bytes, and store | |
// the data in "readBuf". The number of bytes read is stored in | |
// "readSize". | |
readSize = read(events[n].data.fd, readBuf, readBufSize); | |
if (readSize == -1) { | |
perror("Error on read"); | |
exit(EXIT_FAILURE); | |
} | |
// Send the data to the same socket, i.e. back to the client. We send | |
// the same read buffer, and readSize bytes. | |
sentSize = send(events[n].data.fd, readBuf, readSize, MSG_NOSIGNAL); | |
if (sentSize == -1) { | |
perror("Error on send"); | |
exit(EXIT_FAILURE); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment