C言語で大きなサイズのファイル読み込みと書き出し

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
iop@

C言語で大きなサイズのファイル読み込みと書き出し

#1

投稿記事 by iop@ » 12年前

Linuxの組込みプログラムを書いています。

下記のようなファイルから、余分なヘッダーとフッターを取り除いてバイナリデータのみをファイルに書き出すというようなことを実現するのに、よい方法はありますでしょうか。
ファイルは約20MB程度あります。
組込みなので、メモリにあまり余裕がなく、できれば大きなサイズ(メガオーダー)のmallocは避けたいと思っています。
毎回malloc成功する保証がないため。

C言語での解決策を考えています。
よろしくお願いします。

**************ファイルここから****************
------------XXXXX
content-disposition: form-data; name="test"; filename="test.bin"
Content-Type: application/octet-stream                           
                                                   ←ここまでが余分なヘッダー
... test.bin の内容(バイナリ) ...

----------XXXXX--                                        ←ここが余分なフッター
**************ファイルここまで****************

katariya
記事: 11
登録日時: 12年前

Re: C言語で大きなサイズのファイル読み込みと書き出し

#2

投稿記事 by katariya » 12年前

コード:

	FILE* fpR = fopen("test.bin", "rb");
	FILE* fpW = fopen("testtemp.bin", "wb");
	if(fpR == NULL)
	{
		fclose(fpW);
		return;
	}
       
  fseek(fpR, …, SEEK_SET);     // ヘッダーを読み飛ばし

	while(!feof(fpR))
	{
		unsigned char temp;
		int ret = fread(&temp, sizeof(char), 1, fpR);
		
                
        if(...) 
		{// フッターの終了条件(データ長とか)
			break;
		}
		fwrite(&temp,sizeof(char), 1, fpW);
	}

	fclose(fpR);
	fclose(fpW);
時間はかかりますが、確実に最後の1バイトまでコピーは出来ます。

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前
住所: 東京
連絡を取る:

Re: C言語で大きなサイズのファイル読み込みと書き出し

#3

投稿記事 by h2so5 » 12年前

katariyaさんの1byte単位の方法だと効率が悪いだけではなく、一文字しか分からないのでフッターの判定が困難です。
(データのフォーマットからするとHTTPですよね)

まずfgetsでまとまったバイト数を読み込んでヘッダー・フッターの位置を判定(フッターはファイルの最後から適当なサイズだけバックした位置から読み込み)、そのあとヘッダー終了位置まで戻ってバイナリデータを適当に分割しながらfreadしたあと書き込む、という方法が良いと思います。
この方法でもそんなに大きなバッファは必要ないでしょう。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: C言語で大きなサイズのファイル読み込みと書き出し

#4

投稿記事 by みけCAT » 12年前

組んでみました。適当に改造してください。
単純にバッファリングするとフッタが切れてしまい、検出できない危険があるので、
バッファを2個使って半分ずつ出力したのがポイントです。

コード:

#include <stdio.h>
#include <string.h>

/* バッファサイズ ヘッダおよびフッタ全体が入る量を確保してください */
#define BUFFER_SIZE 1024

/* フッタ(最後の余計なデータ)を作成する */ 
void generateFooter(char* footer,const char* initHeader) {
	/* 仕様に合わせて実装してください */ 
	strcpy(footer,"\n");
	strcat(footer,initHeader+2);
	strcat(footer,"--");
}

/* ヘッダの終端を見つけ、ヘッダの後のデータの開始位置のポインタを返す */
/* ヘッダの終端がみつからなかったらNULLを返す */ 
char* searchHeaderTerminater(char* data) {
	/* 仕様に合わせて実装してください */
	char* headerEnd=strstr(data,"\n\n");
	if(!headerEnd)return NULL;
	return headerEnd+2;
}

/* フッタを見つけ、フッタの開始位置のポインタを返す */ 
/* フッタがみつからなかったらNULLを返す */ 
char* searchFooter(char* data,const char* footer) {
	/* 仕様に合わせて実装してください */
	int i;
	int len=strlen(footer);
	int owari=BUFFER_SIZE*2-len;
	for(i=0;i<owari;i++) {
		if(strncmp(data,footer,len)==0)return data;
		data++;
	}
	return NULL;
}

