Concurrent File Transfer using BSD Socket with pThreadÂ
History
I have discussed multiple file transfers using a BSD socket in that post: https://www.arupsarker.com/blog/multiple-file-transfer-using-bsd-socketÂ
On the server side, multiple connections are established for each file. accept function runs in an infinite while loop. Whenever a new connection comes from the client, it will be handled accordingly. But, if the client creates multiple connections concurrently, a server is unable to handle them.
In this article, I will explain, how concurrency works and how we can implement it.
Here is the basic step:Â
On the server side,
 Create a server socket.
 Bind socket with server address.
 Open a listener to accept the client connection request and wait for an incoming request
 In the while loop,
 Accept client connection requests in a loop for multiple connections.
 Now the connection is established. Implement logic for handling incoming files.Â
 Call Pthread_create and implement the callback function of the thread.
 In the callback function, Receive a file name from the client and create an empty file with that name.
 Start receiving file data line by line and write to the file.
 After receiving one file, create a new connection and receive the next one.
On the client side,
 Read all the files from a directory.
 For each file, repeat steps 3 to 7.Â
 Call Pthread_create and implement the callback function of the thread.
 In the callback function, Create a client socket.
 Connect with the server socket where there is a listener.
 After the connection is successful, send the File Name to the server.
 Send data line by line using the socket file descriptor.
Server-side Changes:
We are creating threads based on the number of connections requested from the clients. After creating threads, we will use those to receive the file name and its contents.
int *new_sock;
pthread_t tid[60];
int idx = 0;
while(1) {
// 7. Accept connection for creating multi-thread
socklen_t addrlen = sizeof(nwClientAddr);
int connfd = accept(sockfd, (struct sockaddr *) &nwClientAddr, &addrlen);
if (connfd == -1) {
perror("Error in Accepting Connection");
exit(1);
}
new_sock = (int*)malloc(1);
*new_sock = connfd;
//printf("Connection ID: %d\n", *new_sock);
// 8. Multi-Thread creation
if(pthread_create(&tid[idx], NULL, socketThreadFT, (void*)new_sock) < 0 ) {
printf("Failed to create thread\n");
exit(1);
}
idx++;
}
In the sockThreadFT callback functions, we have implemented all the logic of receiving files.
void* socketThreadFT(void *arg)
{
int connfd = *((int *)arg);
unsigned int checkSum = 0;
pthread_mutex_lock(&lock);
// 8. Receive incoming file from client
char filename[MAX_NAME] = {0};
if (recv(connfd, filename, MAX_NAME,0) == -1) {
perror("Unable to receive file due to connection or file error");
exit(1);
}
int fileLength = strlen(filename);
pthread_mutex_unlock(&lock);
sleep(1);
if(fileLength > 0) {
// 9. After receiving from client, create file to save data
//printf("File Name : %s\n", filename);
FILE *fp = fopen(filename, "wb");
if (fp == NULL) {
perror("Unable to create File pointer");
exit(1);
}
// 10. Start receiving file Data and print in console
char addr[INET_ADDRSTRLEN];
// 11. Write the data into file.
checkSum = fileWriter(filename, connfd, fp);
// 12. Print success message into console
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char s[64];
assert(strftime(s, sizeof(s), "%c", tm));
printf("%s is received with checkSum %d at :Â %s\n",filename, checkSum,s);
}
close(connfd); Â Â Â Â Â Â Â
pthread_exit(NULL);
}
Client Side changes:
On the client side,Â
1. We have to read each file from the directory.
2. We are creating threads based on the number of concurrency values from user inputs.
3. We will send all the files by using those threads. Let's say, we have 4 concurrent threads and 10 files to send from a directory. Then we will send 4 files in parallel with 4 concurrent threads at first.
4. When one file is finished to send, then the free worker thread will pick another file to send.
int j = 0;
while(j != val) {
// 11. Create multi-thread for each files
if(pthread_create(&tid[j++], NULL, threadForFileTransfer, (void*)sParams) != 0 ) {
printf("Failed to create thread\n");
exit(1);
}
printf("Thread ID : %d\n", j);
}
// 12. Execute Thread
j = 0;
while(j != val)
{
pthread_join(tid[j++],NULL);
printf("pthread join %d:\n",j);
}
Here is the callback function threadForFileTransfer. This will be used to read file name and file data to server.
void *threadForFileTransfer(void *vargp)Â
{Â
SP *sockParamList = (SP *) vargp;
if(sockParamList == NULL) {
printf("Socket Param Error");
exit(1);
}
for(int i = 0; i < sockParamList->fileCount; i++) {
if(!sockParamList->threadParamList[i]->isUsed) {
pthread_mutex_lock(&lock);
sockParamList->threadParamList[i]->isUsed = true;
pthread_mutex_unlock(&lock);
//sleep(1);
// 2. Create TCP Socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Unable to create Socket");
exit(1);
}
// 3. Setup information about server
struct sockaddr_in serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
// 4. IP address should be IPv4
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(SERVER_PORT);
// 5. Check IP adddres and convert it with inet_pton
if (inet_pton(AF_INET, sockParamList->threadParamList[0]->serverIP, &serverAddress.sin_addr) < 0) {
perror("Conversion Error in IP Address");
exit(1);
}
// 6. Client will connect after server bind
if (connect(sockfd, (const struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) {
perror("Unable to Connect");
exit(1);
}
// 11. Send file name from buffer data through socket so that server can create file with same name.
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char s[64];
assert(strftime(s, sizeof(s), "%c", tm));
printf("%s file Sending is started at : %s\n",sockParamList->threadParamList[i]->fileName,s);
if (send(sockfd, sockParamList->threadParamList[i]->fileName, MAX_NAME, 0) == -1) {
perror("Unable to send Filename");
exit(1);
}
// 12. Create File pointers
FILE *filePointer = fopen(sockParamList->threadParamList[i]->filePath, "rb");
if (filePointer == NULL) {
perror("Unable to open the file");
exit(1);
}
unsigned int checkSum = 0;
// 13. Send file through socket.
checkSum = fileSender(filePointer, sockfd);
struct tm *tm1 = localtime(&t);
char endTime[64];
assert(strftime(endTime, sizeof(endTime), "%c", tm1));
printf("%s file is sent successfully with checkSum : %d at %s\n",sockParamList->threadParamList[i]->fileName, checkSum, endTime);
close(sockfd);
}
}
// 18. Close socket after sending single file.
pthread_exit(NULL);
}
Prepare
$ git clone https://github.com/arupcsedu/SocketFileTransfer.git
$ cd SocketFileTransfer
Build and Run Server
$ cd NWServer
$ g++ Server.cpp -o Server -pthread
$ ./ServerÂ
Build and Run Client
$ cd ..
$ cd NWClient
$ dd if=/dev/zero of=res/s1 bs=10MB count=1 //This is for creating 10MB file in res directory. Execute multiple times for creating multiple files.
$ g++ Client.cpp -o Client -pthread
$ ./Client res 127.0.0.1 4