In c socket, why my server can't receive the whole content? - c

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);

Related

C Programming Sending File over Socket issues

I'm trying to setup a client and a server program where the client can download a file from the server.
I have got the client to download a .txt file however, the .txt file is only created, none of the data within it is transferred.
I've figured out the error is either from when the server sends the size of the file to the client OR the client is failing to recognize where the EOF is.
This is my server side code:
int send_all(int clientSocket, const void *buffer, int len) {
const char *pbuf = (const char *) buffer;
while(len > 0) {
int sent = send(clientSocket, pbuf, len, 0);
printf("%d\n", sent);
if(sent < 1) {
printf("%s\n", "Can't write to socket");
return -1;
}
pbuf += sent;
len -= sent;
}
return(0);
}
void SendFileToClient(int clientSocket, char* fileName) {
//int offset;
char buffer[0x1000];
char temp[512] = "/root/Documents/";
const char* filename = fileName;
struct stat s;
strcat(temp, filename);
printf("%s\n", temp);
//append the filenameonto the directory
if(stat(temp, &s) == -1) {
printf("%s\n", "Can't get file info");
return;
}
FILE *file = fopen(temp, "rb");
if(!file) {
printf("%s\n", "Can't open the file for reading");
return;
}
off_t size = s.st_size;
off_t tmp_size = ntohl(size);
if(send_all(clientSocket, &tmp_size, sizeof(tmp_size)) == 0) {
while(size > 0) {
printf("%ld\n", size);
int rval = fread(buffer, 1, min(sizeof(buffer), size), file);
if(rval < 1) {
printf("%s\n", "Cant read from file");
break;
}
if (send_all(clientSocket, buffer, rval) == -1)
break;
size -= rval;
}
}
fclose(file);
}
This is my client side code:
int write_all(FILE *file, const void *buffer, int len) {
const char *pbuf = (const char *) buffer;
while (len > 0) {
int written = fwrite(pbuf, 1, len, file);
if(written < 1) {
printf("%s\n", "Cant write to file");
return -1;
}
pbuf += written;
len -= written;
}
return 0;
}
int read_all(int clientSocket, void *buffer, int len) {
char *pbuf = (char *) buffer;
int total = 0;
while (len > 0) {
int rval = recv(clientSocket, pbuf, len, 0);
if(rval < 0) {
printf("%s\n", "Cant read from socket");
return -1;
}
if(rval == 0) {
printf("%s\n", "Socket disconnected");
return 0;
}
pbuf += rval;
len -= rval;
total += rval;
}
return total;
}
void RecvFile(int clientSocket, const char* filename) {
int rval;
char buffer[0x1000];
//printf("%s\n", filename);
FILE *file = fopen(filename, "wb");
if(!file) {
printf("%s\n", "Cant open file for writing");
return;
}
off_t size = 0;
//this statement is not working
if(read_all(clientSocket, &size, sizeof(size)) == 1) {
size = ntohl(size);
printf("%s%ld\n", "size: ", size);
while(size > 0) {
rval = read_all(clientSocket, buffer, min(sizeof(buffer), size));
printf("%s%d\n", "rval: ", rval);
if(rval < 1)
break;
if(write_all(file, buffer, rval) == -1) {
printf("%s\n", "Cant write to file");
break;
}
}
}
printf("%s\n", "closing file...");
fclose(file);
}
My apologies if the code looks a bit messy - for some reason it didn't paste in with nice formatting.
I believe this is where part of the problem is, but even through various debugging I can't seem to get it to work :(
//this statement is not working
if(read_all(clientSocket, &size, sizeof(size)) == 1) {
Thanks in advance for any help!

why \n stop the next send socket? C

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);
}

tcp server blocking read in C

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);
}

C- Socket : Programming a Client/Server-Application to send a file