int main(int argc,char* argv[]) {
#pragma pack(1)
	struct dataBuffer_t {
		int firstSize;
		int secondSize;
		char first[BUFFER_SIZE];
		char second[BUFFER_SIZE];
	} buffer;
#pragma pack()
	char initHeader[BUFFER_SIZE]; /* ------------XXXXX を読み込んで格納する */
	char footer[BUFFER_SIZE]; /* initHeaderからフッタを作成する */
	FILE* fpin;
	FILE* fpout;
	char* dataStartPointer;
	int dataSize;
	int footerFound;
	if(argc<3) {
		fprintf(stderr,"Usage: deleteheader <input file> <output file>\n");
		return 1;
	}
	fpin=fopen(argv[1],"rb");
	fpout=fopen(argv[2],"wb");
	if(!fpin || !fpout) {
		if(fpin)fclose(fpin);
		if(fpout)fclose(fpout);
		fprintf(stderr,"ファイルオープンに失敗しました。\n");
		return 1;
	}

	/* ------------XXXXX を読み込んでフッタを作成する */
	fgets(initHeader,sizeof(initHeader),fpin);
	{
		char* newLine=strchr(initHeader,'\n');
		char* newLine2=strchr(initHeader,'\r');
		if(newLine==newLine2+1)newLine=newLine2;
		if(newLine)*newLine=0;
	}
	generateFooter(footer,initHeader);

	/* ヘッダを読み込んでヘッダ以外を出力 */
	buffer.firstSize=fread(buffer.first,1,BUFFER_SIZE,fpin);
	dataStartPointer=searchHeaderTerminater(buffer.first);
	if(dataStartPointer==NULL) {
		fprintf(stderr,"ヘッダ終端が見つかりません。\n");
		fclose(fpin);
		fclose(fpout);
		return 1;
	}
	dataSize=(int)(buffer.first+buffer.firstSize-dataStartPointer);
	memmove(buffer.first,dataStartPointer,dataSize);
	buffer.firstSize=dataSize+fread(buffer.first+dataSize,1,BUFFER_SIZE-dataSize,fpin);

	/* データを読み込んで出力 */
	footerFound=0;
	buffer.secondSize=0;
	do {
		char* footerPointer;
		buffer.secondSize=fread(buffer.second,1,BUFFER_SIZE,fpin);
		footerPointer=searchFooter(buffer.first,footer);
		if(footerPointer) {
			fwrite(buffer.first,1,(int)(footerPointer-buffer.first),fpout);
			footerFound=1;
			break;
		} else {
			fwrite(buffer.first,1,BUFFER_SIZE,fpout);
			memcpy(buffer.first,buffer.second,BUFFER_SIZE);
		}
	} while(!feof(fpin));
	if(!footerFound) {
		if(buffer.secondSize>0) {
			fwrite(buffer.first,1,BUFFER_SIZE,fpout);
			fwrite(buffer.second,1,buffer.secondSize,fpout);
		} else {
			fwrite(buffer.first,1,buffer.firstSize,fpout);
		}
	}
	fclose(fpin);
	fclose(fpout);
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ソラトブペンギン
記事: 3
登録日時: 12年前

Re: C言語で大きなサイズのファイル読み込みと書き出し

#5

投稿記事 by ソラトブペンギン » 12年前

もとのファイルを見る限り、メールやHTTPの中にあえるMIMEのパルチパートデータを分割してファイルを取り出そうとしているのでしょうか。
そうであれば、ある程度MIMEのフォーマットを考慮した解析をかけた方が確実ですし、これくらいなら余りメモリを使わなくてもなんとかなると思います。

まず、マルチパートデータの場合、「-XXXX」がboundaryというパラメータが最初に記述されています。
この文字列はマルチパートデータの区切り部分を示す文字列で、ヘッダやデータ内部と確実にかぶらないようになっています。
サンプルのファイルであれば、ファイルの先頭行にある文字列と最後にある文字列(の先頭部分)が一致しているはずです。
boundaryはどんなに長くても120文字を超えることは無いと思いますが、余裕あれば1kbyteくらいで先頭行を記憶しておきます。これを使えば、データの終了部分を検出できます。

次に、ヘッダについて。
ヘッダ部分が終了すると空行が1行入り、その後にデータが始まります。
空行を検知したら、次のデータからファイルの書き込みを開始すればOKです。

そして、データの読み込みファイルの書き込み。
少しずつファイルを読み込んで記憶したboundary(の先頭部分)と一致する部分が無いか確認します。
全く一致しなければそのまま書き込み、一致するようならそこで書き込みをストップさせます。

このような流れにすれば、少しファイルを読み込んで少しファイルを書き込む、といった処理になるので、ファイル全体をメモリに格納せずに済むはずです。
(投稿してから みけCAT さんの方法ほぼそのままだったことに気づく...)


ただ、サンプルファイルを見て気になる点が一つ。
ヘッダの中にContent-Transfer-Encodingと記述してあるものはありませんか?
メールなどは純粋なバイナリを嫌う傾向にあり、バイナリを無理矢理テキストに変換してから転送するのが通例になっています。
もし記述されていれば、変換されたテキストをバイナリに戻すデコード処理を行う必要があるのですが、いかがでしょうか。
(逆にすべてテキストに変換されていれば最後のboundaryの検出処理がグッと楽になるのですが...)

閉鎖

“C言語何でも質問掲示板” へ戻る