Related
I am studying Multithread while making a file transfer program.
Client Code(The code that sends the file)
// Opened a file using fopen function.
int SendPercent = 0; // Value that shows the current progress
while (1)
{
len = fread(buf, 1, BUFSIZE, fp);
if (len)
{
retval = send(sock, buf, len, 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
SendPercent += len;
printf("percent : %2.2f%%\r", (float)SendPercent / (float)AllLen * 100.0f);
}
else if (SendPercent == AllLen)
{
printf("\nFinished.\n");
break;
}
else {
printf("\nIt did not end successfully.\n");
break;
}
}
fclose(fp);
closesocket(sock);
WSACleanup();
return 0;
Server Code(The code that receives the file)
FILE* fp = fopen(filename, "wb");
while (1)
{
retval = recv(client_sock, buf, sizeof(buf), MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0) {
printf("\ncurLen : %f, allLen : %f\n", (float)curLen, (float)allLen);
break;
}
curLen += fwrite(buf, 1, retval, fp);
memset(&buf, 0, sizeof(buf));
printf("percent : %2.2f%%\r", (float)curLen / (float)allLen * 100.0f);
if (curLen == allLen)
{
printf("\nSuccessfully Received.\n");
break;
}
}
fclose(fp);
}
The server's receive function acts as multi-threaded through the CreateThread function.
It works fine when sending small files of 4KB size, but problems arise when sending large files of more than 77000KB.
When a problem occurs(server)
percent : 99.92%
curLen : 79141464.000000, allLen : 79202368.000000
[TCP Server] Disconnected. IP Address=127.0.0.1, Port Number=....
When a problem occurs(client)
percent : 100.00%
Finished.
The client disconnects before the curLen reaches allLen.
What's even more strange is that when I open a folder in the server program and look at the received files, the files have been copied successfully.
Is the file sent correctly?
Why isn't the server's transmission rate going up properly?
Obviously, this program worked normally when it was single thread.
Sadly, I have not even identified the cause of the problem before solving this problem. Please help me.
I am writing a program that a client can ask for files to a server. Then the server will send them in chunks of 512 bytes. The problem is that when the client read the file:
*Sometimes the first 512 bytes are different from the original file. The total read file also has a different size (and obviously it also ends different from the original file) and therefore the client loop that writes to the new file does never end.
*Sometimes it works perfectly and i don't know why.
Server:
/* Check if file exists */
if(access(retrFileName, F_OK) == 0){
/* Open file */
fd = open(retrFileName, O_RDONLY);
lseek(fd, 0, SEEK_SET);
if (fd == -1){
fprintf(stderr, "Error opening file --> %s", strerror(errno));
exit(EXIT_FAILURE);
}
/* Get file stats */
if (fstat(fd, &fileStat) < 0){
fprintf(stderr, "Error fstat --> %s", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(fileSize, "%li", fileStat.st_size);
/* Sending file data */
offset = 0;
remainData = fileStat.st_size;
while (((sentBytes = sendfile(clientSock, fd, &offset, 512)) == 512) && (remainData > 0)) {
remainData -= sentBytes;
fprintf(stdout, "Server envio %d bytes del file, offset ahora vale: %li y quedan = %d bytes\n", sentBytes, offset, remainData);
}
remainData -= sentBytes;
fprintf(stdout, "Server envio %d bytes del file, offset ahora vale: %li y quedan = %d bytes\n", sentBytes, offset, remainData);//do while
close(fd);////////////////////////
send(clientSock, NICETRANSFER, sizeof(NICETRANSFER), 0); //LO METE AL ARCHIVO
printf("send\n");
//close(clientSock);///////////
}
else{
send(clientSock, FILEERROR, sizeof(FILEERROR), 0);
printf("send\n");
}
}
Client:
/* Open file */
receivedFile = fopen("r.txt", "wb");
if (receivedFile == NULL){
fprintf(stderr, "Failed to open file --> %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Write to the file */
int contador = 0;
int remainData = fileSize;
do{
if(remainData < 512)
bytesLeidos = recv(clientSock, readingBuffer, remainData, 0);
else
bytesLeidos = recv(clientSock, readingBuffer, 512, 0);
fwrite(readingBuffer, bytesLeidos, 1, receivedFile);
remainData -= 512;
contador += 512;
printf("bytesleidos: %li, contador: %d:\n%s\n\n", bytesLeidos, contador, readingBuffer);
}while(contador < fileSize);
fclose(receivedFile);
Golden rule of socket programming: Always check the return value from recv. It's not always what you think it will be.
Even though you "send" 512 bytes at a time, you are in no way guaranteed that TCP will deliver the same number of bytes at a time to the receiver. TCP segmentation, IP fragmentation, and general Internet weirdness will cause the recv side to get an arbitrary number of bytes at a time.
Hence, your hardcoded assumption that recv will always return 512 is incorrect:
remainData -= 512;
contador += 512;
Instead, you should be saying:
remainData -= bytesLeidos;
contador += bytesLeidos;
An you need to check for errors and socket closing too.
This is an improved main loop for your client code:
while (remainData > 0)
{
size_t recvSize = (remainData >= 512) ? 512 : remainData;
bytesLeidos = recv(clientSock, readingBuffer, recvSize, 0);
if (bytesLeidos > 0)
{
fwrite(readingBuffer, bytesLeidos, 1, receivedFile);
remainData -= bytesLeidos;
contador += bytesLeidos;
/* null terminate readingBuffer so garbage isn't printed.*/
/* Make sure readingBuffer is allocated to be at least */
/* N+1 bytes (513) to account for this character being appended. */
readingBuffer[bytesLeidos] = '\0';
printf("bytesleidos: %li, contador: %d:\n%s\n\n", bytesLeidos, contador, readingBuffer);
}
else if (bytesLeidos == 0)
{
/* remote side closed connection */
printf("Remote side exited connection\n");
break;
}
else if (bytesLeidos < 0)
{
/* connection error */
printf("Connection error\n");
break;
}
}
I solved my problem!! I needed to sync both client and server. To do so, the server send the size of the file and waits for an answer for the client with recv. When the client recieve the file size, it send a "" message.
I don't know if this is the correct solution, but this way you can sync server and client.
After sync, the server send the respective file normally with sendfile
I write program and it works fine, but i want to rewrite it using sendfile() and now i got stuck in a loop.
Server side:
send name = ok
send md5 checksum = ok
send size = ok
send file = ko
Client side:
recv name = ok
recv md5 cecksum = ok
recv size = ok
create dir and create file = ok
write data to created file = ko
P.S In previous version of program i stuck some time to, but it depend how much i use printf why? for e.x i add one line with printf program stuck, delete it, works fine.
UPDT: rewrite code client/server
client
/* Received file name */
int rc_byte = 0;
rc_byte = recv(fd, rx_tx_file->out_name, sizeof(rx_tx_file->out_name),0);
if (rc_byte < 0){
perror("Failed to receive file name: ");
exit(-1);
} else
printf("Recv out name %s\n", rx_tx_file->out_name);
//printf("file name rc %s\n", rx_tx_file->out_name);
trimm_path_name(rx_tx_file);
/* Received md5sum */
rc_byte = recv(fd, rx_tx_file->md5sum, sizeof(rx_tx_file->md5sum), 0);
if (rc_byte < 0) {
perror("Failed to receive check sum: ");
exit(-1);
} else
printf("recv md5s %s\n", rx_tx_file->md5sum);
/* Received file size */
rc_byte = recv(fd, &size, sizeof(size), 0);
if(rc_byte < 0) {
perror("Recevid size of file: ");
exit(-1);
}
printf("%d recv size\n", size);
to_read = size;
if (stat(dir, &st) == -1){
mkdir(dir, 0777);
}
send_data: (add func to server)
void send_data(int client_fd, m_file *rx_tx_file, int option, int size) {
int send_byte = 0;
int total_send = 0;
if (option == SEND_NAME) {
while (total_send < strlen(rx_tx_file->in_name)) {
send_byte = send(client_fd, rx_tx_file->in_name, sizeof(rx_tx_file->in_name),0);
if(send_byte == -1) {
perror("Failed to send file name to client: ");
exit(SEND_TO_CLIENT_ERROR);
}
total_send += send_byte;
}
}
else if (option == SEND_MD5) {
total_send = 0;
send_byte = 0;
while (total_send < strlen(rx_tx_file->md5sum)) {
send_byte = send(client_fd, rx_tx_file->md5sum, sizeof(rx_tx_file->md5sum),0);
if(send_byte == -1){
perror("Failed to send file md5sum to client: ");
exit(-1);
}
total_send += send_byte;
}
}
else if (option == SEND_SIZE) {
send_byte = send(client_fd, &size, sizeof(size),0);
if (send_byte == -1) {
perror("Failed to send size: ");
}
}
}
server:
client_fd = accept(server_fd, (struct sockaddr*) &client_addr, &length)
/*send name of file*/
send_data(client_fd, rx_tx_file, SEND_NAME, 0);
/*send md5 sum*/
take_check_sum(rx_tx_file,rx_tx_file->file_in, 0);
send_data(client_fd, rx_tx_file, SEND_MD5, 0);
/*send size of file*/
size = stats.st_size;
send_data(client_fd, rx_tx_file, SEND_SIZE, size);
remain_data = stats.st_size;
printf("File [%s] ready to send\ncheck sum [%s]\n", rx_tx_file->in_name,rx_tx_file->md5sum);
while (((send_byte = sendfile(client_fd, file_fd, &offset, size)) > 0) && (remain_data > 0))
{
remain_data -= send_byte;
printf("remain %d", remain_data);
}
printf("Succesfully");
Since i work with one client and pass file which should send on server side through command line args, i dont need to wait in while (client_fd = accpet) i just work with one connection and close server. Now its work good. But one question is open, how i should rewrite client side to recv data in a loop. I don't know which size i should recv and because of that i cant write right condition to my while loop. THX all for helping.
TCP is a stream. It has no message boundaries. Your code won't work because of that.
First, you send the name of the file:
send(client_fd, rx_tx_file->in_name, strlen(rx_tx_file->in_name)+1,0)
then you immediately send the md5 sum and then the file size:
send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0)
send(client_fd, &size, sizeof(int),0)
Since the first two strings don't have a fixed number of bytes, it's quite likely that when you try to read the file size or md5 sum from the server you also read the size of the file and maybe even some of the file data.
First, stop trying to put as much of your send and read code as you can into the conditional clause of your if and while statements.
What exactly does
if (send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0) == -1) {
perror("Failed to send file md5sum to client: ");
exit(-1);
}
gain you over
ssize_t bytes_sent = send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0);
if ( bytes_sent < 0 )
{
perror("Failed to send file md5sum to client: ");
exit(-1);
}
Putting all that code into the if clause gains you nothing on the send. And what if strlen(rx_tx_file->md5sum)+1 is 87 and the send() call returns 15? That's a possible return value that your code can't handle because it stuffs everything into the if clause.
ssize_t bytes_sent = send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0);
if ( bytes_sent < 0 )
{
perror("Failed to send file md5sum to client: ");
exit(-1);
}
else if ( bytes_sent < strlen(rx_tx_file->md5sum)+1 )
{
// partial send...
}
That's actually better coded as a loop.
You didn't post your receive code, but if it's in the same style you not only don't gain anything, by putting everything into the if clause you again can't do any decent error detection or correction.
If your file name recv code is similar to
char filename[1024];
if (recv(fd, &filename, sizeof(filename), 0) < 0) {
perror("Failed to read file name: ");
exit(-1);
}
you can't tell what you just received. How many bytes did you just receive? You may have received the file name. You may have received only part of the file name. You may have received the file name, the md5 sum, and some of the file contents itself.
You don't know what you received, and with your code you can't tell. If you zero out the file name and md5 receive buffers and only recv up to one byte less than the size of the buffer, you at least avoid undefined behavior. But if you don't zero out the buffer, or if you read up the the last byte of the buffer, you can also wind up without a nul-terminated string for your filename or md5 sum. And when you then try to treat it as a nul-terminated string you get undefined behavior.
And if you did get extra bytes in the recv calls you make before trying to read the file data, that explains why your code gets stuck - it already read some of the file contents before getting to the loop, so the loop will never see all the content - some of it is gone.
You should avoid using strlen here in your server:
if(send(client_fd, rx_tx_file->in_name, strlen(rx_tx_file->in_name)+1,0) == -1)
Rather just send fixed length string of size sizeof(rx_tx_file->out_name) as you expect in your client
If the filename is smaller just pad it with spaces to make it of length sizeof(rx_tx_file->out_name)
You should also put each receive call in while loop, and add checks that it actually received expected number of bytes, at times recv will just return partial data, you need to post another recv to receive rest of the expected data
I'm trying to send files on a Client-Server application written in c.
Client should upload files and server should receive files.
Client side :
if(fstat(fd,&file_stat) < 0){
perror("Fstat error");
return -1;
}
FILE *filepointer = fopen(filename,"rb");
if(filepointer == NULL){
perror("Opening file error");
return -1;
}
strcat(sendbuf,"8"); // option number
strcat(sendbuf,client_account.user); //user who is sending a file
strcat(sendbuf,"p1.txt"); // file name
printf("sendbuf : %s\n",sendbuf );
if(write(sock_fd,sendbuf,strlen(sendbuf)) < 0 ){
perror("Writing error");
return -1;
}
/* Check server's answer */
if((nread = read(sock_fd,buffer,sizeof(buffer))) < 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
printf("Buffer : %s : %d",buffer,atoi(buffer));
if(atoi(buffer) != GENERIC_OK){
printf("Error occurred\n");
return -1;
}
sprintf(file_size,"%lld",file_stat.st_size);
/* Writing file size */
if((nwritten = write(sock_fd,file_size,sizeof(file_size))) < 0){
perror("Writing error");
return -1;
}
memset(buffer,0,sizeof(buffer));
/* Check second server's answer */
if((nread = read(sock_fd,buffer,sizeof(buffer))) < 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
printf("Buffer : %s : %d",buffer,atoi(buffer));
if(atoi(buffer) != GENERIC_OK){
printf("Error occurred\n");
return -1;
}
while(1){
nbytes = fread(sendbuf,1,sizeof(sendbuf),filepointer);
/* There are bytes to send */
printf("Sendbuf : %s \n" , sendbuf);
if(nbytes > 0){
write(sock_fd,sendbuf,nbytes);
}
if(nbytes < 256){
if(feof(filepointer) || ferror(filepointer))
break;
}
}
Server side :
... first buffer is received well ...`
/* WRITE TO CLIENT TO CONTINUE */
if(write(sock_fd,"500",strlen("500")) < 0){ /*GENERIC OK*/
perror("Writing error");
return -1;
}
memset(recvBuf,0,sizeof(recvBuf));
/*RECEIVING FILE SIZE */
if((nread = read(sock_fd,buffer,sizeof(buffer)))< 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
file_size = atoi(buffer);
printf("file size : %d\n",file_size);
if((fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU)) < 0){
perror("File opening error"); /* file already exists */
}
recv_file = fopen(filename,"wb");
if(recv_file == NULL){
perror("Opening error");
return -1;
}
remaining_data = file_size;/*i think processes are blocking on while loops*/
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
recvBuf[nread] = '\0';
printf("%d bytes received : %s \n",nread,recvBuf);
fwrite(recvBuf,1,nread,recv_file);
remaining_data -= nread;
}
if(nread < 0){
perror("Reading error");
}
I've tried to use sendfile function but I'm on Mac OS and it's not supported. Have you got any advice for me?
It should go like this :
1) client sends a buffer to server announcing it's going go send a file with its name - OK
2) server receives this buffer and sends a generic ok code to client - OK
3) client sends the size of the file to the server - OK
4) server receives this buffer and sends a generic ok code to client - OK
5) client read stuff from file and sends it to the server - NOT OK
6) server receives stuff from the client and writes stuff into the file - NOT OK
This line, in the server,
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
is trying to read first, without having checked that remaining_data >0. The result is a read operation after all the file is transfered.
Suggest using a while(remaining_data>0) { select()/read() }, where the select() has a reasonable timeout parameter and causes an exit of the loop when a timeout occurs.
this code:
if((fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU)) < 0){
perror("File opening error"); /* file already exists */
}
recv_file = fopen(filename,"wb");
if(recv_file == NULL){
perror("Opening error");
return -1;
}
is opening the output file, in the server, twice, without an intervening close(). This is probably not what is needed.
I think you have left out some of the relevant code, but very likely the key problem is in the server's while condition:
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
Note that it will always attempt to read data, even if it has already read the expected number of bytes. If the client holds the connection open (maybe it's waiting for a response from the server) then no EOF condition will be detected, and you have a deadlock.
Swapping the order of the conditions should resolve that problem, at least for well-behaved clients. Note, however, that even with that change, you could still get a deadlock if the client sends fewer bytes than it promises, yet holds the connection open. A malicious client could intentionally exhibit that behavior to perform a DoS against your server.
Also, 'read(sock_fd,buffer,sizeof(buffer)', followed by 'buffer[nread] = '\0';' will overflow the buffer by 1 if the the full 'sizeof(buffer)' bytes are received.
'file_size = atoi(buffer)' - not guaranteed to work since you don't know if the whole buffer has been received.
Also endianness issue with same.
I want to create a remote control for GNUNet, so I started writing a self-made multithreaded-generical-purpose webserver for the GNU OS, able to authenticate users (reading from the system user database) and able to execute generic CGI programs/scripts. I started from scratch and it's just a draft for now. However, everything seems to work fine.
I have just a question.
As you know, a CGI programs/scripts read the POST string from the STDIN and send their content to the STDOUT. The following is (part of) the code I wrote. And it seems to work.
if (pipe(cgiPipe))
{
perror("pipe");
}
cgiPid = fork();
if (cgiPid == 0)
{
/* child */
/* piping the POST content... */
/* first, send the truncated part of the POST string contained within the request string... */
if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment,
nReqLen + requestString - sPOSTSegment)) > 0)
{
nPOSTLength -= nSentChrs;
/* after, read and send the rest of the POST string not received yet... */
while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer,
BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer,
nReadChrs)) > 0 && nReadChrs == nSentChrs)
{
nPOSTLength -= nReadChrs;
}
if (nReadChrs < 0)
{
printf("Error reading POST string.\n");
goto closeThread;
}
if (nSentChrs < 0)
{
printf("Error sending POST string.\n");
goto closeThread;
}
}
else
{
write(cgiPipe[1], "(null)", 6);
}
close(cgiPipe[1]);
/* redirecting the output of the pipe to the STDIN of the child process */
dup2(cgiPipe[0], STDIN_FILENO);
/* redirecting STDOUT of the child process to the remote client */
dup2(nRemote, STDOUT_FILENO);
setuid(nUserID);
if (execve(sLocalPath, NULL, aCGIEnv))
{
/* unable to execute CGI... */
perror("execve");
sendString(nRemote,
"HTTP/1.1 200 OK\r\n"
"Content-length: 97\r\n"
"Content-Type: text/html\r\n\r\n"
"<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n"
);
}
goto closeThread;
}
else if (cgiPid > 0)
{
/* parent */
close(cgiPipe[0]);
/* wait for child process. */
if (waitpid(cgiPid, NULL, 0) == -1)
{
perror("wait");
}
goto closeThread;
}
else
{
/* parent */
perror("fork");
/* let's try to send it as normal file, if the user has the right permissions... */
}
As you can see, before executing the CGI program, the whole POST string is received from the client and piped (first the truncated part of it contained within the request string – usually few bytes – and then the rest). Then, the CGI program is executed.
And now my question…
If I try to upload a file of several MBs, several MBs are piped before the invocation of the CGI: is there any way to redirect the socket directly to the STDIN of the new process, in order to not read it before? But, for sure, I have to send the read truncated part of the POST string, before. So, I can schematize what I would like to do in this way:
piping a string (few bytes) to the STDIN, then
redirecting the socket (the client) to the STDIN, then
executing an external process (the CGI program)
Is it possible? Can you show me how?
Solved!!
I just had to put the sending process inside the parent rather than the child. In this way the CGI is executed immediately:
if (pipe(cgiPipe))
{
perror("pipe");
}
cgiPid = fork();
if (cgiPid == 0)
{
/* child */
/* piping the POST content... */
close(cgiPipe[1]);
/* redirecting the output of the pipe to the STDIN of the child process */
dup2(cgiPipe[0], STDIN_FILENO);
/* redirecting STDOUT of the child process to the remote client */
dup2(nRemote, STDOUT_FILENO);
setuid(nUserID);
if (execve(sLocalPath, NULL, aCGIEnv))
{
/* unable to execute CGI... */
perror("execve");
sendString(nRemote,
"HTTP/1.1 200 OK\r\n"
"Content-length: 97\r\n"
"Content-Type: text/html\r\n\r\n"
"<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n"
);
}
goto closeThread;
}
else if (cgiPid > 0)
{
/* parent */
close(cgiPipe[0]);
/* first, send the truncated part of the POST string contained within the request string... */
if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment,
nReqLen + requestString - sPOSTSegment)) > 0)
{
nPOSTLength -= nSentChrs;
/* after, read and send the rest of the POST string not received yet... */
while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer,
BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer,
nReadChrs)) > 0 && nReadChrs == nSentChrs)
{
nPOSTLength -= nReadChrs;
}
if (nReadChrs < 0)
{
printf("Error reading POST string.\n");
goto closeThread;
}
if (nSentChrs < 0)
{
printf("Error sending POST string.\n");
goto closeThread;
}
}
else
{
write(cgiPipe[1], "(null)", 6);
}
/* wait for child process. */
if (waitpid(cgiPid, NULL, 0) == -1)
{
perror("wait");
}
goto closeThread;
}
else
{
/* parent */
perror("fork");
/* let's try to send it as normal file, if the user has the right permissions... */
}
Thank you for your help!
And… let's hope to see a remote control for GNUNet as early as possble! :)
This can be achieved by replacing the file descriptor STDIN_FILENO with the opened socket using dup2(). You should also then close the original socket in the child process:
dup2(socket_fd, STDIN_FILENO);
close(socket_fd); ;
execve("cgi_process", args, env);
The execve puts into execution another process whose STDIN is bound to socket_fd.