poll() function complete

This commit is contained in:
2024-09-15 16:50:55 +05:30
parent bbdbb22c8e
commit 18d72e6d59
3 changed files with 346 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
#include <stdio.h>
#include <poll.h>
int main(){
struct pollfd pfds[1]; // More if you want to monitor more
pfds[0].fd = 0; //Standard input
pfds[0].events = POLLIN;// Tell me when ready to read
// If you needed to monitor other things, as well:
// pfds[1].fd = some_socket; // Some socket descriptor
// pfds[1].events = POLLIN; // Tell me when ready to read
printf("Hit RETURN or wait 2.5 seconds for timeout\n");
int num_events = poll(pfds, 1, 2500); // 2.5 second timeout
if(num_events == 0){
printf("Poll timed out!\n");
}
else{
int pollin_happened = pfds[0].revents & POLLIN;
if( pollin_happened ){
printf("File descriptor %d is ready to read\n", pfds[0].fd);
}
else{
printf("Unexpected event occured: %d\n", pfds[0].revents);
}
}
return 0;
}

View File

@@ -0,0 +1,215 @@
/*
** pollserver.c -- a multiperson chat server
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <poll.h>
#define PORT "9034" // Port we're listening on
// Get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if( sa->sa_family == AF_INET){
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
// Return a listening socket
int get_listener_socket(void){
int listener; // Listening socket descriptor
int yes=1; // For setsockopt() SO_REUSEADDR, below
int rv;
struct addrinfo hints, *ai, *p;
// Get use a socket and bind it
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if( (rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0){
fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
exit(1);
}
for(p = ai; p != NULL; p = p->ai_next){
listener = socket( p->ai_family, p->ai_socktype, p->ai_protocol);
if( listener < 0 ){
continue;
}
// Lose the pesky "address already in use" error message
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if(bind(listener, p->ai_addr, p->ai_addrlen) < 0){
close(listener);
continue;
}
break;
}
freeaddrinfo(ai); // All done with this
// If we got here, it means we didn't get bound
if( p == NULL ){
return -1;
}
// Listener
if(listen(listener, 10) == -1){
return -1;
}
return listener;
}
// Add a new file descriptor to the set
void add_to_pfds(struct pollfd *pfds[], int newfd, int *fd_count, int *fd_size){
// If we don't have room, add more space in the pfds array
if(*fd_count == *fd_size){
*fd_size *= 2; // Double it
*pfds = realloc(*pfds, sizeof(**pfds) * (*fd_size));
}
(*pfds)[*fd_count].fd = newfd;
(*pfds)[*fd_count].events = POLLIN; // Check ready-to-read
(*fd_count)++;
}
// Remove an index from the set
void del_from_pfds(struct pollfd pfds[], int i, int *fd_count){
// Copy the one from the end over this one
pfds[i] = pfds[*fd_count - 1];
(*fd_count)--;
}
// Main
int main(void){
int listener; // Listening socket descriptor
int newfd; // Newly accept()ed socket descriptor
struct sockaddr_storage remoteaddr; // Client address
socklen_t addrlen;
char buf[256]; // Buffer for client data
char remoteIP[INET6_ADDRSTRLEN];
// Start off with room for 5 connections
// (We'll realloc as necessary)
int fd_count = 0;
int fd_size = 5;
struct pollfd *pfds = malloc(sizeof *pfds * fd_size);
// Set up and get a listening socket
listener = get_listener_socket();
if(listener == -1){
fprintf(stderr, "error getting listening socket\n");
exit(1);
}
// Add the listener to set
pfds[0].fd = listener;
pfds[0].events = POLLIN; // Report ready to read on incoming connection
fd_count = 1; // For the listener
// Main loop
for(;;){
int poll_count = poll(pfds, fd_count, -1);
if( poll_count == -1){
perror("poll");
exit(1);
}
// Run through the existing connections looking for data to read
for(int i = 0; i < fd_count; i++){
// Check if someone's ready to read
if( pfds[i].revents & POLLIN){
// We get one !!
if(pfds[i].fd == listener){
// If listener is ready to read, handle new connection
addrlen = sizeof remoteaddr;
newfd = accept(listener,
(struct sockaddr *)&remoteaddr,
&addrlen);
if( newfd == -1){
perror("accept");
}
else{
add_to_pfds(&pfds, newfd, &fd_count, &fd_size);
printf("pollserver: new connection from %s on socket %d\n",
inet_ntop(remoteaddr.ss_family,
get_in_addr((struct sockaddr *)&remoteaddr),
remoteIP, INET6_ADDRSTRLEN), newfd);
}
}
else{
// If not the listener, we're just a regular client
int nbytes = recv(pfds[i].fd, buf, sizeof buf, 0);
int sender_fd = pfds[i].fd;
if( nbytes <= 0){
// Got error or connection closed by client
if( nbytes == 0){
// Connection closed
printf("pollserver: socket %d hung up\n", sender_fd);
}
else{
perror("recv");
}
close(pfds[i].fd); // Bye!
del_from_pfds( pfds, i, &fd_count);
}
else{
// We got some good data from a client
for( int j = 0; j < fd_count ; j++ ){
// Send to everyone!
int dest_fd = pfds[j].fd;
// Except the listener and ourselves
if( dest_fd != listener && dest_fd != sender_fd){
if(send(dest_fd, buf, nbytes, 0) == -1){
perror("send");
}
}
}
}
} // END handle data from client
} // END got ready-to-read from poll()
} // END looping through file descriptor
}
return 0;
}

View File

@@ -0,0 +1,98 @@
# Advanced Topics
These are some advance technique and functionality, which quite used in real world network programming in C in Unix environment.
## Blocking :-
Here, "block" is same as "sleep", in technical terms. Many functions we have discussed so far, some of them are blocking function call. For example, recvfrom() will be in blocking state until some data arrives.
Some of the functions that blocks are :
* recvfrom()
* accept()
* socket()
If you want socket to be non-blocking :
```
#include <unistd.h>
#include <fcntl.h>
sockfd = socket(PF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
```
Setting a socket to non-blocking, leads to "polling" socket for information. If we try reading from non-blocking socket and there's no data there, it's not allowed to block it will return -1 and `errno` will be set to `EAGAIN` or `EWOULDBLOCK`.
> [!NOTE]
> The return value `EAGAIN` or `EWOULDBLOCK` is implementation dependent
> and we should check both for portability.
However, this way of polling is not a great idea because it will be in constant busy-wait state and will be sucking out CPU time. However, the solution to this is using of `poll()`.
## poll() --- Synchronous I/O Multiplexing
* Purpose :
It is used to monitor a *bunch* of sockets at once and then handle the ones that have data ready.
> [!WARNING]
> *poll()* is horribly slow when it comes to a giant number of connections.
> In those circumstances, *libevent* will give better performance. It's a
> event notification library.
* Function Prototype :
```
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
```
1. fds[] :
Type : `struct pollfd`
Value : Array of file descriptor stored in *struct pollfd*.
Purpose : Each element store information about a particular fd, with *bitmap* of events we're interested in and also contain another buffer *bitmap* to stores the events that has occured.
2. nfds :
Type : `int`
Value : Count of releavent fds.
Purpose : To keep the count of releavent fds and discard the rest.
3. timeout :
Type : `int`
Value : timeout in millisecond.
Purpose : Time period to monitor this fds and let the process go to sleep, giving all the dirty work to the OS.
* Returns :
returns the number of elements in the array that have had an event occur.
After the poll() returns, check the `revents` field to see if POLLIN or POLLOUT is set, indicating the event occurred.
> [!NOTE]
> Structure of `struct pollfd`
>
> ```
> struct pollfd{
> int fd; // the socket descriptor
> short events; // bitmap of events we're interested in
> short revents; // when poll() returns, bitmap of events that occured.
> }
> ```
>
> The events field is the bitwise-OR of the following:
>
> POLLIN - Alert me when data is ready to recv() on this socket.
>
> POLLOUT - Alert me when I can send() data to this socket without blocking.