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;
}
Related
The following is a code snippet from the server socket that reads a linux command sent by the client, executes it and sends the output back to the client :
while(1){
char command[200];
message_read = read(sock, command, sizeof(command));
if(message_read > 0){
command[message_read] = '\0';
dup2(sock, STDOUT_FILENO);
dup2(sock, STDERR_FILENO);
system(command);
}
}
The following is a code snippet of the client that sends a command to the server and receives back the output:
char output[10240];
send(sock, command, strlen(command), MSG_NOSIGNAL);
if((message_read = read(sock, output, sizeof(output)))>0){
output[message_read] = '\0';
//print the output somewhere
}
While the commands like "ls -al", "pwd", or "whoami" give the output in one go, the client fails to read whole of the output produced by commands like "ping", "ps" or "du". However when I call the above snippet multiple times, it gets me the rest of the output produced by the above commands(in chunks.)
I tried to modify the client function as follows:
send(sock, command, strlen(command), MSG_NOSIGNAL);
do{
if((message_read = read(sock, output, sizeof(output))) > 0){
output[message_read] = '\0';
//print the output somewhere
}
}while(message_read);
The above solution hanged the client program. However, after I killed the server, the outputs did show up in the client's window!
Also, this time the output was all scattered and poorly indented.
Q1. What's happening?
Q2. How to solve it?
The way your code is sending and reading strings is not sufficient.
TCP is a byte stream. There is no 1-to-1 relationship between sends and reads. As such, the sender MUST either:
send the string length before sending the string's data.
send a unique terminator after the string data.
And the receiver MUST either:
read the length then read the specified amount of data.
read until the terminator is reached.
Also, send()/write() and recv()/read() can return fewer bytes than requested, so they need to be called in loops (or, in the case of recv(), you can use the MSG_WAITALL flag).
Try something more like this instead:
// common functions ...
bool sendRaw(int sock, void *data, size_t len)
{
char *ptr = (char*) data;
while (len > 0) {
int sent = send(sock, ptr, len, MSG_NOSIGNAL);
if (sent < 0) return false;
ptr += sent;
len -= sent;
}
return true;
}
int recvRaw(int sock, void *data, size_t len)
{
char *ptr = (char*) data;
while (len > 0) {
int recvd = recv(sock, ptr, len, MSG_NOSIGNAL);
if (recvd <= 0) return recvd;
ptr += recvd;
len -= recvd;
}
return 1;
}
bool sendUInt32(int sock, uint32_t value)
{
value = htonl(value);
return sendRaw(sock, &value, sizeof(value));
}
uint32_t recvUInt32(int sock)
{
uint32_t value;
if (recvRaw(sock, &value, sizeof(value)) <= 0) return -1;
return ntohl(value);
}
bool sendString(int sock, const char *str)
{
uint32_t len = strlen(str);
if (!sendUInt32(sock, len)) return false;
return sendRaw(sock, str, len);
/* alternatively:
return sendRaw(sock, str, strlen(len) + 1);
*/
}
/*
bool grow(char **str, size_t *cap, size_t stepBy)
{
size_t newcap = cap + stepBy;
char *newstr = (char*) realloc(*str, newcap);
if (!newstr) return false;
*str = newstr;
*cap = newcap;
return true;
}
*/
char* recvString(int sock)
{
uint32_t len = recvUInt32(sock);
if (len == -1) return NULL;
char *str = (char*) malloc(len+1);
if (!str) return NULL;
if (recvRaw(sock, str, len) <= 0){
free(str);
return NULL;
}
str[len] = '\0';
return str;
/* alternatively:
char ch, *str = NULL;
size_t len = 0, cap = 0;
do{
if (recvRaw(sock, &ch, 1) <= 0){
free(str);
return NULL;
}
if (ch == '\0') break;
if (len == cap){
if (!grow(&str, &cap, 256)){
free(str);
return NULL;
}
}
str[len++] = ch;
}
while (1);
if (len == cap){
if (!grow(&str, &cap, 1)){
free(str);
return NULL;
}
}
str[len] = '\0';
return str;
*/
}
// server ...
char *command;
while ((command = recvString(sock)) != NULL){
// ...
system(command);
free(command);
// read from command's stdout until finished ...
if (!sendString(sock, output, outputLength)) break;
}
// client ...
if (sendString(sock, command)){
char *output = recvString(sock);
if (output){
//print the output somewhere
free(output);
}
}
Alternatively, if you don't know the length of the command's response ahead of time, and/or don't want to buffer it all in a single memory buffer, then you can read it in chunks, sending each chunk as you go, eg:
// common functions, see above ...
typedef struct _chunk
{
uint8_t size;
char data[256];
} chunk;
bool sendChunk(int sock, const chunk *chk)
{
uint8_t size = chk ? chk->size : 0;
if (!sendRaw(sock, &size, 1)) return false;
if (chk) return sendRaw(sock, chk->data, size);
return true;
}
bool recvChunk(int sock, chunk *chk)
{
if (recvRaw(sock, &(chk->size), 1) <= 0) return false;
if (chk->size) return recvRaw(sock, chk->data, chk->size);
return true;
}
// server ...
bool sendOutput(int sock)
{
chunk chk;
int size;
do{
// read from command's stdout ...
size = read(..., chk.data, sizeof(chk.data));
if (size <= 0) break;
chk.size = (uint8_t) size;
if (!sendChunk(sock, &chk)) return false;
}
while(1);
// tell client the data is finished ...
return sendChunk(sock, NULL);
}
char *command;
while ((command = recvString(sock)) != NULL){
// ...
system(command);
free(command);
if (!sendOutput(sock)) break;
}
// client ...
if (sendString(sock, command)){
chunk chk;
do{
if (!recvChunk(sock, &chk)) break;
if (chk.size == 0) break;
//print the chk.data somewhere
}
while (1);
}
I want to implement a simple TCP server with blocking read, that receives messages sent from a client character by character until a separator. Once a message is received, it has to wait until the next message appears. Here is my pseudocode:
// Messages sent from the client
char *message1 = "mssg1\n"
char *message2 = "mssg2\n"
// On server side
char buffer;
char completeMessage[5]
while(1){
while(buffer != '\n'){
recv(sock, &buffer, 1, 0); // 1 is the read size
if(buffer != '\n') {
printf("buffer: %c\n", buffer);
completeMessage[n] = buffer;
count ++;
}
else{
printf("Complete message: %s\n", completeMessage);
count = 0;
}
}
}
And the result is the following:
buffer: m
buffer: s
buffer: s
buffer: g
buffer: 1
Complete message: mssg1
buffer:
buffer:
buffer:
buffer:
buffer:
buffer:
// Error due to buffer overflow
I don't know why recv instead of waiting for the next message character (blocking read), it continues reading blank spaces. My questions are the following:
Is recv really a socket blocking read function?
Is there something wrong or missing in the code?
Any other suggestions for implementing this?
Is recv really a socket blocking read function?
Yes, unless you made the handle non-blocking.
Is there something wrong or missing in the code?,
You're not checking what recv returns. 0 indicates EOF, and -1 indicates an error.
You don't check how full your buffer is, so you risk buffer overflows.
You're not terminating the string in completeMessage with a NUL as required by printf %s.
Any other suggestions for implementing this?
You shouldn't read a character at a time!
#define BUFFER_SIZE (64*1024)
char* extract_string(const char* start, const char* end) {
size_t len = end - start;
char* dst = malloc(len+1);
if (dst == NULL)
return NULL;
memcpy(dst, src, len);
dst[len] = '\0';
return dst;
}
{
char buf_start[BUFFER_SIZE];
char* buf_end = buf_start + BUFFER_SIZE;
char* window_start = buf_start;
char* window_end = buf_start;
while (1) {
if (window_end == buf_end) { // No more space.
fprintf(stderr, "Overly large message");
return 0;
}
ssize_t rv = recv(sock, window_end, buf_end-window_end, 0);
if (rv == -1) { // Error.
perror("recv");
return 0;
}
if (rv == 0) { // EOF.
return 1;
}
while (rv--) {
if (*(window_end++) == '\n') {
char* msg = extract_string(window_start, window_end-1); // Excl LF.
if (msg == NULL) {
fprintf(stderr, "Out of memory");
return 0;
}
// Do something with msg
printf("Complete message: %s\n", msg);
free(msg);
window_start = window_end;
}
}
memmove(buf_start, window_start, window_end-window_start);
window_end -= (window_start - buf_start);
window_start = buf_start;
}
}
There are quite a number of problems with your code, namely that you are ignoring the return value of recv(), you are not null-terminating your buffer before printing it, and you are not protecting yourself from a buffer overflow.
Try something more like this instead:
char ch, *tmp, *message = NULL;
int ret, length = 0, allocated = 0;
while (1)
{
ret = recv(sock, &ch, 1, 0);
if (ret <= 0)
{
if (ret < 0)
printf("Read error: %d\n", errno); // or WSAGetLastError() on Windows
else
printf("Client disconnected\n");
break;
}
if (ch == '\n')
{
if ((length > 0) && (message[length-1] == '\r'))
--length;
printf("Complete message: '%.*s'\n", length, message);
length = 0;
}
else
{
printf("ch: %c\n", ch);
if (length == allocated)
{
if (length >= 5000) // some max length of your choosing...
{
printf("Message length too large!\n");
break;
}
// just for example. You should use a more robust growth algorithm in production code...
tmp = (char*) realloc(message, allocated + 10);
if (!tmp)
{
printf("Memory allocation failed\n");
break;
}
message = tmp;
allocated += 10;
}
message[length] = ch;
++length;
}
}
free(message);
Alternatively, don't read char-by-char. Read as much data as you can from the socket on any given read and store it all in a growing buffer, and then scan that buffer for complete messages, eg:
char *buffer = (char*) malloc(100);
if (!buffer)
{
printf("Memory allocation failed\n");
}
else
{
int ret, offset, remaining, inbuf = 0, allocated = 100;
char *ptr;
while (1)
{
if (inbuf == allocated)
{
if (inbuf >= 5000) // some max length of your choosing...
{
printf("Buffer length too large!\n");
break;
}
// just for example. You should use a more robust growth algorithm in production code...
tmp = (char*) realloc(buffer, allocated + 100);
if (!tmp)
{
printf("Memory allocation failed\n");
break;
}
buffer = tmp;
allocated += 100;
}
ret = recv(sock, buffer+inbuf, allocated-inbuf, 0);
if (ret <= 0)
{
if (ret < 0)
printf("Read error: %d\n", errno); // or WSAGetLastError() on Windows
else
printf("Client disconnected\n");
break;
}
printf("Received: %.*s\n", ret, buffer+inbuf);
inbuf += ret;
while (ptr = (char*)memchr(buffer, '\n', inbuf))
{
offset = (ptr-buffer);
if ((offset > 0) && (buffer[offset-1] == '\r'))
--offset;
printf("Complete message: '%.s'\n", offset, buffer);
++ptr;
remaining = (inbuf - (ptr - buffer));
if (remaining > 0)
memmove(buffer, ptr, remaining);
inbuf = remaining;
}
}
free(buffer);
}
I have an assignment in which a TCP client sends data to the TCP server in the form of:
IP_address\0port\0message\n
Now, the server (IP address 10.0.2.15) receives the packet fine when I send some data through a terminal like this:
printf "127.0.0.1\0004444\000Some message\n" | nc -N 10.0.2.15 3333
However, the second part of the assignment is to read a packet that comes in multiple segments:
(printf "127.0.0.1"; sleep 0.3; printf "\0004444\000"; sleep 0.3; \
printf "It works"; sleep 0.5; printf "\n") | nc -N 10.0.2.15 3333
How should I implement the read function on the server so that, if possible, all the segments are stored into a buffer?
The number of bytes recv() returns can be as few as 1 byte up to as many bytes as requested. TCP is a byte stream, it has no concept of messages, that has to be handled in the application code instead.
The receiver must know how many bytes to expect, and then keep reading in a loop until it has read that many bytes, however many reads it takes.
However, in this situation, the receiver does not know the exact length of the message, because the sender is not sending the message length before sending the message itself, so the only option available is for the receiver to read from the socket byte-by-byte until it encounters the terminating \n.
For example:
int readLine(int socket, char **line)
{
int r, len = 0, cap = 256;
char b;
*line = NULL;
char *outline = (char*) malloc(cap);
if (!outline) return -2;
do
{
r = recv(socket, &b, 1, 0);
if (r <= 0)
{
free(outline);
return r;
}
if (b == '\n')
break;
if (len == cap)
{
cap += 256;
char *newline = (char*) realloc(outline, cap);
if (!newline)
{
free(outline);
return -2;
}
outline = newline;
}
outline[len] = b;
++len;
}
while (true);
if ((len > 0) && (line[len-1] == '\r'))
--len;
if (len == cap)
{
char *newline = (char*) realloc(outline, cap + 1);
if (!newline)
{
free(outline);
return -2;
}
outline = newline;
}
outline[len] = '\0';
*line = outline;
return 1;
}
char *line;
int r;
do
{
r = readLine(cliSock, &line);
if (r <= 0)
{
if (r == 0)
printf("client disconnected\n");
else if (r == -2)
printf("memory error\n");
else
printf("read error\n");
break;
}
// process line as needed...
free(line);
}
while (true);
Alternatively, you can use an intermediate buffer to help you cache data between reads and get data out of the socket more efficiently:
char *buffer;
int buflen, bufcap;
int readLine(int socket, char **line)
{
char *ptr;
int r, idx = 0;
*line = NULL;
do
{
ptr = memchr(buffer + idx, '\n', buflen - idx);
if (ptr)
{
int total = ((ptr + 1) - buffer);
int len = (total - 1);
if ((len > 0) && (buffer[len-1] == '\r'))
--len;
*line = (char*) malloc(len + 1);
if (*line == NULL)
return -2;
memcpy(*line, buffer, len);
(*line)[len] = '\0';
if (total < buflen)
memmove(buffer, buffer + total, buflen - total);
buflen -= total;
break;
}
if (buflen == bufcap)
{
int newcap = bufcap + 256;
char *newbuffer = (char*) realloc(buffer, newcap);
if (!newbuffer)
return -2;
buffer = newbuffer;
bufcap = newcap;
}
r = recv(socket, buffer + buflen, bufcap - buflen, 0);
if (r <= 0)
return r;
buflen += r;
}
while (true);
return 1;
}
buflen = 0;
bufcap = 256;
buffer = (char*) malloc(bufcap);
if (buffer)
{
char *line;
int r;
do
{
r = readLine(cliSock, &line);
if (r <= 0)
{
if (r == 0)
printf("client disconnected\n");
else if (r == -2)
printf("memory error\n");
else
printf("read error\n");
break;
}
// process line as needed...
free(line);
}
while (true);
free(buffer);
}
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.
I want to write a function that read line by line from a socket buffer obtained from third parameter from read() function from unistd.h header.
I have wrote this:
int sgetline(int fd, char ** out)
{
int buf_size = 128;
int bytesloaded = 0;
char buf[2];
char * buffer = malloc(buf_size);
char * newbuf;
int size = 0;
assert(NULL != buffer);
while( read(fd, buf, 1) > 0 )
{
strcat(buffer, buf);
buf[1] = '\0';
bytesloaded += strlen(buf);
size = size + buf_size;
if(buf[0] == '\n')
{
*out = buffer;
return bytesloaded;
}
if(bytesloaded >= size)
{
size = size + buf_size;
newbuf = realloc(buffer, size);
if(NULL != newbuf)
{
buffer = newbuf;
}
else
{
printf("sgetline() allocation failed!\n");
exit(1);
}
}
}
*out = buffer;
return bytesloaded;
}
but I have some problems with this function, for example, if the input is something like:
HTTP/1.1 301 Moved Permanently\r\n
Cache-Control:no-cache\r\n
Content-Length:0\r\n
Location\r\nhttp://bing.com/\r\n
\r\n\r\n
and I do
int sockfd = socket( ... );
//....
char* tbuf;
while(sgetline(sockfd, &tbuf) > 0)
{
if(strcmp(tbuf,"\r\n\r\n") == 0)
{
printf("End of Headers detected.\n");
}
}
the above C application does not output "End of Header detected.". Why is this, and how can I fix this?
It's not OK to read one byte at a time, because you are making too many system calls - better is to use a buffer, read a chunk and check if you got \n. After getting a line, the rest of the bytes read remains in the buffer, so you cannot mix read/recv with read_line. Another version of read n bytes using this kind of buffer can be write...
My version to read a line, and a little example to use it.
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#define CBSIZE 2048
typedef struct cbuf {
char buf[CBSIZE];
int fd;
unsigned int rpos, wpos;
} cbuf_t;
int read_line(cbuf_t *cbuf, char *dst, unsigned int size)
{
unsigned int i = 0;
ssize_t n;
while (i < size) {
if (cbuf->rpos == cbuf->wpos) {
size_t wpos = cbuf->wpos % CBSIZE;
//if ((n = read(cbuf->fd, cbuf->buf + wpos, (CBSIZE - wpos))) < 0) {
if((n = recv(cbuf->fd, cbuf->buf + wpos, (CBSIZE - wpos), 0)) < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (n == 0)
return 0;
cbuf->wpos += n;
}
dst[i++] = cbuf->buf[cbuf->rpos++ % CBSIZE];
if (dst[i - 1] == '\n')
break;
}
if(i == size) {
fprintf(stderr, "line too large: %d %d\n", i, size);
return -1;
}
dst[i] = 0;
return i;
}
int main()
{
cbuf_t *cbuf;
char buf[512];
struct sockaddr_in saddr;
struct hostent *h;
char *ip;
char host[] = "www.google.com";
if(!(h = gethostbyname(host))) {
perror("gethostbyname");
return NULL;
}
ip = inet_ntoa(*(struct in_addr*)h->h_addr);
cbuf = calloc(1, sizeof(*cbuf));
fprintf(stdout, "Connecting to ip: %s\n", ip);
if((cbuf->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(80);
inet_aton(ip, &saddr.sin_addr);
if(connect(cbuf->fd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
perror("connect");
return 1;
}
snprintf(buf, sizeof(buf), "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", host);
write(cbuf->fd, buf, strlen(buf));
while(read_line(cbuf, buf, sizeof(buf)) > 0) {
// if it's an empty \r\n on a line, header ends //
if(buf[0]=='\r' && buf[1] == '\n') {
printf("------------------------\n");
}
printf("[%s]", buf);
}
close(cbuf->fd);
free(cbuf);
return 0;
}
Try this implementation instead:
int sgetline(int fd, char ** out)
{
int buf_size = 0;
int in_buf = 0;
int ret;
char ch;
char * buffer = NULL;
char * new_buffer;
do
{
// read a single byte
ret = read(fd, &ch, 1);
if (ret < 1)
{
// error or disconnect
free(buffer);
return -1;
}
// has end of line been reached?
if (ch == '\n')
break; // yes
// is more memory needed?
if ((buf_size == 0) || (in_buf == buf_size))
{
buf_size += 128;
new_buffer = realloc(buffer, buf_size);
if (!new_buffer)
{
free(buffer);
return -1;
}
buffer = new_buffer;
}
buffer[in_buf] = ch;
++in_buf;
}
while (true);
// if the line was terminated by "\r\n", ignore the
// "\r". the "\n" is not in the buffer
if ((in_buf > 0) && (buffer[in_buf-1] == '\r'))
--in_buf;
// is more memory needed?
if ((buf_size == 0) || (in_buf == buf_size))
{
++buf_size;
new_buffer = realloc(buffer, buf_size);
if (!new_buffer)
{
free(buffer);
return -1;
}
buffer = new_buffer;
}
// add a null terminator
buffer[in_buf] = '\0';
*out = buffer; // complete line
return in_buf; // number of chars in the line, not counting the line break and null terminator
}
int sockfd = socket( ... );
//....
char* tbuf;
int ret;
// keep reading until end of headers is detected.
// headers are terminated by a 0-length line
do
{
// read a single line
ret = sgetline(sockfd, &tbuf);
if (ret < 0)
break; // error/disconnect
// is it a 0-length line?
if (ret == 0)
{
printf("End of Headers detected.\n");
free(tbuf);
break;
}
// tbuf contains a header line, use as needed...
free(tbuf);
}
while (true);
You are making things more difficult for yourself than they need to be. You really don't need to do strcats to get the single character you read on each read added at the current position.
But your bug is that the routine returns as soon as it sees a \n, so the string it returns can never contain anything following the first \n.