[C++] 155 TCP/IP HTTPプロトコル

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

昨年2021年7月に購入した『C言語によるTCP/IPネットワークプログラミング』(初版 2001年)をようやく読み始めました。

まずはHTTPプロトコルのサンプルコードを走らせようとしましたが、何せ21年前に書かれたC99のコードなので案の定エラーの嵐でした。

色々修正し、さらにC言語をC++に変えて完成させました。

修正手順
1.ヘッダファイルを別途作成
2.”exit(-1)”を”return -1″に書き換え他
3.va_listまわりの修正
4.Makefileの書き換え(ccコンパイラをclang++へ変更他)

一番苦労したのは変数引数リストva_listを作成するところです。元ソースにあったvarargs.hがすでに廃止されていたため、stdarg.hに切り替えました。引数の内容が変わっており、修正に結構時間を費やしました。

Mac mini内にMAMPで仮想Webサーバーを立ち上げ、その中にあるindex.htmlにアクセスしてみました。

とりあえず動きましたが、コードの内容についてはこれから理解を深めていきます。

# コマンド
httpget localhost:/index.html
--------------------------------------------------
出力
--------------------------------------------------
host=localhost,path=/index.html
GET /index.html HTTP/1.0

HTTP/1.1 200 OK
Date: Tue, 23 Aug 2022 16:06:37 GMT
Server: Apache/2.4.46 (Unix) OpenSSL/1.0.2u PHP/7.4.21 mod_wsgi/3.5 Python/2.7.18 mod_fastcgi/mod_fastcgi-SNAP-0910052141 mod_perl/2.0.11 Perl/v5.30.1
Last-Modified: Tue, 23 Aug 2022 16:04:38 GMT
ETag: "78-5e6eab8a35180"
Accept-Ranges: bytes
Content-Length: 120
Connection: close
Content-Type: text/html
int main(int argc,char *argv[])
{
char buf[512];
char host[MAXHOSTNAMELEN],path[512],*ptr;
int	i;

	if(argc<=1){
		fprintf(stderr,"httpget host:path ...\n");
		// exit(-1);
		return -1;
	}

	// 引数のループ 
	for(i=1;i<argc;i++){
		// ホスト:パス 
		strcpy(buf,argv[i]);
		if((ptr=strtok(buf,":"))==NULL){
			strcpy(host,"localhost");
			strcpy(path,argv[1]);
		}
		else{
			strcpy(host,ptr);
			if((ptr=strtok(NULL,""))==NULL){
				fprintf(stderr,"host:path error\n");
				// exit(-1);
				return -1;
			}
			strcpy(path,ptr);
		}
		// HTTP取得実行 
		DoHttpGet(host,path);
	}
}

int SOCprintf(int num, ...)
// va_dcl
{
	va_list args;
	int	soc;
	char *fmt;
	char buf[4096];

	va_start(args,num);
	soc = va_arg(args,int);
	fmt = va_arg(args,char *);

	vsprintf(buf,fmt,args);
	vfprintf(stderr,fmt,args);
	va_end(args);

	send(soc,buf,strlen(buf),0);

	return 0;
}

[C++] 154 マルチバイト文字有無の判定 コード改良の試み mbstowcs関数

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

前回の続きです。

どうしても気になったので、さらに調べてみました。

どうやらappファイルではmbstowcs関数がまともに動かないようです。マルチバイト文字を1文字としてカウントすることができません。

下図の右ウインドウに表示されているように、#FFFFFF(#は3バイト文字)がstring、wstringともに10文字としてカウントされています。mbstowcs関数が機能していればwstringは8文字になるはずです。同時にビルドした実行ファイルでは実際そうなっています。

これはライブラリを提供しているApple側の問題に思えます。これで一応の結論にたどり着きました。

MacOSでC++を扱っていると細かいところで非対応や不具合に遭遇します。やはりWindowsのVisual C++が至高だと思います。まあいざとなればObjective-C++へ鞍替えします。

int narrowToWide(string str) {
	wchar_t *wcs = new wchar_t[str.length() + 1];
	int num = mbstowcs(wcs, str.c_str(), str.length() + 1);
    
	return num;
}

int multibyteDetect(string str){
    int length = str.length();
    cout << "length " << length << endl;
    output_line2->insert("length ");
    output_line2->insert((to_string(length)).c_str());
    output_line2->insert("\n");

    int length_w = narrowToWide(str);
    cout << "length_w " << length_w << endl;
    output_line2->insert("length_w ");
    output_line2->insert((to_string(length_w)).c_str());
    output_line2->insert("\n");

    if (length != length_w){
        return -1;
    }
    
    return 0;
}

