【C】ソケットAPIを用いたデータロガープログラムを書いてみる【socket】

こんにちは。
TCP/IP でのソケットを用いたデータ通信の仕組みに関して改めて勉強してみたので、自分自身の理解のために簡単なデータロガープログラムを書いてみました。
※ いつも通りエラーハンドリングは 無視 省力です。

client.c

まずはクライアント側ですね。
socket() でソケットを作成した後に、connect() を使えばコネクションが張れるようです。
後は生成したソケットを使って、send() でサーバ側にデータを送るだけです。
送信するデータは fgets() で標準入力から取得します。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void){
    int sockfd;
    char mes[64];
    struct sockaddr_in server;

    server.sin_family = AF_INET;
    server.sin_port = htons(8888);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    connect(sockfd, (struct sockaddr *)&server, sizeof(server));

    printf("INPUT:");
    fgets(mes, sizeof(mes), stdin);

    send(sockfd, mes, 64, 0);

    close(sockfd);

    return 0;
}

接続先サーバの情報は sockaddr_in 構造体を使って socket に紐づけます。
※ sockaddr にキャストする必要アリです。
例では localhost の 8888 ポートに接続しています。

server.c

次はサーバ側ですね。
サーバは bind() を使って ソケットに IPアドレス/ポート番号を紐づけ、listen() で割り当てたポート番号に接続を作成できることをシステムに伝えます。
※ 例では最大5つの接続を受け持ちます。( 後段の処理が完了するまで最大5つまでの接続要求がキューに入ります )
実際のデータの送受信にはこのソケットは用いず、accept() を使ってデータ送受信用のソケットを生成して処理を委任します。
後は recv() を使ってデータを受信し、ファイルにデータを書き込み データ送受信用のソケットをクローズします。
※ 36行目には到達しないです。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void){
    int sock, sockfd, sock_size;
    char mes[64];
    struct sockaddr_in server;
    struct sockaddr_in client;
    FILE *file;

    server.sin_family = AF_INET;
    server.sin_port = htons(8888);
    server.sin_addr.s_addr = INADDR_ANY;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bind(sockfd, (struct sockaddr *)&server, sizeof(server));

    listen(sockfd, 5);

    while(1){
        sock_size = sizeof(client);
        sock = accept(sockfd, (struct sockaddr *)&client, &sock_size);

        recv(sock, mes, 64, 0);
        file = fopen("loger.txt", "a");
        fprintf(file, mes);
        fclose(file);
        
        close(sock);
    }
    close(sockfd);
    return 0;
}

クライアントと同じく、受け持つIPアドレス/ポート番号を sockaddr_in 構造体を使って socket に紐づけます。
こちらも sockaddr にキャストする必要アリです。
※ INADDR_ANY は全ての接続元( 0.0.0.0 )を表しています。

まとめると下記の形になりますね。

1.socket() を実行して TCPソケットを作成する。
2.bind() を実行してソケットにポート番号(IPアドレスも)を割り当てる。
3.listen() を実行し割り当てたポート番号へ接続を作成できることをシステムに伝える。
4.以下繰り返し
・接続要求を受け取るたびに、accept() を呼び出して新規ソケットを取得
・作成したソケットを介してクライアント(接続要求元)とやり取り
・close() でクライアントとの接続をクローズ

【C】接続元IPアドレスの JSON を返すだけの Apache モジュールを作ってみる

こんにちは。
普段まだまだ業務でも使う機会の多い Apache ですが、そういえばモジュール周りってどういう風に実装されているんだろうと調べてみた備忘録です。
雛型自体は apxs コマンドで自動的に生成されるそうなので、接続元 IP アドレスの JSON を返すだけの API みたいなよく分からない Apache モジュールを作ってみます。

※ PHP だったら下記2行で終わるんですけどね。

<?php
        $json = ['remote_ip' => $_SERVER["REMOTE_ADDR"]];
        echo json_encode($json);

環境構築

はい、勿論Apacheのモジュールなんで、Apacheの本体と、apxsコマンドを使うので開発ツールを導入します。
ただそれだけ~。

yum groupinstall "Development tools"
yum install httpd httpd-devel

apxsコマンドで雛型を作ります。
cd /usr/local/src
apxs -g -n info
cd info/

下記みたいなディレクトリとファイルが生成されるんで、mod_info.c に処理を書いていきます。
[root@test-server1-1 src]# tree
.
└── info
    ├── Makefile
    ├── mod_info.c
    └── modules.mk

1 directory, 3 files

コード

はい、雛型に1行加えただけです…

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"

