I've tried to put \0 at the end of the message, but that didn't work. I've also put the terminating char when receiving the socket in the client side but that didn't work either. Here is an image of the console:
server side:
char u[BUFFER]
char *msg = "You are required to enter username:\n\n";
send(clie, msg, strlen(msg), 0);
// not shown on console
char *u_msg = "Username: ";
send(clie, u_msg, strlen(u_msg), 0);
recv(clie, u, sizeof(u), 0);
client-side
char srecv[BUFFER]; // BUFFER = 1024
while (1) {
bytes = recv(ser, & srecv, BUFFER, 0);
srecv[bytes] = '\0';
printf("%s", srecv);
scanf("%s", ssend);
if (send(ser, ssend, strlen(ssend), 0) == -1) {
perror("send\n");
exit(1);
}
}
Since there are multiple '\n' characters in your server's messaging, that is not sufficient to let the client know when each message has finished being received. You should either:
send a message's length before sending the actual message.
send a unique terminator at the end of each message (in your example, the null terminator will suffice).
Either way will allow the client to keep reading and displaying a message's bytes to the console until the true end of message has been reached, BEFORE then reading the user's response from the console. The client MUST wait to receive both messages in their entirety before then calling scanf().
There is no 1:1 relationship between send() and recv() in TCP, you MUST be prepared to handle that. Both functions MAY return fewer bytes than requested, so both functions must be called in loops until all expected bytes are sent/received. And messages MUST be explicitly framed by the sender in such a way that the receiver knows when a message actually ends.
Try something more like this instead:
Common code for both sides:
int sendAll(int sckt, const void *data, size_t size)
{
const char *pdata = (const char*) data;
while (size > 0)
{
ssize_t sent = send(sckt, pdata, size, 0);
if (sent < 0) return -1;
pdata += sent;
size -= sent;
}
return 0;
}
int recvAll(int sckt, void *data, size_t size)
{
char *pdata = (char*) data;
while (size > 0)
{
ssize_t recvd = recv(sckt, pdata, size, 0);
if (recvd <= 0) return recvd;
pdata += recvd;
size -= recvd;
}
return 1;
}
int sendMsg(int sckt, const char *msg)
{
uint32_t msglen = strlen(msg);
uint32_t temp = htonl(msglen);
int ret = sendAll(sckt, &temp, sizeof(temp));
if (ret == 0) ret = sendAll(sckt, msg, msglen);
return ret;
}
int recvMsg(int sckt, char **msg)
{
*msg = NULL;
uint32_t msglen = 0;
int ret = recvAll(sckt, &msglen, sizeof(msglen));
if (ret <= 0) return ret;
msglen = ntohl(msglen);
char *pmsg = (char*) malloc(msglen+1);
if (!pmsg) return NULL;
if (msglen > 0)
{
ret = recvAll(sckt, pmsg, msglen);
if (ret <= 0)
{
free(pmsg);
return ret;
}
}
pmsg[msglen] = '\0';
*msg = pmsg;
return 1;
}
Alternatively:
int sendMsg(int sckt, const char *msg)
{
if (!msg) msg = "\0";
int size = strlen(msg) + 1;
do
{
ssize_t sent = send(sckt, msg, size, 0);
if (sent < 0) return -1;
msg += sent;
size -= sent;
}
while (size > 0);
return 0;
}
int recvMsg(int sckt, char **msg)
{
char c, buf[1024];
int inbuf = 0;
char *pmsg = NULL;
int msglen = 0;
*msg = NULL;
do
{
ssize_t ret = recv(sckt, &c, 1, 0);
if (ret <= 0)
{
if (pmsg) free(pmsg);
return ret;
}
if (c == '\0')
break;
if (inbuf == sizeof(buf))
{
char *newmsg = (char*) realloc(msg, msglen + inbuf + 1);
if (!newmsg)
{
if (pmsg) free(pmsg);
return -1;
}
memcpy(buf, &newmsg[msglen], inbuf);
newmsg[msglen + inbuf] = '\0';
pmsg = newmsg;
msglen += inbuf;
inbuf = 0;
}
buf[inbuf] = c;
++inbuf;
}
while (1);
if ((inbuf > 0) || (msglen == 0))
{
char *newmsg = (char*) realloc(msg, msglen + inbuf + 1);
if (!newmsg)
{
if (pmsg) free(pmsg);
return -1;
}
if (inbuf > 0) memcpy(buf, &newmsg[msglen], inbuf);
newmsg[msglen + inbuf] = '\0';
pmsg = newmsg;
}
*msg = pmsg;
return 1;
}
Server side:
sendMsg(clie, "You are required to enter username:\n\n");
sendMsg(clie, "Username: ");
char *u;
if (recvMsg(clie, &u) == 1)
{
...
free(u);
}
Client side:
char *msg;
while (1) {
ret = recvMsg(ser, &msg);
if (ret <= 0)
{
if (ret < 0)
{
perror("recvMsg\n");
exit(1);
}
break;
}
printf("%s", msg);
if (strcmp(msg, "Username: ") == 0)
{
scanf("%s", ssend);
if (sendMsg(ser, ssend) == -1)
{
perror("sendMsg\n");
exit(1);
}
}
free(msg);
}
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 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 am new in this field, and writing one server and client, but it really confusing that I can't get all the content, but some small clip.
My server code:
read(connfd, name, 20);
//recv(connfd,name,1024,0);
char* a=name;
while(a[0]!='\n'){
a++;
}
a[0]='\0';
printf("name:%s\n", name);
read(connfd, size, 20);
printf("size:%s\n", size);
recv(connfd,buf,8192,0);
printf("buf:%s\n", buf);
if((stream = fopen(name,"w+t"))==NULL){
printf("The file was not opened! \n");
}
int write_length = fwrite(buf,sizeof(char),8192,stream);
bzero(buf,8192);
if(put){
char *res="OK\n";
write(connfd, res, 1024);
}
fclose(stream);
and my client code is:
char buffer[8192];
bzero(buffer,8192);
char * put="PUT\n";
if ((write(fd, put, 8192)) <= 0) {
if (errno != EINTR) {
fprintf(stderr, "Write error: %s\n", strerror(errno));
exit(0);
}
}
struct stat st ;
stat( put_name, &st );
char str[100];
sprintf(str, "%d", st.st_size);
int len;
char *current=NULL;
len=strlen(put_name);
char sendname[1024];
strcpy(sendname,put_name);
strcat(sendname,"\n");
write(fd, sendname, 10);
strcat(str,"\n");
write(fd, str, 10);
FILE *stream;
if((stream = fopen(put_name,"r"))==NULL)
{
printf("The file was not opened! \n");
exit(1);
}
int lengsize = 0;
while((lengsize = fread(buffer,1,8192,stream)) > 0){
if(send(fd,buffer,8192,0)<0){
printf("Send File is Failed\n");
break;
}
bzero(buffer, 8192);
}
Now, I can send all content, but can receive part of them. for example, on my mac, server can receive name but the str is neglected, when I printf the str in the server, it shows the content of file. and the content of file is not the whole file content. Some content disappear. Could you tell me why?
The read and write functions are not guaranteed to send or receive the entire message with a single call. Instead, you're expected to sit in a loop, writing the message incrementally until everything has been sent and reading everything incrementally until everything has been read. For example, if you know exactly how much has been sent, you can do this:
char recvBuffer[BUFFER_SIZE];
int bytesRead = 0;
while (bytesRead < BUFFER_SIZE) {
int readThisTime = read(file, recvBuffer + bytesRead, BUFFER_SIZE - bytesRead);
if (readThisTime == -1) {
// handle error...
}
bytesRead += readThisTime;
}
If you don't know exactly how much has been sent, try this:
char recvBuffer[BUFFER_SIZE];
int bytesRead = 0;
while (bytesRead < BUFFER_SIZE) {
int readThisTime = read(file, recvBuffer + bytesRead, BUFFER_SIZE - bytesRead);
if (readThisTime == -1) {
// handle error...
}
if (readThisTime == 0) break; // Done!
bytesRead += readThisTime;
}
You are ignoring the return values of send() and recv(). You MUST check return values!
When sending the file, lengsize receives how many bytes were actually read from the file. Your client is sending too many bytes when lengsize is < 8192 (typically the last block of the file if the file size is not an even multiple of 8192).
But more importantly, although the client is telling the server the file size, the server is ignoring it to know when to stop reading. The server is also ignoring the return value of recv() to know how many bytes were actually received so it knows how many bytes can safely be written to the output file.
Try something more like this instead:
common:
int readData(int s, void *buf, int buflen)
{
int total = 0;
char *pbuf = (char*) buf;
while (buflen > 0) {
int numread = recv(s, pbuf, buflen, 0);
if (numread <= 0) return numread;
pbuf += numread;
buflen -= numread;
total += numread;
}
return total;
}
int sendData(int s, void *buf, int buflen)
{
int total = 0;
char *pbuf = (char*) buf;
while (buflen > 0) {
int numsent = send(s, pbuf, buflen, 0);
if (numsent <= 0) return numsent;
pbuf += numsent;
buflen -= numsent;
total += numsent;
}
return total;
}
int readInt32(int s, int32_t *value)
{
int res = readData(s, value, sizeof(*value));
if (res > 0) *value = ntohl(*value);
return res;
}
int sendInt32(int s, int32_t value)
{
value = htonl(value);
return sendData(s, &value, sizeof(value));
}
char* readStr(int s)
{
int32_t size;
if (readInt32(s, &size) <= 0)
return NULL;
char *str = malloc(size+1);
if (!str)
return NULL;
if (readData(s, str, size) <= 0) {
free(str);
return NULL;
}
str[size] = '\0';
return str;
}
int sendStr(int s, const char *str)
{
int len = strlen(str);
int res = sendInt32(s, len);
if (res > 0)
res = sendData(s, str, len);
return res;
}
server:
char buffer[8192];
char *name = readStr(connfd);
if (!name) {
// error handling ...
sendStr(connfd, "Socket read error");
return;
}
printf("name:%s\n", name);
int32_t filesize;
if (readInt32(connfd, &filesize) <= 0) {
// error handling ...
free(name);
sendStr(connfd, "Socket read error");
return;
}
printf("size:%d\n", filesize);
if ((stream = fopen(name, "wb")) == NULL) {
// error handling ...
printf("The file was not opened!\n");
free(name);
sendStr(connfd, "File not opened");
return;
}
while (filesize > 0) {
int numread = readData(connfd, buf, min(filesize, sizeof(buffer)));
if (numread <= 0) {
// error handling ...
close(stream);
free(name);
sendStr(connfd, "Socket read error");
return;
}
printf("buf:%.*s\n", numread, buf);
if (fwrite(buf, 1, numread, stream) != numread) {
// error handling ...
close(stream);
free(name);
sendStr(connfd, "File write error");
return;
}
filesize -= numread;
}
fclose(stream);
free(name);
sendStr(connfd, "OK");
client:
char buffer[8192];
struct stat st;
if (stat( put_name, &st ) != 0) {
// error handling ...
exit(0);
}
if ((stream = fopen(put_name, "rb")) == NULL) {
// error handling ...
printf("The file was not opened!\n");
exit(0);
}
if (sendStr(fd, put_name) <= 0) {
// error handling ...
close(stream);
exit(0);
}
int32_t filesize = st.st_size;
if (sendInt32(fd, filesize) <= 0) {
// error handling ...
close(stream);
exit(0);
}
int lengsize;
while (filesize > 0) {
lengsize = fread(buffer, 1, min(filesize , sizeof(buffer)), stream);
if (lengsize <= 0) {
printf("Read File Failed\n");
// error handling ...
close(stream);
exit(0);
}
if (sendData(fd, buffer, lengsize) <= 0) {
printf("Send File Failed\n");
// error handling ...
close(stream);
exit(0);
}
filesize -= lengsize;
}
close(stream);
char *resp = readStr(fd);
if (!resp) {
// error handling ...
exit(0);
}
if (strcmp(resp, "OK") == 0)
printf("Send File OK\n");
else
printf("Send File Failed: %s\n", resp);
free(resp);
I have a Server that contact another Server in order to obtain a file and then send it to the Client. I have write this piece of code (C - Linux) but only the first 4 bytes arrive to the Client.
Someone better than me can see the mistake?
Thank you very much
int Recv_file (int s, int f, char *ptr, size_t maxlen){
int const TIMEOUT = 60; /* 60 seconds */
size_t n;
ssize_t nread;
ssize_t nsend;
char c;
fd_set cset;
struct timeval tval;
int x;
for (n=1; n<maxlen; n++)
{
FD_ZERO(&cset);
FD_SET(s, &cset);
tval.tv_sec = TIMEOUT;
tval.tv_usec = 0;
x = select(FD_SETSIZE, &cset, NULL, NULL, &tval);
if (x==-1) {
perror("select() failed");
return -1; /* -1 = close connection with the client */
}
if (x>0) {
nread=recv(s, &c, 1, 0);
if (nread == 1)
{
*ptr++ = c;
nsend = Send(f,&c,1);
if (nsend != 1) {
return -1; /* close connection with the client */
}
}else if (nread == 0){
*ptr = 0;
return (-1); /* close connection with the client */
}
else
return (-1); /* close connection with the client */
}else{
printf("(It's been %d seconds and I have not received any response",TIMEOUT);
close(s);
return(-1); /* close connection with the client */
}
}
*ptr = 0;
return (n); /* n == maxlen */
}
with:
int Send(int s, char *ptr, size_t nbytes){
size_t nleft;
ssize_t nwritten;
for (nleft=nbytes; nleft > 0; )
{
nwritten = send(s, ptr, nleft, 0);
if (nwritten <=0)
return (nwritten);
else
{
nleft -= nwritten;
ptr += nwritten;
}
}
return (nbytes - nleft); /* number of bytes sent */
}
UPDATE:
While waiting for an answer I modified:
nread=recv(s, &c, 1, 0); in nread=recv(s, &c, sizeof(c), 0);
and
Send(f,&c,1); in Send(f,&c,sizeof(c));
But now I don't receive the last bytes of the file!
Thank you again!
You don't get the last byte because the for loop start at 1
for (n=1; n<maxlen; n++);
// it should be
for (n=0; n<maxlen; n++);
// or either
for (n=1; n<=maxlen; n++);
EDIT: Also, you could read as much bytes as are available not just one by one, in fact you are sending in the correct way,
For example:
int bLeft = maxlen;
while (bLeft > 0)
{
FD_ZERO(&cset);
FD_SET(s, &cset);
tval.tv_sec = TIMEOUT;
tval.tv_usec = 0;
x = select(FD_SETSIZE, &cset, NULL, NULL, &tval);
if (x > 0) {
nread=recv(s, ptr, bLeft , 0);
if (nread > 0)
{
nsend = Send(f, ptr, nread, 0);
if (nsend <= 0) {
return -1; /* close connection with the client */
}
ptr+ = nread;
bLeft -= nread;
}else if (nread == 0){
*ptr = 0;
return (-1); /* close connection with the client */
}
else
return (-1); /* close connection with the client */
}else{
printf("(It's been %d seconds and I have not received any response",TIMEOUT);
close(s);
return(-1); /* close connection with the client */
}
}
// I would not recommended this, you should adhere to a maxlen length
// assuming there is more space could lead to a memory Exception.
ptr++;
*ptr ='\0';
return (maxlen-bLeft);
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.