[C++] 153 マルチバイト文字有無の判定 コード改良の試み

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

前回の続きです。

そう言えば、ちょうど1ヶ月前にマルチバイト文字の抽出方法について記事を書いていました。その内容を流用します。

stringをwstringに変換して文字数を取得すれば、マルチバイト文字を1文字扱いにした全文字数+αが分かります。

これで文字列が可変長でも対応できるはずですが、自製カラーアプリのappファイルが落ちるようになってしまったため、前回のコードのままにしておきます。なおコンソール付きの実行ファイルでは問題なく動作します。

マルチバイト文字にあまり関わると深みにはまってしまうので、これ以上追究するのはやめておきます。

wstring narrowToWide(const string src) {
    wchar_t *wcs = new wchar_t[src.length() + 1];
    mbstowcs(wcs, src.c_str(), src.length() + 1);
    return wcs;
}

int multibyteDetect(string str){
    int length = str.length();
    cout << "length " << length << endl;

    wstring str_w = narrowToWide(str);
    int length_w = str_w.length();
    cout << "length_w " << length_w << endl;

    // stringの内容確認
    int i = 0;
    for (int b: str){
        cout << "string " << i << " " << b << endl;
        i++;
    }

    // stringの内容確認2
    for (int i = 0 ; i < str.length() ; ++i){
        cout << "string2 " << i << " " << str[i] << endl;
    }

    // wstringの内容確認
    int i2 = 0;
    for (int b: str_w){
        cout << "wstring " << i2 << " " << b << endl;
        i2++;
    }

    // wstringの内容確認2
    for (int i = 0 ; i < str_w.length() ; ++i){
        cout << "wstring2 " << i << " " << str_w[i] << endl;
    }

    if (length != length_w){
        return -1;
    }
    
    return 0;
}
--------------------------------------------------
出力例 #FFFFFF 数学記号の#(ここでの表記は1バイト文字の#)
--------------------------------------------------
length 10
length_w 8

string 0 35
string 1 -30
string 2 -128
string 3 -83
string 4 70
string 5 70
string 6 70
string 7 70
string 8 70
string 9 70

string2 0 #
string2 1 ?
string2 2 ?
string2 3 ?
string2 4 F
string2 5 F
string2 6 F
string2 7 F
string2 8 F
string2 9 F

wstring 0 35
wstring 1 8237
wstring 2 70
wstring 3 70
wstring 4 70
wstring 5 70
wstring 6 70
wstring 7 70

wstring2 0 35
wstring2 1 8237
wstring2 2 70
wstring2 3 70
wstring2 4 70
wstring2 5 70
wstring2 6 70
wstring2 7 70

[C++] 152 マルチバイト文字有無の判定

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

HTMLやCSSではカラーコードは#を先頭に付けます。

ただ、この#が曲者で1バイト文字、3バイト文字(数学記号)、3バイト文字(全角)の3種類あります。

ネット初心者以外ではまずないとは思いますが、後2者が混ざり込んでいるのに気が付かずネットに貼ってしまう方がいたりします。全角の場合は見れば分かるものの、数学記号の方は見た目が全く同じで厄介です。

そのようなカラーコードをアプリにコピペした際、不具合が生じないようにするため合計バイト数が7である場合のみ処理を進めるようにしました。

文字列が可変長の場合については調査中です。

ネットから#を含む文字列をコピペする場合はそのようなリスクがあることを知っておくべきでしょうね。

int multibyteDetect(string str){
    int length = str.length();
    cout << "length " << length << endl;

    if (length != 7){
        return -1;
    }
    return 0;
}

[C++] 151 FLTK : Fl_Multiline_OutputとFl_Text_Displayの比較

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_MultiLine_OutputにFl_Scrollbarを内包させられるかどうか公式ドキュメントを調べたところ、そもそもの性質が合わないために難しいことが判明しました。

調べた結果を以下の図にまとめました。違和感を覚えていたFl_Groupの命名の意味がようやく理解できました。言っても仕方ないですが、Fl_Containerとでも命名してくれていたら理解は早かったと思います。

Fl_Input_群は行数を把握できない低機能なウィジェットであることが分かりました。行数が分からないのではFl_Scrollbarを内包できないです。

ただFl_ScrollをコンテナとしてFl_Multiline_Outputを中に配置した場合に適切に動作するよう調整が可能なのか、確認しておく必要があります。