/* The sample content handler */
static int info_handler(request_rec *r)
{
    if (strcmp(r->handler, "info")) {
        return DECLINED;
    }
    r->content_type = "text/html";

    if (!r->header_only) {
        /* 接続元IPを表示する */
        ap_rprintf(r,"{\"client_ip\":\"%s\"}\n",r->connection->client_ip);
    }
    return OK;
}

static void info_register_hooks(apr_pool_t *p)
{
    ap_hook_handler(info_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA info_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    NULL,                  /* table of config file commands       */
    info_register_hooks  /* register hooks                      */
};

接続元 IP は構造体のポインタ r , connection のメンバである client_ip に格納されているので、ap_rprintf 関数でこれを表示させます。
/**
*Output data to the client in a printf format
*
*@Parameters
*      r      The current request
*      fmt      The format string
*      ...      The arguments to use to fill out the format string
*
*@Returns
*      The number of bytes sent
*/
int ap_rprintf( request_rec * 	r,
                const char *    fmt,
                                ... 
)

コンパイル

こちらも apxs コマンドでモジュールのコンパイルと Apache へのモジュールのロード設定まで自動で完了します。

# ディレクトリ移動&コンパイル
cd /usr/local/src/info
apxs -c -i -a mod_info.c

# Apache設定追加
cat >> /etc/httpd/conf/httpd.conf << EOF
<Location "/info">
    SetHandler info
</Location>
EOF

# Apache起動
systemctl start httpd

curl で叩いたら json が返ってきますね。OKです。

keisuke@DESKTOP-MOGIJIA:~$ curl http://192.168.33.10/info
{"client_ip":"192.168.33.1"}

【C言語】単純なTCPサーバをdemontoolsでデーモン化する【demontools】

C 言語で単純な TCP サーバを作って demontools でデーモン化してみます。
※ demontools を使う機会があったので備忘録を兼ねています。

TCPサーバ

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(){
        int sockfd, sock_size, sock;
        struct sockaddr_in server;
        struct sockaddr_in client;

        /* ソケット作成 */
        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        server.sin_family = AF_INET;
        server.sin_port = htons(8888);
        server.sin_addr.s_addr = INADDR_ANY;

        /* ソケットにポート番号割り当て */
        bind(sockfd, (struct sockaddr *)&server, sizeof(server));

        /* 割り当てたポート番号へ接続が作成できることをシステムに伝える */
        listen(sockfd, 5);

        /* 接続要求を受ける度にソケットを新しく取得 */
        while(1){
                sock_size = sizeof(client);
                sock = accept(sockfd, (struct sockaddr *)&client, &sock_size);
                write(sock, "TEST\n", 5);
                /* クライアントとの接続をクローズ */
                close(sock);
        }
        close(sockfd);
        return 0;
}

エラー処理は省いています。
適当なディレクトリにコンパイルしてください。
# ディレクトリ移動
cd /usr/local/bin
vi main.c
===================
*上記コード
===================
# コンパイル
gcc main.c -o simple-server

demontools

demontools の設定です。

# demontoolsの導入
mkdir -p /package
chmod 1755 /package
cd /package
wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
gunzip daemontools-0.76.tar
tar -xpf daemontools-0.76.tar
rm -f daemontools-0.76.tar
cd admin/daemontools-0.76
cd src/

# ヘッダファイル修正
vi error.h
=======================
extern int errno;

↓

#include <errno.h>
=======================

# インストール
cd ..
package/install

# centos7 では使用しないのでコメントアウト
vi /etc/inittab
=======================
#SV:123456:respawn:/command/svscanboot
=======================

# サービスファイル作成
vi /etc/systemd/system/demontools.service
======================================================
[Unit]
Description=run demontools during boot
After=network.target

[Service]
User=root
Group=root
Type=simple
RemainAfterExit=yes
ExecStart=/command/svscanboot
Restart=always
LimitNOFILE=20480

[Install]
WantedBy=multi-user.target
======================================================


# プロセスのデーモン化
mkdir /service/simple-server/
vi /service/simple-server/run
======================================================
#!/bin/sh

exec /usr/local/bin/simple-server
======================================================

# ログ出力設定
mkdir /service/simple-server/log/
mkdir /var/log/simple-server
vi /service/simple-server/log/run
==============================================
#!/bin/sh

exec /usr/local/bin/multilog t /var/log/simple-server
==============================================

# 権限変更
chmod 755 /service/simple-server/run
chmod 755 /service/simple-server/log/run

# demontools 起動/自動起動設定
systemctl start demontools
systemctl enable demontools