Related
I have a program running on a QEMU VM. The program running inside this VM gets notified by a program on the host via interrupts and using QEMU ivshmem. The program on the host creates an eventfd and sends this file descriptor to QEMU when the VM starts. The program in the guest then opens a VFIO group device and sets an interrupt request fd on this device. We can then add the interrupt fd to epoll and epoll_wait to wait for notifications from the host.
The thing is that I want a 1-1 matching between the times the host writes to the eventfd and the number of events that are signaled in epoll_wait. For this I decided to use EFD_SEMAPHORE for the evenfds on the host and the guest. From my understanding, every time I write an 8 byte integer with value 1, the eventfd_counter is incremented by 1. Then every time the eventfd is read, the counter is decremented by 1 (different from a regular eventfd where each read clears the whole counter). For some reason, I am not getting the desired behaviour, so I was wondering if either eventfds with the EFD_SEMAPHORE flags are not properly supported by VFIO or QEMUs ivshmem.
Below is a simplified version of the parts I think are relevant and how I setup the notification system. I hope the code below is not too verbose. I tried to reduce the number of irrelevant parts (there is too much other code in the middle that is not particularly relevant to the problem) but not 100% sure what might be relevant or not.
Code host uses to signal guest
int ivshmem_uxsocket_send_int(int fd, int64_t i)
{
int n;
struct iovec iov = {
.iov_base = &i,
.iov_len = sizeof(i),
};
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = NULL,
.msg_controllen = 0,
.msg_flags = 0,
};
if ((n = sendmsg(fd, &msg, 0)) != sizeof(int64_t))
{
return -1;
}
return n;
}
int ivshmem_uxsocket_sendfd(int uxfd, int fd, int64_t i)
{
int n;
struct cmsghdr *chdr;
/* Need to pass at least one byte of data to send control data */
struct iovec iov = {
.iov_base = &i,
.iov_len = sizeof(i),
};
/* Allocate a char array but use a union to ensure that it
is aligned properly */
union {
char buf[CMSG_SPACE(sizeof(fd))];
struct cmsghdr align;
} cmsg;
memset(&cmsg, 0, sizeof(cmsg));
/* Add control data (file descriptor) to msg */
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = sizeof(cmsg),
.msg_flags = 0,
};
/* Set message header to describe ancillary data */
chdr = CMSG_FIRSTHDR(&msg);
chdr->cmsg_level = SOL_SOCKET;
chdr->cmsg_type = SCM_RIGHTS;
chdr->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(chdr), &fd, sizeof(fd));
if ((n = sendmsg(uxfd, &msg, 0)) != sizeof(i))
{
return -1;
}
return n;
}
/* SETUP IVSHMEM WITH QEMU AND PASS THE EVENTFD USED TO
NOTIFY THE GUEST */
int ivshmem_uxsocket_accept()
{
int ret;
int cfd, ifd, nfd;
int64_t version = IVSHMEM_PROTOCOL_VERSION;
uint64_t hostid = HOST_PEERID;
int vmid = 0
/* Accept connection from qemu ivshmem */
if ((cfd = accept(uxfd, NULL, NULL)) < 0)
{
return -1;
}
/* Send protocol version as required by qemu ivshmem */
ret = ivshmem_uxsocket_send_int(cfd, version);
if (ret < 0)
{
return -1;
}
/* Send vm id to qemu */
ret = ivshmem_uxsocket_send_int(cfd, vmid);
if (ret < 0)
{
return -1;
}
/* Send shared memory fd to qemu */
ret = ivshmem_uxsocket_sendfd(cfd, shm_fd, -1);
if (ret < 0)
{
return -1;
}
/* Eventfd used by guest to notify host */
if ((nfd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
/* Ivshmem protocol requires to send host id
with the notify fd */
ret = ivshmem_uxsocket_sendfd(cfd, nfd, hostid);
if (ret < 0)
{
return -1;
}
/* THIS IS THE EVENTFD OF INTEREST TO US: USED BY HOST
TO NOTIFY GUEST */
if ((ifd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
ret = ivshmem_uxsocket_sendfd(cfd, ifd, vmid);
if (ret < 0)
{
return -1;
}
if (epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev) < 0)
{
return -1;
}
return 0;
}
/* NOW EVERY TIME WE WANT TO NOTIFY THE GUEST
WE CALL THE FOLLOWING FUNCTION */
int notify_guest(int fd)
{
int ret;
uint64_t buf = 1;
ret = write(fd, &buf, sizeof(uint64_t));
if (ret < sizeof(uint64_t))
{
return -1;
}
return 0;
}
Code guest uses to receive notifications from host
/* THIS FUNCTION SETS THE IRQ THAT RECEIVES THE
NOTIFICATIONS FROM THE HOST */
int vfio_set_irq(int dev)
{
int fd;
struct vfio_irq_set *irq_set;
char buf[sizeof(struct vfio_irq_set) + sizeof(int)];
if ((fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
irq_set = (struct vfio_irq_set *) buf;
irq_set->argsz = sizeof(buf);
irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set->index = 2;
irq_set->start = 0;
irq_set->count = 1;
memcpy(&irq_set->data, &fd, sizeof(int));
if (ioctl(dev, VFIO_DEVICE_SET_IRQS, irq_set) < 0)
{
return -1;
}
return irq_fd;
}
/* The guest sets up the ivshmem region from QEMU and sets the
interrupt request. */
int vfio_init()
{
int cont, group, irq_fd;
struct epoll_event ev;
struct vfio_group_status g_status = { .argsz = sizeof(g_status) };
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
/* Create vfio container */
if ((cont = open("/dev/vfio/vfio", O_RDWR)) < 0)
{
return -1;
}
/* Check API version of container */
if (ioctl(cont, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
{
return -1;
}
if (!ioctl(cont, VFIO_CHECK_EXTENSION, VFIO_NOIOMMU_IOMMU))
{
return -1;
}
/* Open the vfio group */
if((group = open(VFIO_GROUP, O_RDWR)) < 0)
{
return -1;
}
/* Test if group is viable and available */
ioctl(group, VFIO_GROUP_GET_STATUS, &g_status);
if (!(g_status.flags & VFIO_GROUP_FLAGS_VIABLE))
{
return -1;
}
/* Add group to container */
if (ioctl(group, VFIO_GROUP_SET_CONTAINER, &cont) < 0)
{
return -1;
}
/* Enable desired IOMMU model */
if (ioctl(cont, VFIO_SET_IOMMU, VFIO_NOIOMMU_IOMMU) < 0)
{
return -1;
}
/* Get file descriptor for device */
if ((dev = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, VFIO_PCI_DEV)) < 0)
{
return -1;
}
/* Get device info */
if (ioctl(dev, VFIO_DEVICE_GET_INFO, &device_info) < 0)
{
return -1;
}
/* Set interrupt request fd */
if ((irq_fd = vfio_set_irq(dev)) < 0)
{
return -1
}
/* Add interrupt request fd to interest list */
if (vfio_subscribe_irq() < 0)
{
return -1;
}
/* Do other shm setup stuff not related to the interrupt
request */
ev.events = EPOLLIN;
ev.data.ptr = EP_NOTIFY;
ev.data.fd = irq_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, irq_fd, &ev) != 0)
{
return -1;
}
return 0;
}
int ivshmem_drain_evfd(int fd)
{
int ret;
uint64_t buf;
ret = read(fd, &buf, sizeof(uint64_t));
if (ret == 0)
{
return -1;
}
return ret;
}
/* I should get every notification from the host here,
but it seems that not all notifications are going
through. The number of calls to notify_guest does not
match the number of events received from epoll_wait
here */
int notify_poll()
{
int i, n;
struct epoll_event evs[32];
n = epoll_wait(epfd, evs, 32, 0);
for (i = 0; i < n; i++)
{
if (evs[i].events & EPOLLIN)
{
/* Drain evfd */
drain_evfd(irq_fd);
/* Handle notification ... */
handle();
}
}
}
My single threaded HTTP Server works just fine, but I'm having trouble multithreading it. I know I am supposed to use pthreads, locks, and condition variables, but I can't get the logic set up properly. The trouble starts after listening to the server. Currently I have a struct that contains a client socket variable, a lock variable, a condition variable, and some variables necessary for parsing and storing headers. I create a struct array sized with the amount of threads, then create a pthread array sized with the amount of threads. I go into a while(1) loop which goes into a for loop and iterates through all the threads accepting each connection, calling pthread_create and passing them to my handle connections function, then closing the client socket. My handle connections then does the request handling that my single threaded http server did (reading, parsing, processing, constructing), then returns NULL. No request gets read when I run this using pthread_create, but if I run handle connections without the pthreads, it works just fine. And below I'll attach my code. Any help is appreciated
Thank you for commenting so well ...
Okay, I coded up, but not tested the changes.
Your loop is inherently single threaded, so a bit of refactoring is in order
You have to scan for an unused thread control slot after doing accept.
You have to pthread_join completed/done threads [from any prior invocations].
The thread function has to close the per-client socket [not main thread]
You need a global (file scope) mutex.
I've coded it up, but not tested it. I put #if 0 around most of what I clipped out and #if 1 around new code.
Note that number of simultaneous connections [second arg to listen], herein 5 has to be less than or equal to threadNum. Although I didn't do it, I'd just do listen(...,threadNum) instead of hardwiring it.
Here's the short code with just the relevant changes:
#if 1
pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
struct threadObject {
char method[5]; // PUT, HEAD, GET. HEAD==4 letters+null terminator
char filename[28]; // what is the file we are worried about. Max 27 ASCII characters (NULL terminated on 28)
char httpversion[9]; // HTTP/1.1
ssize_t content_length; // example: 13
uint16_t status_code; // status code for the request
char buffer[BUFFER_SIZE]; // buffer to transfer data
char rest_of_PUT[BUFFER_SIZE]; // incase client send part of PUT message in header
int client_sockd;
pthread_mutex_t *dispatch_lock;
const pthread_cond_t *job_pool_empty;
// pthread_mutex_t* log_lock;
// const pthread_cond_t* log_pool_empty;
pthread_mutex_t *read_write_lock;
pthread_cond_t *file_list_update;
// JobQueue* job_pool;
// LogQueue log_pool;
// bool is_logging;
#if 1
pthread_t tsk_threadid;
int tsk_inuse;
int tsk_done;
#endif
};
void *
handle_connections(void *ptr_thread)
{
// create a mutual exclusion to lock out any other threads from the function
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// pthread_mutex_lock(&mutex);
// operations go here
struct threadObject *thread = (struct threadObject *) ptr_thread;
// reset message after each loop
memset(thread->buffer, '\0', BUFFER_SIZE);
memset(thread->method, '\0', 5);
memset(thread->filename, '\0', 28);
memset(thread->httpversion, '\0', 9);
thread->content_length = 0;
thread->status_code = 0;
memset(thread->rest_of_PUT, '\0', BUFFER_SIZE);
// read message
if (read_http_response(thread) == true) {
// process message
process_request(thread);
}
// construct a response
construct_http_response(thread);
// unlock the function
// pthread_mutex_unlock(&mutex);
#if 1
close(thread->client_sockd);
pthread_mutex_lock(&global_mutex);
thread->tsk_done = 1;
pthread_mutex_unlock(&global_mutex);
#endif
return NULL;
}
int
main(int argc, char **argv)
{
// Create sockaddr_in with server information
if (argc < 2) {
perror("No arguments passed\n");
return -1;
}
// make sure port number is above 1024 and set the port # to it
if (atoi(argv[1]) < 1024) {
return 1;
}
char *port = argv[1];
// parse the command line args for options -l and -N. -l specifies it will use a log and the following parameter is the filename. -N specifies the number of threads it will use and the following parameter will be a number
int opt;
uint8_t threadNum = 1;
char *logName = NULL;
while ((opt = getopt(argc - 1, argv + 1, "N:l:")) != -1) {
if (opt == 'N') {
threadNum = atoi(optarg);
}
else if (opt == 'l') {
logName = optarg;
}
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t addrlen = sizeof(server_addr);
// Create server socket
int server_sockd = socket(AF_INET, SOCK_STREAM, 0);
// Need to check if server_sockd < 0, meaning an error
if (server_sockd < 0) {
perror("socket");
return 1;
}
// Configure server socket
int enable = 1;
// This allows you to avoid: 'Bind: Address Already in Use' error
int ret = setsockopt(server_sockd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
if (ret < 0) {
return EXIT_FAILURE;
}
// Bind server address to socket that is open
ret = bind(server_sockd, (struct sockaddr *) &server_addr, addrlen);
if (ret < 0) {
return EXIT_FAILURE;
}
// Listen for incoming connections
ret = listen(server_sockd, 5); // 5 should be enough, if not use SOMAXCONN
if (ret < 0) {
return EXIT_FAILURE;
}
struct threadObject thread[threadNum];
// Connecting with a client
struct sockaddr client_addr;
socklen_t client_addrlen = sizeof(client_addr);
// create a pthread array of size (number of threads). specify this will be using the handle connections function. join the threads together
#if 0
pthread_t thread_id[threadNum];
#endif
#if 1
struct threadObject *tsk = NULL;
int tskidx;
// clear out the thread structs
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
memset(tsk,0,sizeof(struct threadObject));
}
while (true) {
// accept connection
int client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
pthread_mutex_lock(&global_mutex);
// join any previously completed threads
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (tsk->tsk_done) {
pthread_join(tsk->tsk_threadid,NULL);
tsk->tsk_inuse = 0;
tsk->tsk_done = 0;
}
}
// find unused task slot
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (! tsk->tsk_inuse)
break;
}
memset(tsk,0,sizeof(struct threadObject));
tsk->client_sockd = client_sockd;
tsk->tsk_inuse = 1;
pthread_mutex_unlock(&global_mutex);
// fire in the hole ...
pthread_create(&tsk->tsk_threadid, NULL, handle_connections, tsk);
}
#endif
#if 0
for (int i = 0; i < threadNum; i++) {
printf("\n[+] server is waiting...\n");
thread[i].client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
handle_connections(&thread[i]);
// pthread_create(&thread_id[i], NULL, handle_connections, &thread[i]);
printf("Response Sent\n");
// close the current client socket
close(thread[i].client_sockd);
}
}
#endif
return EXIT_SUCCESS;
}
Here's the complete code [just in case I clipped out too much]:
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <fcntl.h>
#include <unistd.h> // write
#include <string.h> // memset
#include <stdlib.h> // atoi
#include <stdbool.h> // true, false
#include <errno.h>
#include <sys/types.h>
#include <ctype.h>
#include <pthread.h>
#define BUFFER_SIZE 4096
#if 1
pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
struct threadObject {
char method[5]; // PUT, HEAD, GET. HEAD==4 letters+null terminator
char filename[28]; // what is the file we are worried about. Max 27 ASCII characters (NULL terminated on 28)
char httpversion[9]; // HTTP/1.1
ssize_t content_length; // example: 13
uint16_t status_code; // status code for the request
char buffer[BUFFER_SIZE]; // buffer to transfer data
char rest_of_PUT[BUFFER_SIZE]; // incase client send part of PUT message in header
int client_sockd;
pthread_mutex_t *dispatch_lock;
const pthread_cond_t *job_pool_empty;
// pthread_mutex_t* log_lock;
// const pthread_cond_t* log_pool_empty;
pthread_mutex_t *read_write_lock;
pthread_cond_t *file_list_update;
// JobQueue* job_pool;
// LogQueue log_pool;
// bool is_logging;
#if 1
pthread_t tsk_threadid;
int tsk_inuse;
int tsk_done;
#endif
};
//read in the header and store it in the appropriate places
bool
read_http_response(struct threadObject *thread)
{
printf("\nThis function will take care of reading message\n");
// how many bytes we're receiving from the header. also puts the message into the buffer
ssize_t bytes = recv(thread->client_sockd, thread->buffer, BUFFER_SIZE, 0);
// if nothing or too much gets sent in the header, return
if (bytes <= 0 || bytes >= BUFFER_SIZE) {
thread->status_code = 400;
printf("Too long or nothing in here\n");
return false;
}
// NULL terminate the last spot on the buffer
thread->buffer[bytes] = '\0';
// how many bytes we received
printf("[+] received %ld bytes from client\n[+] response: \n", bytes);
printf("those bytes are: %s\n", thread->buffer);
// make a char pointer pointer to the buffer to easily traverse it and parse it into the right spots
char *traverse = thread->buffer;
// first stop. sgnals the beginning of the filename
char *file = strstr(traverse, "/");
// 2nd stop. signls the beginning of the HTTP version. only 1.1 is accepted
char *http = strstr(traverse, "HTTP/1.1");
// 3rd stop. Signals the beginning of the content length
char *contlength1 = strstr(traverse, "Content-Length");
char *chunked = strstr(traverse, "chunked");
if (chunked != NULL) {
printf("MESSAGE NOT A FILE PUT\n");
thread->status_code = 403;
return false;
}
// store the method
sscanf(traverse, "%s", thread->method);
printf("method:%s\n", thread->method);
// if its not 1 of the 3 valid requests, throw 400 error
if (strcmp(thread->method, "GET") != 0 &&
strcmp(thread->method, "PUT") != 0 &&
strcmp(thread->method, "HEAD") != 0) {
thread->status_code = 400;
printf("Invalid Method:%s\n", thread->method);
return false;
}
// if the filename doesnt start with /, its invalid throw 400 error
if (*file != '/') {
thread->status_code = 400;
printf("bad filename\n");
return false;
}
// only store the filename portion after the required /
traverse = file + 1;
// to make sure the filename isnt too long
uint8_t size_check = 0;
// traverse filename until first whitespace
while (*traverse != ' ') {
// if any character in the filename isnt 1 of these, its invalid. throw 400 error
if (!isalnum(*traverse) && *traverse != '_' && *traverse != '-') {
// if theres no filename at all, throw a 404 error
if (size_check == 0) {
thread->status_code = 404;
printf("No file specified\n");
return thread->status_code;
}
thread->status_code = 400;
printf("Invalid filename character:%c\n", *traverse);
return false;
}
sscanf(traverse++, "%c", thread->filename + size_check++);
// if the filename breaks the 27 character limit, return a 400 error
if (size_check > 27) {
thread->status_code = 400;
printf("filename too long\n");
return false;
}
}
printf("filename:%s\n", thread->filename);
// if HTTP/1.1 isnt given, throw a 400 error
if (http == NULL) {
printf("HTTP/1.1 400 Bad Request\r\n\r\n");
thread->status_code = 400;
return false;
}
traverse = http;
// read in the http version until the first \r\n. this signals the end of the given version name
sscanf(traverse, "%[^\r\n]s", thread->httpversion);
printf("HTTP:%s\n", thread->httpversion);
// if its not a put request, this is the end of the header. return
if (strcmp(thread->method, "PUT") != 0) {
return true;
}
// for put requests only. traverse until the beginning of the content length
traverse = contlength1;
// last stop. signals the end of a normal PUT header. if a client wants to put some of the message in the header, it gets stored after this
char *end = strstr(traverse, "\r\n\r\n");
// if theres no \r\n\r\n, the header is bad. return 400
if (end == NULL) {
printf("bad header\n");
thread->status_code = 400;
return false;
}
// traverse to the next digit
while (!isdigit(*traverse)) {
// if theres no next digit after "content length", the header is bad. return 400
if (traverse == end) {
printf("bad header\n");
thread->status_code = 400;
return false;
}
traverse++;
}
// set to traverse to be sure fit the entire content length. use size_check to traverse through
char *temp = traverse;
size_check = 0;
// while its taking in digits, put them into the char array.
while (isdigit(*traverse)) {
sscanf(traverse++, "%c", temp + size_check++);
}
// convert the new string into numbers
thread->content_length = atoi(temp);
// if the content length is < 0 throw a 400 error
if (thread->content_length < 0) {
thread->status_code = 400;
printf("bad content length:%ld\n", thread->content_length);
return false;
}
// printf("Content Length:%ld\n", thread->content_length);
// move +4 spots to get to the end of this. if its a normal PUT, this will be the last spot. If the client puts part of the message in the header, it goes after this
traverse = end + 4;
// put the rest of the header into a char array to append later. if theres nothing, itll do nothing
strcpy(thread->rest_of_PUT, traverse);
// printf("Rest of PUT:%s\n", thread->rest_of_PUT);
// will only get here if status code is 0
return true;
}
//process the message we just recieved
void
process_request(struct threadObject *thread)
{
printf("\nProcessing Request\n");
// server side file descriptor
int fd;
// if the method is PUT
if (strcmp(thread->method, "PUT") == 0) {
// open the file for read only to check if its already there or not to set proper status code
fd = open(thread->filename, O_WRONLY);
// if it doesnt exist, set 201 status code
struct stat checkExist;
if (stat(thread->filename, &checkExist) != 0) {
thread->status_code = 201;
}
// if it exists, set 200 and overwrite
else {
struct stat fileStat;
fstat(fd, &fileStat);
// check write permission
if ((S_IWUSR & fileStat.st_mode) == 0) {
printf("MESSAGE NOT WRITEABLE PUT\n");
thread->status_code = 403;
return;
}
thread->status_code = 200;
}
// close it
close(fd);
// reopen it. this time for writing to or overwriting. if its there, overwrite it. if not, create it. cant use for status codes since it will always create a new file
fd = open(thread->filename, O_WRONLY | O_CREAT | O_TRUNC);
// printf("fd in process is:%d\n", fd);
// if theres a bad fd, throw a 403
if (fd < 0) {
printf("ERROR\n\n");
thread->status_code = 403;
return;
}
// to check that the amount of bytes sent = the amount received
ssize_t bytes_recv,
bytes_send;
// if theres no body, put an empty file on the server
if (thread->content_length == 0) {
bytes_send = write(fd, '\0', 0);
}
// if there is a body, put it onto the new file created on the server and make sure the received bytes = the sent ones
else {
ssize_t total = 0,
len_track = thread->content_length;
while (thread->content_length != 0) {
bytes_recv = recv(thread->client_sockd, thread->buffer, BUFFER_SIZE, 0);
bytes_send = write(fd, thread->buffer, bytes_recv);
total += bytes_send;
// if the received bytes != the sent byes, send a 500 error
if (bytes_recv != bytes_send) {
thread->status_code = 500;
printf("Recieved != sent for put request\n");
return;
}
thread->content_length -= bytes_recv;
// printf("Bytes read:%ld\nBytes sent:%ld\nMessage content length:%ld\n", bytes_recv, bytes_send, message->content_length);
}
// if the content length != bytes sent, throw a 403 error
if (len_track != total) {
thread->status_code = 403;
printf("Content length != sent for put request\n");
return;
}
}
printf("Message status code:%d\n", thread->status_code);
// close the fd
close(fd);
return;
}
// if the method is GET or HEAD
else if (strcmp(thread->method, "GET") == 0 || strcmp(thread->method, "HEAD") == 0) {
// open the file for reading only
fd = open(thread->filename, O_RDONLY);
// if bad fd, throw a 404
struct stat fileStat;
fstat(fd, &fileStat);
// check read permission and if it exists
if (((S_IRUSR & fileStat.st_mode) == 0) || stat(thread->filename, &fileStat) != 0) {
printf("BAD GET\n");
thread->status_code = 404;
return;
}
else {
thread->status_code = 200;
thread->content_length = lseek(fd, 0, SEEK_END);
}
// close the fd
close(fd);
return;
}
}
void
construct_http_response(struct threadObject *thread)
{
printf("Constructing Response\n");
// size 22 since the largest code is 21 characters + NULL
char response[22];
// 200=OK, 201=CREATED, 400=BAD REQUEST, 403=FORBIDDEN, 404=NOT FOUND, 500=INTERNAL SERVER ERROR
if (thread->status_code == 200) {
strcpy(response, "OK");
}
else if (thread->status_code == 201) {
strcpy(response, "CREATED");
}
else if (thread->status_code == 400) {
strcpy(response, "BAD REQUEST");
}
else if (thread->status_code == 403) {
strcpy(response, "FORBIDDEN");
}
else if (thread->status_code == 404) {
strcpy(response, "NOT FOUND");
}
else if (thread->status_code == 500) {
strcpy(response, "INTERNAL SERVER ERROR");
}
else {
printf("Bad response...\n");
return;
}
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
if (strcmp(thread->method, "GET") == 0 && thread->status_code == 200) {
int fd = open(thread->filename, O_RDONLY);
ssize_t total = 0,
len_track = thread->content_length,
bytes_recv,
bytes_send;
while (thread->content_length != 0) {
bytes_recv = read(fd, thread->buffer, BUFFER_SIZE);
bytes_send = send(thread->client_sockd, thread->buffer, bytes_recv, 0);
if (bytes_recv != bytes_send) {
thread->status_code = 500;
close(fd);
printf("Recieved != sent for GET request\nReceived:%ld\nSent:%ld\n", bytes_recv, bytes_send);
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
close(fd);
return;
}
total += bytes_send;
thread->content_length -= bytes_recv;
}
if (total != len_track) {
thread->status_code = 403;
printf("Content length != recvd for GET request\n");
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
close(fd);
return;
}
close(fd);
}
}
void *
handle_connections(void *ptr_thread)
{
// create a mutual exclusion to lock out any other threads from the function
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// pthread_mutex_lock(&mutex);
// operations go here
struct threadObject *thread = (struct threadObject *) ptr_thread;
// reset message after each loop
memset(thread->buffer, '\0', BUFFER_SIZE);
memset(thread->method, '\0', 5);
memset(thread->filename, '\0', 28);
memset(thread->httpversion, '\0', 9);
thread->content_length = 0;
thread->status_code = 0;
memset(thread->rest_of_PUT, '\0', BUFFER_SIZE);
// read message
if (read_http_response(thread) == true) {
// process message
process_request(thread);
}
// construct a response
construct_http_response(thread);
// unlock the function
// pthread_mutex_unlock(&mutex);
#if 1
close(thread->client_sockd);
pthread_mutex_lock(&global_mutex);
thread->tsk_done = 1;
pthread_mutex_unlock(&global_mutex);
#endif
return NULL;
}
int
main(int argc, char **argv)
{
// Create sockaddr_in with server information
if (argc < 2) {
perror("No arguments passed\n");
return -1;
}
// make sure port number is above 1024 and set the port # to it
if (atoi(argv[1]) < 1024) {
return 1;
}
char *port = argv[1];
// parse the command line args for options -l and -N. -l specifies it will use a log and the following parameter is the filename. -N specifies the number of threads it will use and the following parameter will be a number
int opt;
uint8_t threadNum = 1;
char *logName = NULL;
while ((opt = getopt(argc - 1, argv + 1, "N:l:")) != -1) {
if (opt == 'N') {
threadNum = atoi(optarg);
}
else if (opt == 'l') {
logName = optarg;
}
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t addrlen = sizeof(server_addr);
// Create server socket
int server_sockd = socket(AF_INET, SOCK_STREAM, 0);
// Need to check if server_sockd < 0, meaning an error
if (server_sockd < 0) {
perror("socket");
return 1;
}
// Configure server socket
int enable = 1;
// This allows you to avoid: 'Bind: Address Already in Use' error
int ret = setsockopt(server_sockd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
if (ret < 0) {
return EXIT_FAILURE;
}
// Bind server address to socket that is open
ret = bind(server_sockd, (struct sockaddr *) &server_addr, addrlen);
if (ret < 0) {
return EXIT_FAILURE;
}
// Listen for incoming connections
ret = listen(server_sockd, 5); // 5 should be enough, if not use SOMAXCONN
if (ret < 0) {
return EXIT_FAILURE;
}
struct threadObject thread[threadNum];
// Connecting with a client
struct sockaddr client_addr;
socklen_t client_addrlen = sizeof(client_addr);
// create a pthread array of size (number of threads). specify this will be using the handle connections function. join the threads together
#if 0
pthread_t thread_id[threadNum];
#endif
#if 1
struct threadObject *tsk = NULL;
int tskidx;
// clear out the thread structs
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
memset(tsk,0,sizeof(struct threadObject));
}
while (true) {
// accept connection
int client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
pthread_mutex_lock(&global_mutex);
// join any previously completed threads
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (tsk->tsk_done) {
pthread_join(tsk->tsk_threadid,NULL);
tsk->tsk_inuse = 0;
tsk->tsk_done = 0;
}
}
// find unused task slot
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (! tsk->tsk_inuse)
break;
}
memset(tsk,0,sizeof(struct threadObject));
tsk->client_sockd = client_sockd;
tsk->tsk_inuse = 1;
pthread_mutex_unlock(&global_mutex);
// fire in the hole ...
pthread_create(&tsk->tsk_threadid, NULL, handle_connections, tsk);
}
#endif
#if 0
for (int i = 0; i < threadNum; i++) {
printf("\n[+] server is waiting...\n");
thread[i].client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
handle_connections(&thread[i]);
// pthread_create(&thread_id[i], NULL, handle_connections, &thread[i]);
printf("Response Sent\n");
// close the current client socket
close(thread[i].client_sockd);
}
}
#endif
return EXIT_SUCCESS;
}
Got some trouble with TCP socket multiplexing.
//socket is non-blocking
const int MAX = 4096;
char *buff[MAX];
char *p = buff;
int fd, rvalue;
rvalue = 0;
if ( (fd = open(path, O_RDONLY)) < 0 ) {
return errno;
} else {
int didsend, didread;
int shouldsend;
while ((didread = read(fd, buff, MAX)) > 0) {
p = buff;
shouldsend = didread;
while ( 1 ) {
didsend = send(sockfd, p, shouldsend, 0);
//if send succeeds and returns the number of bytes fewer than asked for then try to send rest part in next time.
if (didsend < shouldsend) {
p += didsent;
shouldsend -= didsend;
continue;
}
//if there is no place for new data to send, then wait a brief time and try again.
if ( didsend < 0 && (errno == EWOULDBLOCK || errno == EAGAIN) ) {
usleep(1000);
continue;
}
//if all data has been sent then sending loop is over.
if (didsend == shouldsend) {
break;
}
//send error
if ( didsend < 0 ) {
rvalue = errno;
break;
}
}
}
close(fd);
if (didread == -1) {
return errno;
}
return rvalue;
}
Assume I use an I/O Multiplexing function poll() or kqueue(), and non-blocking socket, then if there are only some small data like send a short message, it works fine.
But if it comes to large data, I mean larger than send()'s buffer size, since using non-blocking socket, send() will just send a portion of data, and return how much data it sends, the rest part of data can only be sent in another call of send(), but it takes time, and can't tell how long it will takes. So the second while() is actually a blocking send which using non-blocking socket.
Equivalent to:
//socket is blocking
const int MAX = 4096;
char *buff[MAX];
int fd, n;
if ( (fd = open(path, O_RDONLY)) < 0 ) {
return errno;
} else {
while ((n = read(fd, buff, MAX)) > 0) {
if (send(sockfd, buff, n, 0) < 0) {
return errno;
}
}
close(fd);
return 0;
}
So, what is the solution to this, multithreading might work but that's kind of wasting resource maybe.
This is the general pattern for a single-threaded server that works with multiple connections and non-blocking sockets.
It's primarily pseudo-code in C and doesn't do the necessary error checking. But it gives you an idea that for each accepted connection, you keep a struct instance that maintains the socket handle, request parsing state, response stream, and any other "state" members of that connection. Then you just loop using "select" to wait or having multiple threads doing this same thing.
Again this is only pseudo-code and uses select/poll as an example. You can get even more scalability with epoll.
while (1)
{
fd_set readset = {};
fd_set writeset = {};
for (int i = 0; i < number_of_client_connections; i++)
{
if (client_connections[i].reading_request)
FD_SET(client_connection.sock, &readset);
else
FD_SET(client_connection.sock, &writeset);
}
// add the listen socket to the read set
FD_SET(listen_socket, &readset);
select(n + 1, &readset, &writeset, &timeout); // wait for a socket to be ready (not shown - check for errors and return value)
if (FD_ISSET(listen_socket, &readset))
{
int new_client_socket = accept(listen_socket, &addr, &addrlength);
// create a struct that keeps track of the connection state data
struct ConnectionData client_connection = {};
client_connection.sock = new_client_socket;
client_connection.reading_request = 1; // awaiting for all the request bytes to come in
client_connections[number_of_client_connections++] = client_connection; // pseudo code, add the client_connection to the list
}
for (int i = 0; i < number_of_client_connections; i++)
{
if (client_connections[i].reading_request)
{
if (FD_ISSET(client_connections[i], &readset))
{
char buffer[2000];
int len = recv(client_connections[i].sock, buffer, 2000, 0);
// not shown - handle error case when (recv < 0)
// not shown - handle case when (recv == 0)
ProcessIncomingData(client_connections[i], buffer, len); // do all the request parsing here. Flip the client_connections[i].reading_request to 0 if ready to respond
}
}
else if (client_connections[i].reading_request == 0)
{
if (FD_ISSET(client_connections[i], &writeset))
{
client_connection* conn = &client_connections[i];
int len = send(conn->sock, conn->response_buffer + conn->txCount, conn->response_size - conn->txCount, 0);
conn->txCount += len;
if (conn->txCount == conn->response_size)
{
// done sending response - we can close this connection or change it to back to the reading state
}
}
}
}
Background
I am currently working on a Windows select-like function that not only supports SOCKET handles, but also other kinds of waitable handles. My goal is to wait on standard console handles in order to provide select-functionality to the curl testsuite.
The related program can be found in the curl git repository: sockfilt.c
Question
Is it possible to wait for data to be available on a non-GUI-based console input? The issue is that WaitFor* methods do not support PIPE handles and therefore STDIN is not supported if the process input is fed from another process, e.g. using the pipe | functionality of cmd.
The following example program illustrates the problem: select_ws.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <windows.h>
#include <winsock2.h>
#include <malloc.h>
#include <conio.h>
#include <fcntl.h>
#define SET_SOCKERRNO(x) (WSASetLastError((int)(x)))
typedef SOCKET curl_socket_t;
/*
* select function with support for WINSOCK2 sockets and all
* other handle types supported by WaitForMultipleObjectsEx.
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms687028.aspx
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms741572.aspx
*/
static int select_ws(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
long networkevents;
DWORD milliseconds, wait, idx, avail, events, inputs;
WSAEVENT wsaevent, *wsaevents;
WSANETWORKEVENTS wsanetevents;
INPUT_RECORD *inputrecords;
HANDLE handle, *handles;
curl_socket_t sock, *fdarr, *wsasocks;
int error, fds;
DWORD nfd = 0, wsa = 0;
int ret = 0;
/* check if the input value is valid */
if(nfds < 0) {
SET_SOCKERRNO(EINVAL);
return -1;
}
/* check if we got descriptors, sleep in case we got none */
if(!nfds) {
Sleep((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000));
return 0;
}
/* allocate internal array for the original input handles */
fdarr = malloc(nfds * sizeof(curl_socket_t));
if(fdarr == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal event handles */
handles = malloc(nfds * sizeof(HANDLE));
if(handles == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal socket handles */
wsasocks = malloc(nfds * sizeof(curl_socket_t));
if(wsasocks == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal WINSOCK2 events */
wsaevents = malloc(nfds * sizeof(WSAEVENT));
if(wsaevents == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* loop over the handles in the input descriptor sets */
for(fds = 0; fds < nfds; fds++) {
networkevents = 0;
handles[nfd] = 0;
if(FD_ISSET(fds, readfds))
networkevents |= FD_READ|FD_ACCEPT|FD_CLOSE;
if(FD_ISSET(fds, writefds))
networkevents |= FD_WRITE|FD_CONNECT;
if(FD_ISSET(fds, exceptfds))
networkevents |= FD_OOB;
/* only wait for events for which we actually care */
if(networkevents) {
fdarr[nfd] = (curl_socket_t)fds;
if(fds == fileno(stdin)) {
handles[nfd] = GetStdHandle(STD_INPUT_HANDLE);
}
else if(fds == fileno(stdout)) {
handles[nfd] = GetStdHandle(STD_OUTPUT_HANDLE);
}
else if(fds == fileno(stderr)) {
handles[nfd] = GetStdHandle(STD_ERROR_HANDLE);
}
else {
wsaevent = WSACreateEvent();
if(wsaevent != WSA_INVALID_EVENT) {
error = WSAEventSelect(fds, wsaevent, networkevents);
if(error != SOCKET_ERROR) {
handles[nfd] = wsaevent;
wsasocks[wsa] = (curl_socket_t)fds;
wsaevents[wsa] = wsaevent;
wsa++;
}
else {
handles[nfd] = (HANDLE)fds;
WSACloseEvent(wsaevent);
}
}
}
nfd++;
}
}
/* convert struct timeval to milliseconds */
if(timeout) {
milliseconds = ((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000));
}
else {
milliseconds = INFINITE;
}
/* wait for one of the internal handles to trigger */
wait = WaitForMultipleObjectsEx(nfd, handles, FALSE, milliseconds, FALSE);
/* loop over the internal handles returned in the descriptors */
for(idx = 0; idx < nfd; idx++) {
fds = fdarr[idx];
handle = handles[idx];
sock = (curl_socket_t)fds;
/* check if the current internal handle was triggered */
if(wait != WAIT_FAILED && (wait - WAIT_OBJECT_0) >= idx &&
WaitForSingleObjectEx(handle, 0, FALSE) == WAIT_OBJECT_0) {
/* try to handle the event with STD* handle functions */
if(fds == fileno(stdin)) {
/* check if there is no data in the input buffer */
if(!stdin->_cnt) {
/* check if we are getting data from a PIPE */
if(!GetConsoleMode(handle, &avail)) {
/* check if there is no data from PIPE input */
if(!PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL))
avail = 0;
if(!avail)
FD_CLR(sock, readfds);
} /* check if there is no data from keyboard input */
else if (!_kbhit()) {
/* check if there are INPUT_RECORDs in the input buffer */
if(GetNumberOfConsoleInputEvents(handle, &events)) {
if(events > 0) {
/* remove INPUT_RECORDs from the input buffer */
inputrecords = (INPUT_RECORD*)malloc(events *
sizeof(INPUT_RECORD));
if(inputrecords) {
if(!ReadConsoleInput(handle, inputrecords,
events, &inputs))
inputs = 0;
free(inputrecords);
}
/* check if we got all inputs, otherwise clear buffer */
if(events != inputs)
FlushConsoleInputBuffer(handle);
}
}
/* remove from descriptor set since there is no real data */
FD_CLR(sock, readfds);
}
}
/* stdin is never ready for write or exceptional */
FD_CLR(sock, writefds);
FD_CLR(sock, exceptfds);
}
else if(fds == fileno(stdout) || fds == fileno(stderr)) {
/* stdout and stderr are never ready for read or exceptional */
FD_CLR(sock, readfds);
FD_CLR(sock, exceptfds);
}
else {
/* try to handle the event with the WINSOCK2 functions */
error = WSAEnumNetworkEvents(fds, NULL, &wsanetevents);
if(error != SOCKET_ERROR) {
/* remove from descriptor set if not ready for read/accept/close */
if(!(wsanetevents.lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE)))
FD_CLR(sock, readfds);
/* remove from descriptor set if not ready for write/connect */
if(!(wsanetevents.lNetworkEvents & (FD_WRITE|FD_CONNECT)))
FD_CLR(sock, writefds);
/* remove from descriptor set if not exceptional */
if(!(wsanetevents.lNetworkEvents & FD_OOB))
FD_CLR(sock, exceptfds);
}
}
/* check if the event has not been filtered using specific tests */
if(FD_ISSET(sock, readfds) || FD_ISSET(sock, writefds) ||
FD_ISSET(sock, exceptfds)) {
ret++;
}
}
else {
/* remove from all descriptor sets since this handle did not trigger */
FD_CLR(sock, readfds);
FD_CLR(sock, writefds);
FD_CLR(sock, exceptfds);
}
}
for(idx = 0; idx < wsa; idx++) {
WSAEventSelect(wsasocks[idx], NULL, 0);
WSACloseEvent(wsaevents[idx]);
}
free(wsaevents);
free(wsasocks);
free(handles);
free(fdarr);
return ret;
}
int main(void)
{
WORD wVersionRequested;
WSADATA wsaData;
SOCKET sock[4];
struct sockaddr_in sockaddr[4];
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
SOCKET maxfd = 0;
int selfd = 0;
void *buffer = malloc(1024);
ssize_t nread;
setmode(fileno(stdin), O_BINARY);
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
sock[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[0].sin_family = AF_INET;
sockaddr[0].sin_addr.s_addr = inet_addr("74.125.134.26");
sockaddr[0].sin_port = htons(25);
connect(sock[0], (struct sockaddr *) &sockaddr[0], sizeof(sockaddr[0]));
sock[1] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[1].sin_family = AF_INET;
sockaddr[1].sin_addr.s_addr = inet_addr("74.125.134.27");
sockaddr[1].sin_port = htons(25);
connect(sock[1], (struct sockaddr *) &sockaddr[1], sizeof(sockaddr[1]));
sock[2] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[2].sin_family = AF_INET;
sockaddr[2].sin_addr.s_addr = inet_addr("127.0.0.1");
sockaddr[2].sin_port = htons(1337);
printf("bind = %d\n", bind(sock[2], (struct sockaddr *) &sockaddr[2], sizeof(sockaddr[2])));
printf("listen = %d\n", listen(sock[2], 5));
sock[3] = INVALID_SOCKET;
while(1) {
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(sock[0], &readfds);
FD_SET(sock[0], &exceptfds);
maxfd = maxfd > sock[0] ? maxfd : sock[0];
FD_SET(sock[1], &readfds);
FD_SET(sock[1], &exceptfds);
maxfd = maxfd > sock[1] ? maxfd : sock[1];
FD_SET(sock[2], &readfds);
FD_SET(sock[2], &exceptfds);
maxfd = maxfd > sock[2] ? maxfd : sock[2];
FD_SET((SOCKET)fileno(stdin), &readfds);
maxfd = maxfd > (SOCKET)fileno(stdin) ? maxfd : (SOCKET)fileno(stdin);
printf("maxfd = %d\n", maxfd);
selfd = select_ws(maxfd + 1, &readfds, &writefds, &exceptfds, NULL);
printf("selfd = %d\n", selfd);
if(FD_ISSET(sock[0], &readfds)) {
printf("read sock[0]\n");
nread = recv(sock[0], buffer, 1024, 0);
printf("read sock[0] = %d\n", nread);
}
if(FD_ISSET(sock[0], &exceptfds)) {
printf("exception sock[0]\n");
}
if(FD_ISSET(sock[1], &readfds)) {
printf("read sock[1]\n");
nread = recv(sock[1], buffer, 1024, 0);
printf("read sock[1] = %d\n", nread);
}
if(FD_ISSET(sock[1], &exceptfds)) {
printf("exception sock[1]\n");
}
if(FD_ISSET(sock[2], &readfds)) {
if(sock[3] != INVALID_SOCKET)
closesocket(sock[3]);
printf("accept sock[2] = %d\n", sock[2]);
nread = sizeof(sockaddr[3]);
printf("WSAGetLastError = %d\n", WSAGetLastError());
sock[3] = accept(sock[2], (struct sockaddr *) &sockaddr[3], &nread);
printf("WSAGetLastError = %d\n", WSAGetLastError());
printf("accept sock[2] = %d\n", sock[3]);
}
if(FD_ISSET(sock[2], &exceptfds)) {
printf("exception sock[2]\n");
}
if(FD_ISSET(fileno(stdin), &readfds)) {
printf("read fileno(stdin)\n");
nread = read(fileno(stdin), buffer, 1024);
printf("read fileno(stdin) = %d\n", nread);
}
}
WSACleanup();
free(buffer);
}
Compile using MinGW with the following command:
mingw32-gcc select_ws.c -Wl,-lws2_32 -g -o select_ws.exe
Running the program directly from the console using the following command works:
select_ws.exe
But doing the same with a pipe will constantly signal WaitForMultipleObjectsEx:
ping -t 8.8.8.8 | select_ws.exe
The pipe is ready to read until the parent process is finished, e.g.:
ping 8.8.8.8 | select_ws.exe
Is there a compatible way to simulate a blocking wait on the PIPE-based console input handle together with the other handles? The use of threads should be avoided.
You are welcome to contribute changes to the example program in this gist.
Thanks in advance!
I actually found a way to make it work using a separate waiting-thread. Please see the following commit in the curl repository on github.com.
Thanks for your comments!
Use GetStdHandle(STD_INPUT_HANDLE) to get the STDIN pipe handle, then use ReadFile/Ex() with an OVERLAPPED structure whose hEvent member is set to a manual-reset event from CreateEvent(). You can then use any of the WaitFor*() functions to wait on the event. If it times out, call CancelIo() to abort the read operation.
i've got a problem: sometimes (not regularly) recv returns -1 and errno == EAGAIN while using epoll in edge-triggered mode. piece of code:
server_sock = startup(&port);
if ( (epollfd = epoll_create(4096)) < 0) {
perror("epoll_create error");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = server_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_sock, &ev) == -1) {
perror("epoll_ctl: server_sock");
exit(EXIT_FAILURE);
}
while (1) {
int nfds = epoll_wait(epollfd, events, 4096, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_sock) {
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
(socklen_t *)(&client_name_len));
if (client_sock == -1) //server overloaded
continue;
if (events[i].events & EPOLLIN) {
std::cout << "EPOLLIN on " << client_sock << std::endl;
}
Arch::set_nonblocking(client_sock);
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET; //input data and connection closing
ev.data.fd = client_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sock, &ev) == -1) {
perror("epoll_ctl: client_socket");
exit(EXIT_FAILURE);
}
accept_request(client_sock);
} else {
if (events[i].events & EPOLLRDHUP) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
}
}
}
}
startup(&port) creates nonblocking socket, binding with port and so on. my script sends following data:
GET /connect?id=1&secret=1 HTTP/1.0\r\n\r\n but sometimes recv return -1 in this function (calling inside of accept_request) :
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(int sock, char *buf, int size) {
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n')) {
n = recv(sock, &c, 1, 0);
//debug
std::cout << "n = " << n << std::endl;
if (n > 0) {
if (c == '\r') {
n = recv(sock, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
} else {
//debug
if (errno == EWOULDBLOCK)
std::cout << "EWOULDBLOCK" << std::endl;
c = '\n';
}
}
buf[i] = '\0';
return(i);
}
as epoll man page wrote i have to read/write until i get EAGAIN, but i got it already! and i sure the buffer don't be empty. what i do wrong?
UPD: i've found out an interesting thing: when such situation is happened i use in my code sleep(1) and recc(...) again and i get data that i expect! it's a dirty trick. is there any more graceful approach to solve this problem?
It's completely normal for the first recv() in that case to return EAGAIN. epoll() never told you if it was readable yet or not.
Every single recv() should be prepared to handle EAGAIN if you are using non-blocking sockets. Spurious wakeups are possible, so whenever an API like select(), poll() or epoll() tells you that a socket is readable, it's only saying "it might be readable - give it a try".