FLTKのクラス名をJava的な感覚で解釈すると痛い目にあいますね。Swingがいかに小慣れた巧みな命名をしているか、Swingの方がむしろ特殊で突出して優れていると見るべきでしょうか。だからこそ一時代を築くことが出来たのだと思います。

A

[C++] 150 FLTK : Fl_Multiline_Outputにスクロールバー配置 未完成

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_Multiline_Outputにスクロールバーを配置することは可能ですが、テキスト行数とは連動せず使えません。Fl_Scrollの高さを短くすると動くことは動くものの、上端から下端までカバーできないです。

Fl_Text_Displayのソースコードを読み、Fl_Multiline_Outputに内包できるかどうか検討してみます。

水色のFl_Multiline_Outputはテキスト行が増えてもスクロールバーに変化なし
Fl_Scroll* scroll = new Fl_Scroll(10,165,340,130,"My Output");
    scroll->type(Fl_Scroll::VERTICAL_ALWAYS);
    scroll->align(Fl_Align(FL_ALIGN_TOP_RIGHT));
    scroll->labelsize(10);
    scroll->labelcolor(fl_rgb_color(255,255,255));
    
Fl_Multiline_Output* outputLine = new Fl_Multiline_Output(10,165,340,130);
    outputLine->color(fl_rgb_color(188,226,232)); // みずいろ
    outputLine->textsize(12);
    outputLine->type(FL_MULTILINE_OUTPUT_WRAP);
    outputLine->labelsize(10);
    outputLine->labelcolor(fl_rgb_color(0,0,0));
    
scroll->end();

[C++] 149 FLTK : スクロールバー内包ウィジェット Fl_Text_Display

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_Multiline_Outputがスクロールバーを内包しないことを知り少なからず落胆しましたが、気を取り直してFl_Text_DisplayでGUIを再構築しました。

字面からのイメージで適当に組んでみたら、たまたま上手くいきました。

表示文字列のstringを作成してFl_Text_Bufferに放り込み、Fl_Text_Displayで表示するという流れです。

初期状態はWindowと同じ色ですが、Fl_Text_Bufferを読み込むと設定した色に変わります。

Fl_Text_Display *textDisplay;
Fl_Text_Buffer *textBuffer;
string bufferstr;

void formatConvert(){
    string path2;
    textBuffer = new Fl_Text_Buffer(0, 12800);

    // フォーマットリストのindex取得
    int formatNum = choice->value();

    // 変換元のファイルパス取得
    const char* path = input_line->value();
    cout << "path "<< path << endl;

    // stringへ変換
    string path_str = string(path);

    // ファイルパスに半角スペースが含まれる場合はアンダースコアに置き換えたファイル名に変更
    if (path_str.find(" ") != std::string::npos)
    {
        path2 = spt.splitJoin2(path_str, " ", "_", 0, -1);

        // 元ファイルをリネーム
        rename(path, path2.c_str());
    } else {
        path2 = path_str;
    }

    cout << "path2 "<< path2 << endl;

    // 自製ライブラリSplitによりファイル名から拡張子を削除
    string prefix = spt.splitJoin(path2, ".", 0, -2);
    cout << "prefix "<< prefix << endl;

    // 変換先のファイルパス作成
    string path3 = prefix + fmt[formatNum];
    cout << "path3 "<< path3 << endl;

    // ファイル変換コマンド作成
    string cmd = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffmpeg -y -i " + path2 + " -f " + fmt[formatNum] + " " + path3 + " && echo ffmpeg完了";
    cout << "cmd: "<< cmd << endl;

    string appendstr = "cmd: " + cmd + "\n";
    bufferstr += appendstr;
    cout << "bufferstr: " << bufferstr << endl;

    textBuffer->append(bufferstr.c_str());
    textDisplay->buffer(textBuffer);

    outputTextMake(cmd);
    browser->load(outputText);
    int line_num = browser->size();
    browser->bottomline(line_num);

    cout << "フォーマット変換完了!" << endl;
}
Fl_Text_Display* textDisplay = new Fl_Text_Display(10,165,340,130,"My Output");
    textDisplay->color(fl_rgb_color(188,226,232)); // みずいろ
    textDisplay->textsize(12);
    textDisplay->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); // 折り返し設定
    textDisplay->labelsize(10);
    textDisplay->labelcolor(fl_rgb_color(255,255,255));
    textDisplay->align(Fl_Align(FL_ALIGN_TOP_RIGHT));

[C++] 148 FLTK : Fl_Multiline_Outputはスクロールバーを内包しない

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ビデオツールアプリを作り込んでいますが、ここにきてFl_Multiline_Outputがスクロールバーを内包しないことが判明しました。まさかの事態です。

