Related
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;
}
I have a word guessing game which I needs to repeat until the user types Q:. Generally, the answer is in a format A: answer. The game's using sockets!
Instead of while(1), I tried using while(buffer[0]!='Q') but that does not seem to work.
How do I keep making the client play the game?
Additionally, I also need to tell the user how many games he passed correctly?
while (1)
{
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
int max_fd = server_fd;
for (int i = 0; i < NUMCLIENT; i++)
{
file_descriptor = client_sockets[i];
if (file_descriptor > 0)
{
FD_SET(file_descriptor, &readfds);
}
if (file_descriptor > max_fd)
{
max_fd = file_descriptor;
}
}
int return_value = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if (return_value == -1)
{
printf("Select Error\n");
}
if (FD_ISSET(server_fd, &readfds))
{
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t*) &addrlen)) < 0)
{
perror("accept");
}
send(new_socket, greetings[0], strlen(greetings[0]), 0);
send(new_socket, greetings[1], strlen(greetings[1]), 0);
for (int i = 0; i < NUMCLIENT; i++)
{
if (client_sockets[i] == 0)
{
my_fortune(i);
client_sockets[i] = new_socket;
send(new_socket, client_challenges->question, strlen(client_challenges->question), 0);
break;
}
}
}
for (int i = 0; i < NUMCLIENT; i++)
{
file_descriptor = client_sockets[i];
if (file_descriptor == 0)
{
continue;
}
if (FD_ISSET(file_descriptor, &readfds))
{
memset(buffer, 0, 1024);
valread = read(file_descriptor, buffer, 1024);
if (valread == 0)
{
close(file_descriptor);
client_sockets[i] = 0;
continue;
}
printf("%s\n", buffer);
if (buffer[0] == 'A')
{
if (strlen(client_challenges->answer) == (strlen(buffer) - 4))
{
char store[100];
for (int i = 1; i <= strlen(client_challenges->answer); i++)
{
store[i - 1] = buffer[2 + i];
}
store[strcspn(store, "\r\n")] = 0;
if (strcmp(store, client_challenges->answer) == 0)
{
send(file_descriptor, correct, strlen(correct), 0);
//printf("O: Congratulation - challenge passed!\n");
}
else
{
send(file_descriptor, wrong, strlen(wrong), 0);
send(file_descriptor, client_challenges->answer, strlen(client_challenges->answer), 0);
}
//printf("Wrong guess - expected: %s",client_challenges->answer);}
}
else
{
send(file_descriptor, wrong, strlen(wrong), 0);
send(file_descriptor, client_challenges->answer, strlen(client_challenges->answer), 0);
}
//{printf("Wrong guess - expected: %s",client_challenges->answer);}
}
else if (buffer[0] == 'Q')
{
send(file_descriptor, ending, strlen(ending), 0);
exit(1);
}
else
{
send(file_descriptor, error, strlen(error), 0);
exit(1);
}
}
}
}
If there will only be 1 client, you can simply do
// Actual function to start the game
int play() {
int run = 1;
while (run) {
// run the game
// if game over,
run = 0;
}
return 0; // indicate the game ends gracefully
}
int main(int argc, char** argv) {
int run = 1;
int playAgain = 0;
while (run) {
if (play()) {
// Error: the game ends ungracefully
}
// Prompt for play again
if (!playAgain) {
run = 0;
}
}
// Bye
}
The reason is that the variable run is used to control the game flow. If the user quits or something went wrong, you can set run to 0 and the game will stop running.
If there are going to be multiple clients, you might want to have a look at pthread. Every time you receive a new connection, you can create a new thread with pthread_create and set the play function as start_routine and the fd as the argument. For example, you can try
int MAX_CLIENT = 5;
struct pthread_args {
int fd;
};
void* play(void* arg) {
struct pthread_args* args = (struct pthread_args*)arg;
int fd = args->fd;
// Start the game
}
int main(int argc, char** argv) {
pthread_t pth[MAX_CLIENT];
struct pthread_args pth_args;
int new_fd;
int i = 0;
while(1) { // The server continuously receive connections
// Accept connections
new_fd = accept(/*...*/)
if (new_fd < 0) {
// Error: accepting new connection
}
pth_args.fd = new_fd;
int r = pthread_create(&pth[i], NULL, play, &pth_args);
if (!r) {
// Error: creating thread
}
++i;
}
}
PS: You may want to have a look at pthread_detach as well.
Instead of while(1), I tried using while(buffer[0]!='Q') but that does not seem to work.
The reason while(buffer[0] == 'Q') doesn't work is because by the time the while check for the condition, the buffer (assuming it was declared outside of the while loop) might not be the same buffer anymore because you are looping over all clients and save the buffers from clients to the same buffer variable. So if there are 2 clients and the first client says 'Q' and the second client says 'A', the while sees buffer[0] as 'A' instead of 'Q' and hence continuing the game.
Also, I don't think exit(1) in else if (buffer[0] == 'Q') is a good idea. It kills the server and all clients will be forced to quit the game. Just remove the particular client from the client_sockets array.
:) Hope it helps
I need to make the code more readable, but it has a lot of cycle, can I fix this somehow?
The variable i constantly jumps there and there, it is very inconvenient to watch in debugging.
for(;;)
{
//
// For connection orientated protocols, we will handle the
// packets received from a connection collectively. For datagram
// protocols, we have to handle each datagram individually.
//
//
// Check to see if we have any sockets remaining to be served
// from previous time through this loop. If not, call select()
// to wait for a connection request or a datagram to arrive.
//
for (i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
break;
}
if (i == numSocket)
{
for (i = 0; i < numSocket; i++)
{
FD_SET(servSock[i], &SockSet);
}
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue;
}
for (i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
{
FD_CLR(servSock[i], &SockSet);
break;
}
}
...
}
To make the code more readable (and testable and maintainable) you can encapsulate some of the logic into separate functions with meaningful names. Consider this version of your loop
for(;;)
{
int i = find_socket_to_be_served(numSocket, servSock, &SockSet);
if (i == numSocket)
{
set_all_sockets(numSocket, servSock, &SockSet);
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue;
}
// ...
}
Where the two functions used are defined as
int find_socket_to_be_served(int n_sockets, int *fds, fd_set *fdset)
{
int i = 0;
for (; i < n_sockets; i++)
{
if (FD_ISSET(fds[i], fdset))
{
FD_CLR(fds[i], fdset);
break;
}
}
return i;
}
void set_all_sockets(int n_sockets, int *fds, fd_set *fdset)
{
for (int i = 0; i < n_sockets; i++)
{
FD_SET(fds[i], fdset);
}
}
As far as I understand your code, you don't need FD_CLEAR() your sockets one by one.
Just FD_ZERO() all of them at once, then use a single loop to FD_SET() them again.
Note that after select(), many sockets could be ready to use; you don't have to break when one has been used.
Note also that the first parameter of select() is unused on Windows but should be 1+ the max of your socket file descriptors (not the number of them) everywhere else.
for(;;)
{
FD_ZERO(&SockSet);
for (int i = 0; i < numSocket; i++)
{
FD_SET(servSock[i], &SockSet);
}
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue; // or probably break...
}
for (int i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
{
// use servSock[i]
}
}
}
So far everything have been working perfectly. Just the problem I've been having is that I can't think of a way of assigning or i would say giving names to the clients who are connect to the server so that other clients who know is wrote a message
server:
while (1) {
FD_ZERO(&read_set);
//FD_ZERO(&write_set);
FD_SET(fd, &read_set);
//FD_SET(fd, &write_set);
for (i = 0; i < num_clients; i++) { //at first this part will not excute
FD_SET(clients[i], &read_set);
}
select(fd + num_clients + 1, &read_set, NULL, NULL, NULL);
if (FD_ISSET(fd, &read_set)) {
if ((clients[num_clients++] = accept(fd, NULL, NULL)) == -1) {
perror("accept error");
continue;
}
printf("we got a connection!\n");
}
for (i = 0; i < num_clients; i++) {
if (FD_ISSET(clients[i], &read_set)) {
msg = read(clients[i], buf, sizeof(buf));
if (msg > 0) {
int savedclnt = clients[i];
printf("client %d says: %s\n", i, buf);
for (int p = 0; p<num_clients; p++)
{
if (clients[p] != savedclnt) {
//write("from %d",clients[p]);
//char msg2 = strcat(clients[i],msg);
write(clients[p], buf, msg);
}
}
}
}
}
}
For what you're doing, you don't really need threads. From personal experience, a single threaded server would do the job just fine. Instead of having the main body wrap around a call to accept() you should use select() to see which file descriptors have input, something along the lines of...
fd_set readfds;
struct timeval timeout;
unsigned int num_clients;
unsigned int clients[100];
while(1)
{
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
for(i=0; i<num_clients; i++)
{
FD_SET(clients[i],&readfds);
}
timeout.tv_sec=1;
timeout.tv_usec=0;
select(max_fds, &readfds, NULL, NULL, &timeout);
if(FD_ISSET(sock,&readfds))
{
clients[num_clients++] = accept(sock,(struct sockaddr *)&server,&size);
}
for(i=0;i<num_clients;i++)
{
if(FD_ISSET(clients[i],&readfds))
{
int err=read(clients[i],buf,1000);
if(err>0)
{
for(p=0;p<num_clients;p++)
{
write(clients[i],buf,err);
}
}
}
}
}
That code is just an example and needs error checking to see if a client has disconnected, a way of updating the list of clients and seeing if a client is ready to receive data. But hopefully it gives you the basic idea of where to go?
I am trying to test a client program to make sure that it correctly sends and receives messages from a server. When I run the program I get this weird error from malloc. Any help would be great. I've been looking at this code for way too long. Thanks in advance.
Here is the error:
// The first test runs okay
TEST 1 :
Connected to server
-> Socket open success
-> Request sent success
-> Response received success
Testing print:
Response received: <replyLoadAvg>0.228027:0.250000:0.280273</replyLoadAvg>
LoadAvg for 1min :: 0.228027
LoadAvg for 5min :: 0.250000
LoadAvg for 15min :: 0.280273
Real response -> <replyLoadAvg>0.228027:0.250000:0.280273</replyLoadAvg>
// Then I get this error for some reason
clientc(17781) malloc: ********* error for object 0x7fd048404318: incorrect checksum for
freed object - object was probably modified after being freed.
********* set a breakpoint in malloc_error_break to debug
Abort trap: 6
here is the client code:
// prototype
char* verifyString(char*);
void printLoadAvg(char*);
int receiveResponse(int, char **);
int main(int argc, char *argv[])
{
int sockfd;
char *name;
char *port;
struct sockaddr_in * dest;
if(argc != 3)
{
printf("Not enough arguments -- Exiting program\n");
return 0;
}
name = argv[1];
port = argv[2];
char * received;
char send[256];
char testMessages[5][256] = {"<loadavg/>", "<echo>Hello World!</echo>", "<echo> </echo>", "", "<echo>Bye Bye World...<echo>"};
char expectedResponses[5][256] = {"", "<reply>Hello World!</reply>", "<reply></reply>","<error>unknown format</error>", "<error>unknown format</error>"};
int i;
srand(time(NULL));
for(i = 0; i < 5; i++)
{
printf("\nTEST %i", i+1);
printf(((rand() % 2) ? ":\n" : " :\n"));
sprintf(send, "%s", testMessages[i]);
sockfd = createSocket(name, atoi(port), &dest);
if(sockfd < 0){
printf(" xx Open socket failed\n");
}
else{
printf(" -> Socket open success\n");
if(sendRequest(sockfd, send, dest) < 0){
printf(" xx Send failed\n");
}
else{
printf(" -> Request sent success\n");
if(receiveResponse(sockfd, &received) < 0){
printf(" xx Receive failed\n");
}
else{
printf(" -> Response received success\n");
if(i!=0 && strcmp(received, expectedResponses[i]) != 0){
printf("Incorrect message\n");
printf("Expected: %s\n", expectedResponses[i]);
printf("Received: %s\n", received);
}
else if(i!=0){
printf("Test %i. passed successfully!\n", i+1);
}
else{
printf("Testing print:\n");
printResponse(received);
fprintf(stdout, "Real response -> %s\n", received);
}
}
}
}
free(received);
free(dest);
if(closeSocket(sockfd)==0){
printf(" -> Socket closed success\n");
}
else{
printf(" XX Close failed\n");
}
}
return 0;
}
int createSocket( char* hostname, int port, struct sockaddr_in** dest)
{
// fill in the socket address struct
(*dest) = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
struct hostent *hostptr;
hostptr = gethostbyname(hostname);
int socketfd;
memset((void *) (*dest), 0, (size_t)sizeof(struct sockaddr_in));
(*dest)->sin_family = (short)(AF_INET);
memcpy((void*)& (*dest)->sin_addr, (void *) hostptr->h_addr, hostptr->h_length);
(*dest)->sin_port = htons((u_short)port);
// now get the file descriptor for the socket
if((socketfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
return -1;
}
// connect to the server at the location displayed by TCPserver.c
if(connect(socketfd, (struct sockaddr*)(*dest), sizeof(struct sockaddr)) < 0) {
perror("Error: call to connect()\n");
return -1;
}
printf("Connected to server\n");
return socketfd;
}
int sendRequest(int sock, char* request, struct sockaddr_in* dest)
{
// send a request through the socket to the server.
if(write(sock, request, BUFFERSIZE) < 0)
{
return -1;
}
return 0;
}
int receiveResponse(int sock, char ** response)
{
// dynamically allocate memory to be used in this function
(*response) = malloc(sizeof(char));
// wait for a response from the server
if(read(sock, (*response), BUFFERSIZE) < 0)
{
return -1;
}
return 0;
}
int closeSocket(int sock)
{
int error = 0;
if(close(sock))
{
perror("Error: Failed to close socket\n");
error = -1;
}
return error;
}
void printResponse(char* response)
{
char* buffer = "<replyloadavg>";
int i, isLoadAvg = 0;
// verify the string is in the correct format by calling the verifty string function
// then print the string to the screen.
printf("Response received: %s\n", verifyString(response));
for(i = 0; i < 14; i++)
{
if(response[i] == buffer[i])
isLoadAvg = 0;
else
{
isLoadAvg = 1;
i = 15;
}
}
if (isLoadAvg)
{
printLoadAvg(response);
}
}
char* verifyString(char* string)
{
int i, count = 0;
// step through the array until you come to the last '>' (the end of the string)
// then insert the NULL terminator at the end of the string.
for(i = 0; i < BUFFERSIZE; i++)
{
if(string[i] == '>')
count++;
if(count == 2)
{
string[i + 1] = '\0';
i = BUFFERSIZE;
}
}
return string;
}
void printLoadAvg(char* string)
{
int i = 0, j;
// these three arrays hold the newly formated strings, the ones that are easier to read.
char s1[BUFFERSIZE], s2[BUFFERSIZE], s3[BUFFERSIZE];
// step through the string until you reach the end of the string
while(string[i] != '>')
{
i++;
}
i++;
// now insert a colon and then save the contents of the string to the appropriate s array.
for(j = 0; string[i] != ':'; j++, i++)
{
s1[j] = string[i];
}
// be sure to set the null terminator at the end.
s1[j] = '\0';
i++;
for(j = 0; string[i] != ':'; j++, i++)
{
s2[j] = string[i];
}
// be sure to set the null terminator at the end.
s2[j] = '\0';
i++;
// now move to the beginning of the end of the string array
for(j = 0; string[i] != '<'; j++, i++)
{
s3[j] = string[i];
}
// now insert one more null terminator for the final s array.
s3[j] = '\0';
// now print out the newly fomrated strings.
printf("\nLoadAvg for 1min :: %s\n", s1);
printf("LoadAvg for 5min :: %s\n", s2);
printf("LoadAvg for 15min :: %s\n", s3);
}
(*response) = malloc(sizeof(char));
// wait for a response from the server
if(read(sock, (*response), BUFFERSIZE) < 0)
What size is BUFFERSIZE. Is it bigger than 1? If so, you will overflow your buffer...