Linux c socket,client,server transfer file with multi-thread,多執行緒傳送檔案

下面程式功能為 server 端可以同時接收多個 client 連上來要求檔案,利用 thread 功能達成『同時、多個』的要求!
使用方法為先執行 server 程式,不要關閉,server 會讀取同一個資料夾底下的 TEST.MP3 這個檔案
然後在同一台機器上面執行 client 並且給予 argv 參數作為存檔的檔名
如果沒有給檔名的話,預設會用 GET.MP3 作為存檔名稱

/*
 * Name : server.c
 * Author : Wen chi-ching
 * Date : 2009/10/15
 * Send file with multi thread
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_THREADS 10

void *thread_function(void *);

int sockfd, new_fd[MAX_THREADS],  res, times=0, i, num=0, *p_num;
socklen_t sin_size;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
struct stat filestat;
pthread_t accept_thread[MAX_THREADS];
void *thread_result;

int main(){
	p_num = #
	//Get file stat
	if ( lstat("TEST.MP3", &filestat) < 0){
		exit(1);
	}
	printf("The file size is %lun", filestat.st_size);

	//TCP socket
	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
		perror("socket");
		exit(1);
	}

	//Initail, bind to port 2324
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(2324);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	bzero( &(my_addr.sin_zero), 8 );

	//binding
	if ( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1 ){
		perror("bind");
		exit(1);
	}

	while(1){
		//Start listening
		if ( listen(sockfd, 10) == -1 ){
			perror("listen");
			exit(1);
		}
		printf("nWait for connectn");
		//Connect
		if ( (new_fd[num] = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size)) == -1 ){
			perror("accept");
			exit(1);
		}

		//Create thread
		res = pthread_create( &(accept_thread[times]), NULL, thread_function, (void *)p_num );
		if (res != 0){
			perror("Thread create failed!n");
			exit(EXIT_FAILURE);
		}
		num++;
		times++;
	}
	close(sockfd);
	return 0;
}

void *thread_function(void *arg){
	int numbytes=0, th_num=0, *p_th_num;
	char buf[1000000];
	FILE *fp;
	p_th_num = (int *)arg;
	th_num = *p_th_num;
	fp = fopen("TEST.MP3", "rb");
	//Sending file
	while(!feof(fp)){
		numbytes = fread(buf, sizeof(char), sizeof(buf), fp);
		printf("thread# is %d : fread %d bytes, ", th_num, numbytes);
		numbytes = write(new_fd[th_num], buf, numbytes);
		printf("Sending %d bytesn",numbytes);
	}
	printf("Sending file Finished!n");
	fclose(fp);
	close(new_fd[th_num]);
	return NULL;
}

server 程式的重點在於 ,因為要同時給多個 client 下載,所以要開 thread ,但是是什麼時間點要開呢?

bind 的時候?listen 的時候?accept 的時候?

其實搞不好都可以。

我選擇在 accept 之後開 thread,並且把 accept 回傳的檔案描述子放到一個陣列去,再把這個陣列的 index 值傳給開出來的 thread ,又,因為 while 裡面有 num++ 的動作,所以每次開 thread 出來的 accept 檔案描述子不會重複,如此也才不會造成 server 傳檔案給 client 的時候檔案指標寫入錯誤的檔案描述子。

整理一下重點,免得自己以後不知道在做啥…

1.accept 的回傳值放到一個 int 陣列去

2.pthread_create 的第四個參數 p_num 存放 num 的址,轉型為 void 是函數的參數型態規定

/*
 * Name : client.c
 * Author : Wen chi-ching
 * Date : 2009/10/14
 * Recieve file
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[]){
	int sockfd, numbytes;
	char buf[1000000],filename[10];
	struct sockaddr_in address;
	FILE *fp;

	//TCP socket
	if ( ( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 ){
		perror("socket");
		exit(1);
	}

	//Initial, connect to port 2323
	address.sin_family = AF_INET;
	address.sin_port = htons(2324);
	address.sin_addr.s_addr = inet_addr("127.0.0.1");
	bzero( &(address.sin_zero), 8 );

	//Connect to server
	if ( connect(sockfd, (struct sockaddr*)&address, sizeof(struct sockaddr)) == -1){
		perror("connect");
		exit(1);
	}

	//Open file
	if(argc > 1){
		strncpy(filename, argv[1], sizeof(filename) );
	}
	else{
		strncpy(filename, "GET.MP3", sizeof("GET.MP3"));
	}

	if ( (fp = fopen(filename, "wb")) == NULL){
		perror("fopen");
		exit(1);
	}

	//Receive file from server
	while(1){
		numbytes = read(sockfd, buf, sizeof(buf));
		printf("read %d bytes, ", numbytes);
		if(numbytes == 0){
			printf("n");
			break;
		}
		numbytes = fwrite(buf, sizeof(char), numbytes, fp);
		printf("fwrite %d bytesn", numbytes);
		sleep(1);
	}

	fclose(fp);
	close(sockfd);
	return 0;
}

Makefile

all : client.c server.c
	gcc -Wall -g -o client client.c
	gcc -Wall -g -o server server.c -lpthread

clean :
	rm client
	rm server

加入了 #include <arpa/inet.h> 在這兩個程式裡面,解決「implicit declaration of function ‘inet_addr’」這個 warning
int sin_size 改為 socklen_t sin_size;,解決「pointer targets in passing argument 3 of ‘accept’ differ in signedness」這個 warning
2010/07/07

參考:
Linux c socket,client,server transfer file傳送檔案
Linux c socket,client,server.透過網路傳送文字訊息

1 關於 “Linux c socket,client,server transfer file with multi-thread,多執行緒傳送檔案” 的評論

  1. server 中有個小 bug ,若 thread_function 在 write 前 , sleep 3 則 main
    thread 的 *p_mnu 的 vaule 已被 num++, 但 thread_function new_fd[th_num] 已被改變其值 , 指到下一個 socket (註:使用同一指標 p_num,原因所致)

發表留言