I want to program an application to send a file with sockets:
Here my Server:
void str_server(int sock)
{
char buf[1025];
const char* filename="test.text";
FILE *file = fopen(filename, "rb");
err_abort("Test");
while (!feof(file))
{
int rval = fread(buf, 1, sizeof(buf), file);
send(sock, buf, rval, 0);
}
}
and here my client:
void RecvFile(int sock, const char* filename)
{
int rval;
char buf[0x1000];
FILE *file = fopen(filename, "wb");
while ((rval = recv(sock, buf, sizeof(buf), 0)) > 0)
{
fwrite(buf, 1, rval, file);
}
close(sock);
}
My problem is that my client create a file....but dont write the content in the file!
Add some error handling to your code, that should help you track down the problem. Also note that send(), recv(), fread() and fwrite() are not guaranteed to write/read the entire buffer you specify, so you should take that into account as well.
Also, since TCP is a byte stream, the server needs to indicate when the file ends so the client knows when to stop reading. If you don't send the file size before sending the actual file, the only option is to close the socket when the transfer is done.
Try something like this:
int send_all(int sock, const void *buf, int len)
{
const char *pbuf = (const char *) buf;
while (len > 0)
{
int sent = send(sock, pbuf, len, 0);
if (sent < 1)
{
// if the socket is non-blocking, then check
// the socket error for WSAEWOULDBLOCK/EAGAIN
// (depending on platform) and if true then
// use select() to wait for a small period of
// time to see if the socket becomes writable
// again before failing the transfer...
printf("Can't write to socket");
return -1;
}
pbuf += sent;
len -= sent;
}
return 0;
}
void str_server(int sock)
{
char buf[0x1000];
const char* filename = "test.text";
struct stat s;
if (stat(filename, &s) == -1)
{
printf("Can't get file info");
return;
}
FILE *file = fopen(filename, "rb");
if (!file)
{
printf("Can't open file for reading");
return;
}
// if you need to handle files > 2GB,
// be sure to use a 64bit integer, and
// a host-to-network function that can
// handle 64bit integers...
long size = s.st_size;
long tmp_size = htonl(size);
if (send_all(sock, &tmp_size, sizeof(tmp_size)) == 0)
{
while (size > 0)
{
int rval = fread(buf, 1, min(sizeof(buf), size), file);
if (rval < 1)
{
printf("Can't read from file");
break;
}
if (send_all(sock, buf, rval) == -1)
break;
size -= rval;
}
}
fclose(file);
}
int write_all(FILE *file, const void *buf, int len)
{
const char *pbuf = (const char *) buf;
while (len > 0)
{
int written = fwrite(pbuf, 1, len, file);
if (written < 1)
{
printf("Can't write to file");
return -1;
}
pbuf += written;
len -= written;
}
return 0;
}
int read_all(int sock, void *buf, int len)
{
char *pbuf = (char *) buf;
int total = 0;
while (len > 0)
{
int rval = recv(sock, pbuf, len, 0);
if (rval < 0)
{
// if the socket is non-blocking, then check
// the socket error for WSAEWOULDBLOCK/EAGAIN
// (depending on platform) and if true then
// use select() to wait for a small period of
// time to see if the socket becomes readable
// again before failing the transfer...
printf("Can't read from socket");
return -1;
}
if (rval == 0)
{
printf("Socket disconnected")
return 0;
}
pbuf += rval;
len -= rval;
total += rval;
}
return total;
}
void RecvFile(int sock, const char* filename)
{
int rval;
char buf[0x1000];
FILE *file = fopen(filename, "wb");
if (!file)
{
printf("Can't open file for writing");
return;
}
// if you need to handle files > 2GB,
// be sure to use a 64bit integer, and
// a network-to-host function that can
// handle 64bit integers...
long size = 0;
if (read_all(sock, &size, sizeof(size)) == 1)
{
size = ntohl(size);
while (size > 0)
{
rval = read_all(sock, buf, min(sizeof(buf), size));
if (rval < 1)
break;
if (write_all(file, buf, rval) == -1)
break;
}
}
fclose(file);
}

Read line by line from a socket buffer

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.

Resources