I wrote a C application for a socialization network and also a simple room-based chat. I used ncurses, sockets and basic networking stuff.
The problem is that my function uses select() to read from server socket AND stdin so when I start to write a message, the output window freezes and only shows messages from other clients after I hit enter.
I tried everything possible .. Is there a way to fix this ?
I also tried to force nocbreak().It works okay but if I do that, when I write the message, the echoing is disabled and nothing shows up in the input window as I type, even though the message is there but like "invisible".
Here is the code :
ssize_t safePrefRead(int sock, void *buffer)
{
size_t length = strlen(buffer);
ssize_t nbytesR = read(sock, &length, sizeof(size_t));
if (nbytesR == -1)
{
perror("read() error for length ! Exiting !\n");
exit(EXIT_FAILURE);
}
nbytesR = read(sock, buffer, length);
if (nbytesR == -1)
{
perror("read() error for data ! Exiting !\n");
exit(EXIT_FAILURE);
}
return nbytesR;
}
ssize_t safePrefWrite(int sock, const void *buffer)
{
size_t length = strlen(buffer);
ssize_t nbytesW = write(sock, &length, sizeof(size_t));
if (nbytesW == -1)
{
perror("write() error for length ! Exiting !\n");
exit(EXIT_FAILURE);
}
nbytesW = write(sock, buffer, length);
if (nbytesW == -1)
{
perror("write() error for data ! Exiting !\n");
exit(EXIT_FAILURE);
}
return nbytesW;
}
void activeChat(int sC, const char *currentUser, const char *room)
{
char inMesg[513], outMesg[513];
char user[33];
int winrows, wincols;
WINDOW *winput, *woutput;
initscr();
nocbreak();
getmaxyx(stdscr, winrows, wincols);
winput = newwin(1, wincols, winrows - 1, 0);
woutput = newwin(winrows - 1, wincols, 0, 0);
keypad(winput, true);
scrollok(woutput, true);
wrefresh(woutput);
wrefresh(winput);
fd_set all;
fd_set read_fds;
FD_ZERO(&all);
FD_ZERO(&read_fds);
FD_SET(0, &all);
FD_SET(sC, &all);
wprintw(woutput, "Welcome to room '%s' \n Use /quitChat to exit !\n!", room);
wrefresh(woutput);
while (true)
{
memcpy( &read_fds, &all, sizeof read_fds );
if (select(sC + 1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("select() error or forced exit !\n");
break;
}
if (FD_ISSET(sC, &read_fds))
{
memset(inMesg, 0, 513);
safePrefRead(sC, user);
safePrefRead(sC, inMesg);
wprintw(woutput, "%s : %s\n", user, inMesg);
wrefresh(woutput);
wrefresh(winput);
}
if (FD_ISSET(0, &read_fds))
{
//wgetnstr(winput, "%s", outMesg);
int a, i = 0;
while ( i < MAX_BUF_LEN && (a = wgetch(winput)) != '\n')
{
outMesg[i] = (char)a;
i++;
}
outMesg[i] = 0;
if (outMesg[0] == 0)
continue;
if (strcmp(outMesg, "/quitChat") == 0)
{
safePrefWrite(sC, outMesg);
break;
}
safePrefWrite(sC, outMesg);
delwin(winput);
winput = newwin(1, wincols, winrows - 1, 0);
keypad(winput, true);
wrefresh(winput);
}
}
delwin(winput);
delwin(woutput);
endwin();
}
-safePrefWrite and safePrefRead are wrappers for prexied read / write and error treating
-sC is the server socket.
LE: I tried using fork and threads. Using fork was behaving the same and threads were a disaster, the terminal was messed up.
Thank you.
modify the while(true) loop to only handle one char at a time for the stdin.
Which mostly means for stdin, read a single char:
if char is '\n' then handle as currently,
otherwise, just append char to the buffer to write.
Always, before appending a char to buffer to write, check that buffer is not full.
add code to handle the case where the buffer to write is full
end the function with this sequence:
delwin(winput);
delwin(woutput);
endwin();
endwin();
to end both windows.
Do not call endwin() during processing of the socket input.
Do not call endwin() when select() returns an error condition
the fd_set is not an intrinsic size in C, so use memcpy() to set
read_fds from all. suggest:
memcpy( &read_fds, &all, sizeof read_fds );
the parameter: currentUser is not used, suggest inserting the line:
(void)currentUser;
to eliminate a compiler warning message.
for readability, and ease of understandability, suggest #define the magic numbers 513 and 33 with meaningful names, then use those meaningful names throughout the code.
#define MAX_BUF_LEN (513)
#define MAX_USER_LEN (33)
this line: outMesg[i] = a; raises a compiler warning, suggest:
outMesg[i] = (char)a;
This line: while ( (a = wgetch(winput)) != '\n') can allow the buffer outMesg[] to be overrun, resulting in undefined behaviour and can lead to a seg fault event. suggest:
while ( i < MAX_BUF_LEN && (a = wgetch(winput)) != '\n')
Suggest posting the prototypes for the safePrefWrite() and safePrefRead() functions, similar to:
void safePrefRead( int, char * );
void safePrefWrite( int, char * );
As noted by #user3629249, there are several criticisms which can be applied to the sample code. However, OP's question is not addressed by those improvements.
OP seems to have overlooked these functions:
cbreak or raw, to make wgetch read unbuffered data, i.e., not waiting for '\n'.
nodelay or timeout, to control the amount of time wgetch spends waiting for input.
By the way, making select work with a curses program will make assumptions about the curses library internal behavior: getting that to work reliably can be troublesome.
Fixed it finally by using only the big loop.
Here is the code if anyone has the same problem in the future :
if (FD_ISSET(0, &read_fds))
{
inChar = wgetch(winput);
if (inChar == 27)
{
safePrefWrite(sC, "/quit");
break;
}
if (inChar == KEY_UP || inChar == KEY_DOWN || inChar == KEY_LEFT || inChar == KEY_RIGHT)
continue;
if (inChar == KEY_BACKSPACE || inChar == KEY_DC || inChar == 127)
{
wdelch(winput);
wrefresh(winput);
if (i != 0)
{
outMesg[i - 1] = 0;
i--;
}
}
else
{
outMesg[i] = (char)inChar;
i++;
}
if (outMesg[i - 1] == '\n')
{
outMesg[i - 1] = 0;
i = 0;
if (outMesg[0] == 0)
continue;
if (strcmp(outMesg, "/quit") == 0)
{
safePrefWrite(sC, outMesg);
break;
}
safePrefWrite(sC, outMesg);
delwin(winput);
winput = newwin(1, wincols, winrows - 1, 0);
keypad(winput, true);
wrefresh(winput);
memset(outMesg, 0, 513);
}
}
I also use raw() to disable signals and to treat the codes how I want.
Anything else above and below this "if" is just like in the 1st post.
Related
I am trying to run the LIST command to display the files, but when I run it, it displays all the files like I want it to, but it just hangs there, and doesn't break back to the menu. the last 3 characters of the list is always a newline followed by a period then a newline, so I put that in an if statement to check to break out and close the socket, but it doesnt, am I missing something?
case 'l':
case 'L':
//Handle L case
sprintf(buff, "LIST\n");
send(sockfd, buff, 1000, 0);
int length = strlen(buff);
while ((rsize = recv(sockfd, buff, 1000, 0)) > 0)
{
fwrite(buff, rsize, 1, stdout);
if ( buff[length-3] == '\n' && buff[length-2] == '.' && buff[length-1] == '\n' )
{
break;
}
}
close(sockfd);
break;
Here's your problem:
if ( buff[length-3] ...
length comes from strlen(buff), and buff, at that point, contains the data you sent, not the data you received, so buff[length-3] is probably not even close to the end of your input data, which could be up to 1000 characters long.
You should be concentrating here on rsize, which is the number of bytes you received, rather than length.
EDIT: As was once mentioned in the comments (EDIT 2: and now in a separate answer), you're going to run into problems here any time recv() either unexpectedly stops in the middle of your end-of-line sequence, and particularly if it stops after having read less than three characters, since then you'll be illegally using a negative index to your array. It would be better to write a function to read an entire line from the socket and store it in your buffer, and then just call if ( !strcmp(buffer, ".") ) to know when you're done.
Here's an example:
#include <unistd.h>
#include <errno.h>
#include <string.h>
ssize_t socket_readline(const int socket, char * buffer, const size_t max_len) {
ssize_t num_read, total_read = 0;
bool finished = false;
memset(buffer, 0, max_len);
for ( size_t index = 0; !finished && index < (max_len - 1); ++index ) {
num_read = read(socket, &buffer[index], 1);
if ( num_read == -1 ) {
if ( errno == EINTR ) {
continue; /* Interrupted by signal, so continue */
}
else {
return -1; /* Other read() error, return error code */
}
}
else {
if ( buffer[index] == '\n' ) {
buffer[index] = '\0'; /* Remove newline */
finished = true; /* End of line, so stop */
}
else {
++total_read;
}
}
}
return total_read;
}
Using a system call for each individual character is a bit of an overhead, but if you don't do that you're going to have to store the additional characters you read somewhere, so unless you want to write your own buffering facilities, that's the best option.
As an aside, you should also be checking the return from send() (and from all system calls, for that matter), since that's not guaranteed to send all your characters in one go, and you may need additional tries.
You cant rely on rsize by itself. Think of what happens if one call to recv() ends on the first '\n', and then the next recv() receives the '.'. Or if recv() does not receive >=3 bytes to begin with. You would not be able to check for "\n.\n" in a single if statement like you are trying to do.
What you really should be doing instead is reading the socket data into a buffer until a '\n' is encountered (do not store it in the buffer), then process the buffer as needed and clear it, then repeat until the buffer contains only '.' by itself.
Try something like this:
case 'l':
case 'L':
{
//Handle L case
int linecapacity = 1000;
char *line = (char*) malloc(linecapacity);
if (line)
{
int linelength = 0;
if (send(sockfd, "LIST\n", 5, 0) == 5)
{
bool stop = false;
while (!stop)
{
rsize = recv(sockfd, buff, 1000, 0);
if (rsize <= 0) break;
fwrite(buff, rsize, 1, stdout);
char *start = buff;
char *end = &buff[rsize];
while ((start < end) && (!stop))
{
char *ptr = (char*) memchr(start, '\n', end-start);
if (!ptr) ptr = end;
length = (ptr - start);
int needed = (linelength + length);
if (needed > linecapacity)
{
char *newline = realloc(line, needed);
if (!newline)
{
stop = true;
break;
}
line = newline;
linecapacity = needed;
}
memcpy(buff, &line[linelength], length);
linelength += length;
if ((linelength == 1) && (line[0] == '.'))
{
stop = true;
break;
}
// process line up to linelength characters as needed...
linelength = 0;
start = ptr + 1;
}
}
}
free(line);
}
close(sockfd);
break;
}
Alternatively:
case 'l':
case 'L':
{
//Handle L case
int linecapacity = 1000;
char *line = (char*) malloc(linecapacity);
if (line)
{
int linelength = 0;
if (send(sockfd, "LIST\n", 5, 0) == 5)
{
char ch;
while (true)
{
rsize = recv(sockfd, &ch, 1, 0);
if (rsize < 1) break;
fwrite(&ch, 1, 1, stdout);
if (ch == '\n')
{
if ((linelength == 1) && (line[0] == '.'))
break;
// process line up to linelength characters as needed...
linelength = 0;
}
else
{
if (linelength == linecapacity)
{
char *newline = realloc(line, linecapacity + 1000);
if (!newline)
break;
line = newline;
linecapacity += 1000;
}
line[linelength++] = ch;
}
}
}
free(line);
}
close(sockfd);
break;
}
On page 34 of the book "Linux System Programming" the following example of correctly handling partial reads with a while loop for blocking reads is given
ssize_t ret;
while (len != 0 && (ret = read(fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror("read");
break;
}
len -= ret;
buf += ret;
}
On the next page it gives the following example for nonblocking reads. Does this example need to be wrapped in a while loop to handle the possibility of partial reads?
char buf[BUFSIZ];
ssize_t nr;
start:
nr = read(fd, buf, BUFSIZ);
if (nr == -1) {
if (errno == EINTR)
goto start; /* oh shush */
if (erron == EAGAIN)
/* resubmit later */
else
/* error */
}
Typically, a non-blocking IO is used when the code can (or intends to) do something else in parallel instead of waiting on input (e.g. check on another file or socket). Otherwise, a simple blocking IO will do the same trick as polling on the file for reads, as Jlghtuse mentioned. However, I am not sure if a non-blocking IO is guaranteed to return exact number of bytes requested. As a safe bet, it probably could use a while loop anyways. I think one usable code chunk might look like:
char buf[BUFSIZ];
ssize_t nr;
char *bufp = buf;
ssize_t rdbyts = 0;
while(rdbyts < BUFSIZ) {
nr = read(fd, bufp, (BUFSIZ - rdbyts));
if (nr == -1) {
if (errno == EINTR)
continue; /* oh shush */
else if (errno == EAGAIN)
/* resubmit later - might be do
* something else and come back
* or just sleep. */
do_some_work_or_sleep();
continue;
else
/* error */
break;
} else if (nr < (BUFSIZ - rdbytes)) {
bufp += nr;
rdbyts += nr;
}
}
No, this example don't need to be wrapped in loop. It uses goto statement (see this answer for good examples of its using).
This example shows nonblocking read, that's why this code differs from the first one. See note after second code block:
Handling the EAGAIN case like we did the EINTR case (with a goto
start) would make little sense. We might as well not have used non‐
blocking I/O. The point of nonblocking I/O is to catch the EAGAIN and
do other, useful work.
The code I have right now sends a prompt out to stdout, then reads a line from stdin. Receiving SIGINT at any point interrupts execution and exits the program. I am unsure where I should trap SIGINT, and I know that I cannot start a new prompt when the signal is received with my current code. Is there a proper way to accomplish that (ultimate goal would be for it to act like most shells (SIGINT cancels the current prompt and starts a new one))?
This code will run on Linux, but the less platform independent, the better.
get_line reads a line from stdin into a buffer and generates a char[], which is assigned to line.
split_args takes a line and transforms it into an array of char[], splitting on whitespace.
is_command_valid determines if the user typed a known internal command. External programs cannot be executed.
static int run_interactive(char *user)
{
int done = 0;
do
{
char *line, **args;
int (*fn)(char *, char **);
fprintf(stderr, "gitorium (%s)> ", user);
get_line(&line);
if (line[0] == '\0')
{
free(line);
break;
}
split_args(&args, line);
if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") ||
!strcmp(args[0], "logout") || !strcmp(args[0], "bye"))
done = 1;
else if (NULL == args[0] ||
(!strcmp("help", args[0]) && NULL == args[1]))
interactive_help();
else if ((fn = is_command_valid(args)) != NULL)
(*fn)(user, args);
else
error("The command does not exist.");
free(line);
free(args);
}
while (!done);
return 0;
}
Here are the two most important helper functions
static int split_args(char ***args, char *str)
{
char **res = NULL, *p = strtok(str, " ");
int n_spaces = 0, i = 0;
while (p)
{
res = realloc(res, sizeof (char*) * ++n_spaces);
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces-1] = p;
i++;
p = strtok(NULL, " ");
}
res = realloc(res, sizeof(char*) * (n_spaces+1));
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces] = 0;
*args = res;
return i;
}
static int get_line(char **linep)
{
char *line = malloc(LINE_BUFFER_SIZE);
int len = LINE_BUFFER_SIZE, c;
*linep = line;
if(line == NULL)
return GITORIUM_MEM_ALLOC;
for(;;)
{
c = fgetc(stdin);
if(c == EOF || c == '\n')
break;
if(--len == 0)
{
char *linen = realloc(*linep, sizeof *linep + LINE_BUFFER_SIZE);
if(linen == NULL)
return GITORIUM_MEM_ALLOC;
len = LINE_BUFFER_SIZE;
line = linen + (line - *linep);
*linep = linen;
}
*line++ = c;
}
*line = 0;
return 0;
}
If I understand you correctly, you want to know how to handle the signal as well as what to do once you get it.
The way you establish a signal handler is with sigaction(). You didn't state the platform you're on so I'm assuming Linux, although sigaction() is defined by the POSIX standards and should be available on most other platforms.
There are various ways you can do this. One way is to establish a signal handler which simply sets a global variable to 1, denoting that the signal was caught. Then in your getline() function you establish a check to see if SIGINT was caught and if it was then return NULL and allow run_interactive() to run again.
Here's how you would catch the signal:
#include <signal.h>
static int sigint_caught = 0;
static void sigint_handler(int sig) {
sigint_caught = 1;
}
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // or SA_RESTART if you want to automatically restart system calls interrupted by the signal
sa.sa_handler = sigint_handler;
if (sigaction(SIGINT, &sa, NULL) == -1) {
printf("could not establish handler\n");
exit(-1); // or something
}
And then perhaps in getline(), in the infinite loop, you would establish the check to see if SIGINT has been caught:
for (;;) {
if (sigint_caught) {
return NULL;
}
// ...
And then in your run_interactive() call you can check the return value with the check to see if SIGINT was caught:
// ...
get_line(&line);
if (line == NULL && sigint_caught) {
sigint_caught = 0; // allow it to be caught again
free(line);
continue; // or something; basically go to the next iteration of this loop
} else if (line[0] == '\0') {
free(line);
break;
} else {
// rest of code
Didn't test it so I can't guarantee it'll work, since your question is pretty broad (having to look through more of your code etc.), but hopefully it gives you enough of an idea as to what you can do you in your situation. This is perhaps a pretty naive solution but it might meet your needs. For something more robust perhaps look into the source code for popular shells like bash or zsh.
For example, one thing that can happen is that fgetc() might block since there is no new data in stdin, and that might be when the signal is sent. fgetc() would be interrupted and errno would be EINTR, so you could add a check for this in getline():
c = fgetc(stdin);
// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
return NULL;
This would only happen if you don't set sa_flags to SA_RESTART. If you do, then fgetc should automatically restart and continue blocking until new input is received, which may or may not be what you want.
I have 2 machines running a simple C TCP server that I have written for testing purposes, 1 with Fedora 16, the other with Ubuntu 11.10. My Fedora machine works perfectly but on the Ubuntu machine, recv() does not block. Please keep in mind that these machines are running the same exact code. Has anybody seen this before? Thanks
int TcpSocket::ReadFromClient(int socket, char* buf, int len)
{
char *request = buf;
int slen = len;
int c = recv(socket, request, slen, 0);
while((c > 0) && (request[c-1] != '\n'))
{
request += c;
slen -= c;
c = recv(socket, request, slen, 0);
}
if (c < 0)
{
return c;
}
else if(c == 0)
{
//Sending back an empty string
buf[0] = '\0';
}
return len-slen;
}
It looks like the intention of your code is to stop reading when a '\n' byte arrives. If that is the case then you need to read from the socket 1 byte at a time instead of using the entire available buffer size, especially since you are only checking the last byte of the buffer instead of checking every byte received.
You should also change the loop logic to only call recv() in one place instead of two places. Your current implementation is calling recv() with slen=0 when the buffer is exhausted, which will set c=0 and nullify the first byte in the buffer.
Try this instead:
int TcpSocket::ReadFromClient(int socket, char* buf, int len)
{
int slen = len;
char ch;
while (len > 0)
{
int ret = recv(socket, &ch, 1, 0);
if (ret > 0)
{
*buf = ch;
++buf;
--len;
if (ch == '\n')
break;
}
else
{
if ((ret == 0) || (errno != EAGAIN))
return ret;
fd_set readfd;
FD_ZERO(&readfd);
FD_SET(socket, &readfd);
timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
ret = select(socket+1, &readfd, NULL, NULL, &tv);
if (ret < 0)
return ret;
if (ret == 0)
{
// timeout elapsed while waiting for data
// do something if desired...
}
}
}
return slen - len;
}
From the below piece of code, why I am getting Reading Socket for response
int Read(int sock, char *p, int size)
{
int remain, read=0;
remain = size;
while (remain > 0 ) {
if ((read = recv(sock, p, remain, 0)) < 0) {
/* Error */
return(read);
} else if (read == 0 || *p == 0x0a) {
/* EOF */
break;
}
remain -= read;
p += read;
}
return(size - remain);
}
while (!done)
{
printf("***Reading Socket for response***");
rsplen= Read(myVsHandle.sock,(char *)encXMLResponse,MAX_RSP_LEN);
if (rsplen < 0 )
{
printf("Internal Communication Error");
return -1;
}
else if (rsplen >0)
printf("Revieved response");
done++;
return 0;
else if (rsplen == 0)
{
printf("Reading socket");
}
You are waiting for MAX_RSP_LEN bytes to be read - is there that many bytes to be read? Maybe your process is stuck in a blocking read().
Also depending on the sort of socket you are recv()ing from, there is no guarantee on the amount of data you will read, so specifically looking for a value 0x0a may not work.
Your problem could be that you are not ending your output with a newline. Try ending your outputs with a newline (\n). stdout is line buffered, so you may not see anything for a long time if you don't output a newline.
Another possibility is that you don't return from Read() unless you read the specified number of bytes. Depending upon the value of MAX_RSP_LEN, and the amount of data available, Read() may wait forever.
Also, your test: *p == 0x0a looks suspicious. What are you testing here?
Edit: There is another "bug":
else if (rsplen >0)
printf("Revieved response");
done++;
return 0;
else...
You are missing curly braces. In the current form, the code shouldn't compile. Please post actual code.
This:
if ((read = recv(sock, p, remain, 0)) < 0) {
Should be
if ((read = recv(sock, p, remain, 0)) > 0) { // Greater then 0, because recv returns the number of bytes received if successful, if it fails -1.
You're missing curly braces around the:
else if(rsplen > 0)
... statements
It should be:
...
}else if (rsplen >0){
printf("Revieved response");
done++;
return 0;
} ...