Fl_Text_Displayがスクロールバーを内包しているので取りあえずこれに置き換えましたが、リッチテキスト用ですからこれまでとは流儀が全く異なります。まず背景色を以前の水色に設定できません。insert関数の使い方も違うようです。

FLTKについては色々クセが強いところに慣れながら使ってきたものの、メインとして使うには時間的コストが過大です。

先日導入したwxWidgetsを試してみたいところです。

[C++] 147 FLTK : Fl_Multiline_Outputの折り返し設定

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

結構長い期間の懸案だったFl_Multiline_Outputの折り返し設定ができました。

公式サイトの”Fl_Input_ Class Reference” – “Detailed Description”に説明がありました。上位クラスにあるとは思っていましたが、Fl_InputではなくFl_Input_なのが盲点でした。ちなみに折り返しは英語ではwrapになります。

Fl_Multiline_Output outputLine = new Fl_Multiline_Output(10,165,340,130,"My Output");
    outputLine->color(fl_rgb_color(188,226,232)); // みずいろ
    outputLine->labelcolor(fl_rgb_color(255,255,255));
    outputLine->textsize(12);
    outputLine->align(Fl_Align(FL_ALIGN_TOP_RIGHT)); // ラベルの配置
    outputLine->type(FL_MULTILINE_OUTPUT_WRAP); // 折り返し設定
    outputLine->labelsize(10);

[C++] 146 FLTK : アプリ内コマンドは絶対パス FFmpeg

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ffmpegコマンドをアプリ内で実行する際、コマンドは絶対パスにしないと動作しません。コンソール付き実行ファイルではコマンド名だけで動作するものの、appファイルでは動かないです。

ffmpegコマンドの標準出力はpopenで捉えられませんでしたが、movからmp4への変換自体はできたのでよしとします。

22/8/18追記:
FFmpegの公式サイトからffmpeg-107780-g6ded80af92.7zをダウンロードして実行ファイルを使ってみたところ、MacOSがカーネル機能拡張されたとかでパニック修復ブートにて再起動し、その後再起動を繰り返すようになりました。
ディスクユーティリティのFirst Aidでは直らなかったので、TimeMachineで復元し、復活させました。偶然かもしれませんが、取扱は慎重にした方が良さそうです。MacOSの場合はHomebrewからもインストールできます。こちらは問題ありませんでした。
これまでFirst Aidが修復に貢献したことは一度もなく時間の無駄なので、今後は即TimeMachineで復元します。走り出したら中断できないのも低評価です。

void outputTextMake(string cmd){
    char str[READ_SIZE];
    size_t ret;

    FILE* fp = popen(cmd.c_str(), "r");
    FILE* fo = fopen(outputText, "a+");

    while(1){
        ret = fread(str, sizeof(char), READ_SIZE, fp);
        fwrite(str, sizeof(char), ret, fo);
        
        if(ret < READ_SIZE){
            break; 
        } 
    }

    // 文字列追記
    string finish = "変換完了\n";
    fprintf(fo, "%s", finish.c_str());

    pclose(fp);
    fclose(fo);
}

void formatConvert(){
    string path2;

    // フォーマットリストのindex取得
    int formatNum = choice->value();

    // 変換元のファイルパス取得
    const char* path = input_line->value();
    cout << "path "<< path << endl;

    // stringへ変換
    string path_str = string(path);

    // ファイルパスに半角スペースが含まれる場合はこれをアンダースコアに置換したファイル名に変更
    if (path_str.find(" ") != std::string::npos)
    {
        path2 = spt.splitJoin2(path_str, " ", "_", 0, -1);

        // 元ファイルをリネーム
        rename(path, path2.c_str());
    } else {
        path2 = path_str;
    }

    cout << "path2 "<< path2 << endl;

    // 自製ライブラリSplitによりファイル名から拡張子を削除
    string prefix = spt.splitJoin(path2, ".", 0, -2);
    cout << "prefix "<< prefix << endl;

    // 変換先のファイルパス作成
    string path3 = prefix + fmt[formatNum];
    cout << "path3 "<< path3 << endl;

    // ファイル変換コマンド作成(ffmpegは絶対パス)
    string cmd = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffmpeg -i " + path2 + " -f " + fmt[formatNum] + " " + path3 + " && echo ffmpeg完了";
    cout << "cmd: "<< cmd << endl;
    output_line->insert("cmd: ");
    output_line->insert(cmd.c_str());
    output_line->insert("\n");

    outputTextMake(cmd);
    browser->load(outputText);

    cout << "フォーマット変換完了!" << endl;
}