Byte ordering HTTP Client - c

I wrote a simple HTTP Client that can request data from a host.
I am using getaddrinfo(3). With "GET / HTTP/1.1" request I am able to download HTML page of a given http Host.
Here is a part of my code:
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // Internet Protocol (IP) socket
hints.ai_socktype = SOCK_STREAM; // TCP
int res = getaddrinfo("example.com", "http", &hints, &ai);
if (res != 0)
ERROR_EXIT("getaddrinfo: %s\n", gai_strerror(res));
int sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd < 0)
ERROR_EXIT("socket: %s\n", strerror(errno));
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)
ERROR_EXIT("connect: %s\n", strerror(errno));
FILE *sockfile = fdopen(sockfd, "r+");
if (sockfile == NULL)
ERROR_EXIT("fdopen: %s\n", strerror(errno));
// send a GET request to the server:
if (fputs("GET / HTTP/1.1\r\n\r\n", sockfile) == EOF)
ERROR_EXIT("fputs: %s\n", strerror(errno));
if (fflush(sockfile) == EOF)
ERROR_EXIT("fflush: %s\n", strerror(errno));
char buf[1024];
// print the reply:
while (fgets(buf, sizeof(buf), sockfile) != NULL)
fputs(buf, stdout);
fclose(sockfile);
return 0;
Downloading a HTML page works ok, but downloading PNG image for example "GET /image.png HTTP/1.1\r\n\r\n" gives something like this:
???????ݹh??DHDZ?yW]%?9a??J?6F?Ѧ?E???ݐTd?US?:)??I??M,?-????=??U??&???Nr? ???б???
b??]??8?6+?;??i䂢d?G?WA?rԺ?H[??]?Z5????g?{8??i\?qAC?#c??v.?rb??'<?T?????O?z?
q,yĜ?ŷZI???X??fM?l?Z??l:;M???ۦ?????c?\\?W6+???o?}_???紈A??GvG?p??6{??{%?????0?{?
%??ژ??l?$r<?????ft*'W?N?m߂Ҿ4??E?:^?#?&?%%
????Dw??Z?$??1?4?l%&2?f-5!?? ?E? 8...
I understand that this is a byte transfer and that I have to do byte ordering, but don't know where to begin.
I know that I need to use ntohl(3) and Big-endian order for PNG images. Can Someone give me directions what to look up and how to approach this?
Do I save this output to .png file and then do byte order or do I do before I create a .png file?

The problem is more complex than just "byte order".
The Good News is that byte order is probably irrelevant. The code you've got now should work just fine sending and receiving text strings with the server.
The Bad News is that you need to connect differently if you want to read binary data from the server. Several considerations:
It sounds like you probably don't need to worry about "POST", "PUT" ... or anything but "GET".
It also sounds like you don't need to worry about "MIME Types" or "uuencoding/decoding" - you'll just be reading the data as-is.
You should definitely read the Content-Type and Content-Length headers from the server's HTTP response.
Perhaps the simplest approach is just call fgetc() in a loop if you're reading binary data. Use fgets() for strings, and fgetc() for Content-Length bytes to read the image.
You'll probably want to do an fopen() to write the image bytes to a file.
But calling fgets() eliminates the need to worry about "byte order".
Look here, here and here for examples.
'Hope that helps...

It wouldn't make a whole lot of sense to write out the image's bytes before deciding which order they should be in. What would be your next line be, fopen()?
If you're doing something architecture dependent like this, it's best to guard the case-specific sections with #if/#elif, and use #else to emit #error with a diagnostic message.
If everything is supposed to still work, it can just be #warning... but do put the warning in. For later, when it stops working.
Configuration macros you can test should include #ifdef __BIG_ENDIAN__ and #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; you can see what else is available by 'compiling' with -E -dD. [there will be a lot; grep is your friend]
Oh, and "big endian" means if you're reading bytes in address order (a[0], a[1], a[2], ...), then the byte at this end is the most significant/highest 'digit'. Hence "big-endian".
edit: this may also be of concern...
FILE *sockfile = fdopen(sockfd, "r+"); // "r+"
if (sockfile == NULL)
ERROR_EXIT("fdopen: %s\n", strerror(errno));
if (fputs("GET / HTTP/1.1\r\n\r\n", sockfile) == EOF) // fputs...

Related

Send http URL through C code using visual studios in Windows

I have a video encoder which can start/stop recording by sending a URL.
Start recording: http://192.168.1.5/blah-blah/record/record.cgi
Stop recording: http://192.168.1.5/blah-blah/record/stop.cgi
I don't have to worry about getting a response. I just have to send it.
I was exploring the GET method and also trying to use HappyHTTP library.
Is there a simple way to do it without using any library?
I am using visual studios on Windows.
You can use TCP connection to the server, after connected, you can send the plain HTTP request.
The following program shows this, connecting to google with TCP protocol, sending HTTP get request, and getting the first 2K of the response.
Note: See #Chase great comments below, the program is not complete: usage of gethostbyname should be avoided, as this method is deprecated, error handling should be changed to use WSAGetLastError and not errno, the program should call WSACleanup before it ends.
I don't have time now to fix the program, and I leave it as it, as someone may benefit from it as it is now.
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <winsock.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define SERVER_PORT ((u_short )80)
#define SERVER_HOST "www.google.com"
int main(int argc, char* argv[])
{
int ret;
SOCKET sd;
struct hostent* he = { 0 };
struct sockaddr_in addr_info = { 0 };
WSADATA wsaData = { 0 };
WSAStartup(MAKEWORD(2, 2), &wsaData);
he = gethostbyname(SERVER_HOST);
if (he == NULL)
{
printf("failed to get host information for '%s': %s\n", SERVER_HOST, strerror(errno));
exit(-1);
}
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sd == INVALID_SOCKET)
{
printf("failed to create socket: %s\n", strerror(errno));
exit(-1);
}
addr_info.sin_family = AF_INET;
addr_info.sin_port = htons(SERVER_PORT);
addr_info.sin_addr = *((struct in_addr*)he->h_addr);
memset(&(addr_info.sin_zero), 0, sizeof(addr_info.sin_zero));
printf("connecting to %s ....\n", SERVER_HOST );
ret = connect(sd, (struct sockaddr*)&addr_info, sizeof(struct sockaddr));
if ( ret == -1)
{
printf("failed to connect: %s\n", strerror(errno));
exit(-1);
}
printf("sending GET /index.html request ....\n");
char send_buffer[] = "GET /index.html HTTP/1.1\r\nHost : " SERVER_HOST "\r\n\r\n";
ret = send(sd, send_buffer, sizeof(send_buffer), 0);
if ( ret == -1)
{
printf("failed to send request: %s\n", strerror(errno));
exit(-1);
}
printf("getting response (first 2K)\n");
char response_buffer[2048] = "";
//using sizeof(response_buffer)-1 to have the buffer terminate with zero, even if it is fully consumed by the recv call. this is because I want to print it later on.
ret = recv(sd, response_buffer, sizeof(response_buffer)-1, 0);
if (ret == -1)
{
printf("failed to get response: %s\n", strerror(errno));
exit(-1);
}
printf("received (first 2KB): %s\n", response_buffer);
closesocket(sd);
return 0;
}
program output is:
connecting to www.google.com ....
sending GET /index.html request ....
getting response (first 2K)
received: HTTP/1.1 200 OK
Date: Tue, 29 Dec 2020 08:56:34 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2020-12-29-08; expires=Thu, 28-Jan-2021 08:56:34 GMT; path=/; domain=.google.com; Secure
Set-Cookie: NID=205=a7W2c7M39ojimAWRRgn7nwedmdouUrZfo8uBa1wJeEwo8DUk7ibclM-xwp5ozhKO2BYmcRnQ1l4wwjb_DYfOVsDoi-UdtqBmgySL_KlcG6zMjembghO8OL81e2iHee0_cnlDZvSCCGvPnaC0LHNzFtqeWaYSELF7-1t5Khuv4Yc; expires=Wed, 30-Jun-2021 08:56:34 GMT; path=/; domain=.google.com; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked
...
The barebones way of sending a request to server is """pretty simple""".
Firstly, I'll be assuming you know the exact host and port of the url. I assume this since I cannot access the link you've provided. In most cases, you know the host being the domain you type in the url and the port being 80 for http, or 443 for https - I cannot confirm this for the given link however.
In any case, first things first - the headers you'd require on windows for socket programming is-
#include <WinSock2.h>
#include <WS2tcpip.h>
You'll also need to link the winsock2 library, you can do this by putting #pragma comment(lib, "Ws2_32.lib") after your headers. Or you can put Ws2_32.lib in Project Configuration -> Linker -> Input -> Additional Dependencies in Visual Studio.
Now, winsock2 requires you to initiate the library (and also to close it). You can do this in your main function ideally, since the startup is required for any socket connection to be made-
/* Initialize wsock2 2.2 */
WSADATA wsadat;
if (WSAStartup(MAKEWORD(2, 2), &wsadat) != 0)
{
printf("WSAStartup failed with error %d\n", WSAGetLastError());
return 1;
}
And after everything is done-
/* Cleanup wsock2 */
if (WSACleanup() == SOCKET_ERROR)
{
printf("WSACleanup failed with error %d\n", WSAGetLastError());
return 1;
}
Now you need to obtain information about the server you want to connect to, we do that using getaddrinfo. (Relevant posix docs)
static struct addrinfo const hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
struct addrinfo* addrs;
int gai_retcode = getaddrinfo("192.168.1.5", "http", &hints, &addrs);
if (gai_retcode != 0)
{
fprintf(stderr, "Error encountered during getaddrinfo: %s\n", gai_strerrorA(gai_retcode));
return 1;
}
Let's walk through this a bit, getaddrinfo takes a node parameter - which is either a hostname string or an ip address string.
The second parameter is service - which is a string specifying the port to connect to, or the service being used (http, https, telnet etc - you can find a list of services in the IANA port list or in /etc/services of your linux box).
The third parameter is the address of an addrinfo struct containing "hints" to aid in selecting the right address. For connecting to a server, you usually need to set only the ai_family and ai_socktype members, everything else should be 0 initialized (which is what the compound initializer does automatically). AF_UNSPEC on ai_family means "find me either ipv4 or ipv6 addresses - no particular requirement", SOCK_STREAM on ai_socktype means "find me stream servers addresses" (aka TCP servers, not Datagram servers). HTTP is a stream based protocol.
The final parameter is the address of a pointer to an addrinfo struct. This is where the results of the lookup are stored in.
getaddrinfo returns 0 on success and fills up the pointer to addrs pointer. addrs is now a linked list of addresses returned from the lookup - the first one in the list is directly accessible, the next one is accessible through .ai_next and so on until you reach NULL. The regular shenanigans.
For websites having multiple servers around the world, getaddrinfo will return many addresses. They are arranged in the most relevant way determined by your machine. That is, the first one is the address most likely to work well. But you should ideally try the next ones in the linked list in case the first one fails.
For non zero returns, you can use gai_strerror to print the error message. This is named gai_strerrorA on windows for whatever reason. There's also a gai_strerror on windows but it returns a WCHAR* as opposed to a char*. On linux, you'll be using gai_strerror, not gai_strerrorA. Relevant linux docs on gai_strerror
Now that you have a list of addresses, you can create a socket and connect to one of them-
int sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
if (sfd == INVALID_SOCKET)
{
fprintf(stderr, "Error encountered in socket call: %d\n", WSAGetLastError());
return 1;
}
if ((connect(sfd, addrs->ai_addr, addrs->ai_addrlen)) == -1)
{
fprintf(stderr, "Error encountered in socket call: %d\n", WSAGetLastError());
closesocket(sfd);
return 1;
}
NOTE: This is only trying the first address in the linked list, if the first one fails - it just gives up. This isn't ideal for a real world usecase. But for your specific usecase, it probably won't matter.
Pretty self explanatory, socket takes the ai_family, ai_socktype and ai_protocol values from your addrinfo structure and creates a socket. It returns INVALID_SOCKET on failure (this is just -1 on linux). Relevant linux docs on socket.
connect takes the socket descriptor you just got from socket, and the ai_addr and ai_addrlen values from your addrinfo structure to connect to the server. It returns SOCKET_ERROR on failure (again, just -1 on linux). Relevant linux docs on socket.
Now all the low level socket shenanigans is done. Next comes HTTP shenanigans. To make an HTTP request, you need to know the HTTP spec, you can read the entire thing here.
Just kidding, here's a more digestible version - courtesy of MDN.
But basically, the format of a message is sort of like this-
<HTTP-VERB> <REQUEST-PATH> HTTP/<VERSION>\r\n[HEADERS-TERMINATED-WITH-CRLF]\r\n[BODY]
So a GET request to /blah-blah/record/record.cgi using HTTP 1.0 and no headers (no body either, since this is a GET request) looks like-
GET /blah-blah/record/record.cgi HTTP/1.0\r\n\r\n
Before moving on, I need to mention why I'm demonstrating HTTP/1.0 instead of the more sensible for the times - HTTP/1.1. Since not all servers actually support 1.0 fully. The real difference between the 2 is that 1.1 requires a Host: header, and it allows keeping the connection alive - which is more efficient for multiple requests to the same server.
About the Host: header, if you know what hostname the server is expecting in the request header, great! Change it to HTTP/1.1 and put the Host: hostname in. Where hostname is...well, the hostname. For all I know, your server might just be accepting 192.168.1.5 as the hostname, but it also may not.
The second part is the keep-alive connection part. A keep-alive connection gets a bit complicated with blocking sockets. If you decide to use a keep-alive connection and try to recv the response from the server, when there is nothing more to read from the stream, recv will keep blocking until there's something - since the connection is still alive. There are ways to solve this ofcourse, the behaviors of which vary from linux to windows. So I'd recommend just setting the Connection header to close if using HTTP/1.1. I assume this would be "good enough" for your usecase. The HTTP request would then look like-
GET /blah-blah/record/record.cgi HTTP/1.1\r\nHost: hostname\r\nConnection: close\r\n\r\n
Now, to send the request-
char const reqmsg[] = "GET /blah-blah/record/record.cgi HTTP/1.0\r\n\r\n";
if (send(sfd, reqmsg, sizeof reqmsg, 0) == SOCKET_ERROR)
{
fprintf(stderr, "Error encountered in send call: %d\n", WSAGetLastError());
closesocket(sfd);
return 1;
}
send takes the socket descriptor, the message, and the message length and returns the number of bytes sent or SOCKET_ERROR on failure (-1 on linux). Relevant docs for linux on send
There's some peculiarity here that should be noted. send may send less bytes than the length of the full message. This is normal for stream sockets. When this happens, you have to send the rest by calling send again and starting from where send left off (add the return value to the message string pointer to obtain the continuation point). This is omitted for brevity but it is highly unlikely to actually happen for such a short message. If you have any machine from the last decade - send will send that tiny message in full.
Please don't rely on that in an actual project though.
Congrats! you've successfully sent a request to the server. Now you can receive its response. The response format is like this-
HTTP/<VERSION> <STATUS-CODE> <STATUS-MSG>\r\n[HEADERS-TERMINATED-WITH-CRLF]\r\n[BODY]
You don't have to receive if you don't want to, the data will just be sitting there in the stream buffer, which will "vanish" when you close the socket. But here's a small example on parsing out just the status code-
int status;
char stats[13];
if (recv(sfd, stats, 13, 0) != 13)
{
fprintf(stderr, "Error encountered in recv call: %d\n", WSAGetLastError());
closesocket(sfd);
return 1;
}
sscanf(stats, "HTTP/1.0 %d ", &status);
recv, works much like send, just the other way around. It fills the buffer you pass as its second parameter and the maximum number of bytes it'll read can be mentioned in its third parameter. The return value of recv is the number of bytes read. Relevant docs for linux on recv
The peculiarity mentioned before is present here too. recv may read less than the bytes you mention in the third param. But with any half decent internet connection, 13 bytes will definitely be read in one go.
Why 13 bytes? That's the length of the HTTP/1.0 + a space + 3 digit status code + a space. We're only interested in the status code for this. sscanf will then parse out the status code (assuming the server responded correctly) and put it into status.
If you want to read any marginally large size (and/or execute multiple recv calls), be sure to do it in a loop and use the return value of the number of bytes read to appropriately use the buffer.
After all of this, be sure to call freeaddrinfo on addrs and closesocket on sfd.
Further reading: MS docs on Winsock2
On linux, everything is very similar - but with much less headaches :) - I recommend checking out beej's guide to network programming if you're looking for implementing this on linux.
(In fact, it's a great read for windows programmers as well! Up to date with modern functions that support ipv4 and ipv6 seamlessly, no manual and painful struct filling etc)
Everything will be super similar, you just need to change the headers - get rid of the stupid windows specific startups and replace WSAGetLastError inside fprintf to just a regular perror.
Edit: Replacement for compound literal (static struct addrinfo const hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };)-
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
Suffice to say, if you have access to a compiler that supports C99 and above - use compound literals instead - it's plain better :)

ANSI C download a file from a server

I try to download a tar file from a remote webserver, but I get only false data. Every runtime an other bytesize and with a HTTP header:
//now it is time to receive the page
memset(buf, 0, sizeof(buf));
FILE *fp;
if ((fp = fopen(FILENAME, "wb"))==NULL) {
perror("Error open file");
}
else {
while((tmpres = recv(xmlsock, buf, 65536, 0)) > 0){
fwrite(buf, sizeof(buf), 1, fp);
memset(buf, 0, tmpres);
}
}
if(tmpres < 0) {
perror("Error receiving data");
}
fclose(fp);
close(xmlsock);
My sent header is:
GET /updates/xxxxx.tar HTTP/1.1
Host: www.yyyyyyyy.de
User-Agent: HTMLGET 1.1
Connection: close
Can anybody help me?
Try:
fwrite(buf, 1, tmpres, fp);
You're always writing sizeof(buf) bytes in your original code, but you only read tmpres bytes, so you're writing a lot of cruft every time (there is no guarantee how big the packets are that you receive and in how many recv calls your data will be split up)
Two problems: The first is that you write the complete buffer to the file even if not all of it was filled by the recv Call. The other problem is that you receive everything, without regards to the HyperText Transfer Protocol which naturally includes headers.
For the first problem the solution is very simple, as you already have a variable whose value is the amount of data received.
For the second problem, you need to learn HTTP and how it separates the headers and the data.

C Sockets recv: connection reset by peer

I'm trying to use sockets to get a small JSON test file, which is hosted on my website (http://a-cstudios.com/text.json). When I do this
long numbytes;
char *request = malloc(sizeof(char) * 300);
sprintf(request, "GET %s \r\nHOST:%s \r\n\r\n", restOfURL, baseServer);
// restOfURL = "/text.json" baseServer = "www.a-cstudios.com"
send(sockfd, request, strlen(request) + 1, 0);
char buf[1024];
if ((numbytes = recv(sockfd, buf, 1024-1, 0)) == -1) {
perror("recv");
}
I get recv: connection reset by peer. But if I use the same code, where restOfURL is /index.html and baseServer is www.google.com, this works fine, and buf will contain the text of index.html. Why won't this work for the file on my website?
Since you didn't post full code, I am going to take a stab at it and make an assumption:
You populate the format string of "GET %s \r\nHOST:%s \r\n\r\n" with restOfURL and baseServer
However, during the time of the sprintf call restOfURL isn't initilized so you're pushing garbage data into the first %s
Either post more of your code or make sure you initialize resOfURL
As #Kninnug pointed out, you need the HTTP version field (e.g., HTTP/1.1) at the end of the first line of the request. I just want to point out that you should not include the null terminator when you send the request. That is, change the send statement to
send(sockfd, request, strlen(request), 0);
Also, it is a good practice to always use snprintf instead of sprintf to prevent buffer overflow, although to be really safe you still need to check for truncation.

Transferring files using UDP sockets in C

I'm fairly new to socket programming in C, so the code below may have a ton of newbie mistakes.
I'm trying to make a client-server application in which the server will transfer a file to the client using an UDP socket. Both the client and the server will run on Linux hosts. It's an assignment, so it has to be done that way. Other client-server communications may use a TCP socket, but the file transfer MUST be via UDP. The program works correctly for small files, but if I try to send a slightly larger file (say, a 600 kb text file), the client will stop receiving the packets, even though the server will send them all. Here's the file transfer part of the server code:
FILE* myFile;
long fileSize, readBytes, sentBytes, sizeCheck;
uint32_t encodedFileSize;
myFile = fopen(fileName, "rb");
if(myFile == NULL)
{
perror("Error when opening file.");
exit(1);
}
fseek(myFile, 0, SEEK_END);
fileSize = ftell(myFile);
encodedFileSize = htonl(fileSize);
rewind(myFile);
sizeCheck = 0;
write(myTCPSocket, &encodedFileSize, sizeof(encodedFileSize));
if(fileSize > 255)
{
while(sizeCheck < fileSize)
{
readBytes = fread(bufferRW, 1, 256, myFile);
sentBytes = sendto(sockfdUDP, bufferRW, readBytes, 0, (struct sockaddr*)&cli_addr, udpAddressSize);
sizeCheck += sentBytes;
}
}
else
{
readBytes = fread(bufferRW, 1, 256, myFile);
sentBytes = sendto(sockfdUDP, bufferRW, readBytes, 0, (struct sockaddr*)&cli_addr, udpAddressSize);
}
if(fileSize == sizeCheck)
{
printf("Success.\n");
}
else
{
printf("Fail.\n");
}
fclose(myFile);
fflush(stdout);
close(sockfdUDP);
As you can see, I used a TCP socket to send the client the file size. Here's the client code:
FILE *myFile;
long receivedBytes, writtenBytes, sizeCheck;
long fileSize, realFileSize;
char ack2[5] = "Ok";
sockfdUDP = socket(AF_INET, SOCK_DGRAM, 0);
read(socketTCP, &fileSize, sizeof(long));
realFileSize = ntohl(fileSize);
myFile = fopen(fileName, "wb");
if(myFile == NULL)
{
perror("Error when creating file.");
exit(1);
}
sizeCheck = 0;
if((realFileSize) > 255)
{
while(sizeCheck < (realFileSize))
{
receivedBytes = recvfrom(sockfdUDP, bufferRW, 256, 0, (struct sockaddr*)&serv_addr, &serv_addr_size);
writtenBytes = fwrite(bufferRW, 1, receivedBytes, myFile);
fflush(myFile);
sizeCheck += writtenBytes;
}
}
else
{
receivedBytes = recvfrom(sockfdUDP, bufferRW, 256, 0, (struct sockaddr*)&serv_addr, &serv_addr_size);
fwrite(bufferRW, 1, receivedBytes, myFile);
fflush(myFile);
}
if(realFileSize == sizeCheck)
{
printf("Success.");
}
else
{
printf("Fail.");
}
fclose(myFile);
close(sockfdUDP);
The "bufferRW" buffer was originally declared as char bufferRW[256] and passed to the function as an argument. Same goes for other undeclared variables.
Like I said before, the server will (apparently) send the whole file without any issues. However, the client will stop receiving packets after it's written about 423936 bytes (this may vary between executions). It'll just stay at the recvfrom line, without reading anything.
Now, I'm sure the problem is not being caused by a faulty connection since I'm testing both processes on the same host. And before you ask "What is it with the 256 byte packet size?", there's this weird bug that will throw me a segmentation fault on the realFileSize = ntohl(fileSize); client line if I use a buffer size of, say, 1500.
Could you please tell me what am I missing here?
EDIT: I'm trying with different file sizes now. It seems to handle files larger than 256 bytes without issue (it enters and exits the while loops correctly on both client and server), but the client will start having problems when the file is bigger than, say, 300 kb.
EDIT 2: I just debugged the program. Apparently, the server sends the entire file before the client can even enter its while loop.
EDIT 3: I think I know what's causing the issue. It seems like if the server sends a bunch of packets before the client starts reading, the client will read up to 278 packets, regardless of their size. If I try sending, say, 279 before the client starts reading it won't read the 279th packet. So if the server sends its packets fast enough, the number of packets that the client hasn't read yet will exceed 278 and the client won't finish reading all of the packets. Any ideas on how to fix this?
long* fileSize declared a pointer to a long, but in your code, it's pointing nowhere. In fact, it's pointing to a random address. You should declare it as long fileSize, and call read(socketTCP, &fileSize, sizeof(long)) instead.
You should check the return value of read, write, etc, to guarantee they did not fail. For example, sendto returns -1 on error. You're ignoring this, and incrementing sizeCheck with this value anyway.
UDP is not a reliable protocol for file transfers, but if you cannot do without it, you better implement some controls that TCP already gives you for free, like packet reordering, data checksum, etc. And that can be a very complex task by itself.
Compile your code with -Wall -Wextra. The compiler will give you hints about what could be potentially wrong. I see you're still using *fileSize in a comparison, which is clearly wrong.
After you fix the *fileSize issue, your loop-condition is still using the wrong value (due to fileSize = ntohl(fileSize)). You need to store this value in another variable, or change your loop-condition to use the real file size.
Regarding your EDIT 3, you need to somehow synchronise your client & server, so they can start the transmission at the same time. However, a sender that is much faster than the receiver will still cause packet loss. To solve this, you'll also need to implement packet acknowledgement, and retransmit a packet if the sender doesn't receive an ACK for a respective sent packet after a timeout. This is something TCP already does for you.
A simpler (but not completely reliable) way, would be to slow down the sending process a bit - maybe using nanosleep between each call to sendto.

How to Properly Set up a UDP Socket using UDP_CORK

I have created an echo server under UDP, and am working on a client that splits up a given file into packets and transfers it to a server, which then returns the packets to be reassembled by the client.
Sending the individual packets works just fine. However, I am trying to use the UDP_CORK socket option to transfer as many parts of the file as possible in one packet. I am working with the following code:
#define LINE_SIZE 80
...
// s = socket descriptor.
...
int one = 1;
int zero = 0;
setsockopt(s, IPPROTO_UDP, UDP_CORK, &one, sizeof(one)); // cork
/* Send to server, receive from server, write to new file. */
FILE *orig, *copy;
char line[LINE_SIZE];
if ((orig = fopen(input + 3, "rb")) == NULL) // original file
print_error(input);
if ((copy = fopen(filename, "wb")) == NULL) // copy of file
print_error("fopen");
while (fread(line, sizeof(char), LINE_SIZE, orig) > 0) {
if (sendto(s, line, LINE_SIZE, 0, (struct sockaddr *)&srv, len) == -1)
print_error("sendto");
}
setsockopt(s, IPPROTO_UDP, UDP_CORK, &zero, sizeof(zero)); // uncork
if (recvfrom(s, line, LINE_SIZE, 0, (struct sockaddr *)&srv, &len) == -1)
print_error("recvfrom");
fwrite(line, sizeof(char), LINE_SIZE, copy);
The server indicates that it is only receiving the one "normal-sized" packet, and not the corked packet that I would like. It follows that it is only sending back this one normal packet, received by the client via the recvfrom() call.
I am not sure that I set up the UDP_CORK correctly. Is the second argument correct? I am unfamiliar with what the second option truly means, as the man page was not entirely clear. It is also possible that I am misunderstanding how the cork works.
A few things to note:
I consider myself an intermediate-level programmer, but I have little network programming experience.
I realize that UDP is not the best to transfer files. This is eventually going to be adapted to a different protocol that I am helping to develop.
Thank you!
How big is the file? You know that UDP datagrams are limited to 64K in size, right? And then anything over 1472 bytes (1500 bytes of available ethernet payload less minimum of 20 bytes of IP header, less 8 bytes of UDP header) is IP-fragmented.
Then you never check for the return value of the setsockopt(2). How do you know it's succeeding?
Then fread(3) tells you how much it read, but you still try to send LINE_SIZE bytes. This is wrong.

Resources