コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

マウスの座標を表示する

Javascriptでマウスの座標(左上端からのX,Y距離)を表示する。

ソースコード

mouse.html

<HTML>
<HEAD>
    <TITLE> マウス座標 </TITLE>
</HEAD>
<BODY>
    マウス座標
    <br>
    (X,Y) = 
    <input type="text" style="width:50;" id="global_x">,
    <input type="text" style="width:50;" id="global_y"> <br>

    <SCRIPT type="text/javaSCRIPT">
        (function (){
            // スクロール位置取得
            function GetScrollPosition(obj){
                return{
                    x:obj.body.scrollLeft || obj.documentElement.scrollLeft,
                    y:obj.body.scrollTop  || obj.documentElement.scrollTop
                };
            }

            // マウス追従
            function TailingMouseMotion(e){

                var scrlpos = GetScrollPosition(document);
                var position;

                // X座標計算
                position = document.getElementById("global_x");
                position.value = e.clientX + scrlpos.x;
                
                // Y座標計算
                position = document.getElementById("global_y");
                position.value = e.clientY + scrlpos.y;
            }

            // イベント処理
            if(document.addEventListener){
                document.addEventListener("mousemove" , TailingMouseMotion);
            }else if(document.attachEvent){
                document.attachEvent("onmousemove" , TailingMouseMotion);
            }

        })();
        
    </SCRIPT>
</BODY>
</HTML>

実行結果

f:id:segmentation-fault:20170227225414p:plain





次回は適当なgif画像を追従させるようにしたい。

Windows-Linux間をTCPで通信する

TCPを使ってWindowsとLinux間で通信してみる。
具体的にはWindows上にVirtualboxで仮想マシンを作成しHost-Only-Adapter経由で通信する。

構成

  • サーバ    : Linux CentOS 6, X86_64(64bit) on Virtualbox
  • クライアント : Windows7 (32bit)

プログラム作成

クライアント側

実行バイナリはVisual Studio 2017 で作成。


TCP_Client.cpp

#include "stdafx.h"

#include <stdio.h>
#include <winsock2.h>

#define PORT_NO_SERVER (12345)
#define IP_ADDRESS_SERVER "192.168.100.254"

int main(void)
{
    WSADATA wsaData;
    struct sockaddr_in server;
    SOCKET sfd = -1;
    char buf[32] = { 0 };
    int rc = 0;

    WSAStartup(MAKEWORD(2, 0), &wsaData);

    sfd = socket(AF_INET, SOCK_STREAM, 0);

    server.sin_family = AF_INET;
    server.sin_port = htons(PORT_NO_SERVER);
    server.sin_addr.S_un.S_addr = inet_addr(IP_ADDRESS_SERVER);

    rc = connect(sfd, (struct sockaddr *)&server, sizeof(server));
    if (rc < 0) {
        printf("connect() failed(%d)\n", rc);
        exit(EXIT_FAILURE);
    }

    while (1) {
        memset(buf, 0, sizeof(buf));
        int rs = recv(sfd, buf, sizeof(buf), 0);

        printf("recv %d byte, %s\n", rs, buf);
    }

    WSACleanup();

    return 0;
}


プロジェクトプロパティから入力にws2_32.libを追記してビルドする。

f:id:segmentation-fault:20170226145605p:plain




Windows側のIPアドレスを 192.168.100.0/24のネットワークに設定しておく。

C:> ipconfig

Windows IP 構成

イーサネット アダプター VirtualBox Host-Only Network:

   接続固有の DNS サフィックス . . . :
   IPv4 アドレス . . . . . . . . . . : 192.168.100.1
   サブネット マスク . . . . . . . . : 255.255.255.0
   デフォルト ゲートウェイ . . . . . :

サーバ側


tcp_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#define MAX_CLIENTS (1)
#define MSGBUF_SIZE (1024)

int main(void)
{
    int sfd = -1;
    struct sockaddr_in client;
    int socklen = sizeof(client);
    int ac_sfd = -1;
    int rc = 0;

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(12345),
        .sin_addr = {
            .s_addr = INADDR_ANY,
        },
    };

    /* ソケットの作成 */
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        goto error_end;
    }

    rc = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
    if (rc < 0) {
        perror("bind");
        goto close_sfd_end;
    }

    rc = listen(sfd, MAX_CLIENTS);
    if (rc < 0) {
        perror("listen");
        goto close_sfd_end;
    }

    /* 要求受付 */
    ac_sfd = accept(sfd, (struct sockaddr *)&client, &socklen);
    if (ac_sfd < 0) {
        perror("accept");
        goto close_sfd_end;
    }

    while (1) {
        /* メッセージ送信 */
        char msgbuf[MSGBUF_SIZE] = "HELLO WORLD!";
        ssize_t ws = write(ac_sfd, msgbuf, strlen(msgbuf));
        if (ws < 0) {
            perror("write");
            goto close_all_end;
        }

        sleep(1);
    }

 close_all_end:
    close(ac_sfd);
 close_sfd_end:
    close(sfd);
 error_end:

    return 0;
}


ソースコードのビルド

$ gcc tcp_server.c -o tcp_server
$ 



こちらも通信インタフェースに同様のネットワークのIPアドレスを設定しておく。

$ ip -a addr
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:8c:e9:83 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.254/24 brd 192.168.100.255 scope global enp0s8
       valid_lft forever preferred_lft forever

実行結果

確かにサーバが送信した文字列をクライアントで受け取れている。

f:id:segmentation-fault:20170226151420p:plain


キャプチャを見ても確かに3WAYハンドシェイクからデータ送信まで行っている。
(ただし、クライアント→サーバのキャプチャは見えてない。理由不明。)

f:id:segmentation-fault:20170226151710p:plain

Qt5で始めるGUIプログラミング

Qt5を使ってGUIプログラミングに挑戦してみる。
まずはウィンドウを出すだけ。

Linuxサーバでアプリケーションを作成&実行し、表示はWindowsで行うスタイル。

実行環境

ホスト側

  • Windows7 32bit
  • Xming 6.9.0.31
  • Teraterm 4.9.2

サーバ側(実行側)

  • CentOS 6, x86_64 (64bit)
  • Qt 5.6.2

事前準備

ホスト側

下記アプリケーションをインストールする。

Xmingの起動、TeratermのSSH転送設定

インストール後、Xmingを起動しTeratermは下記設定を行う。

f:id:segmentation-fault:20170225190207p:plain

その後、一度Teratermを再起動して再度サーバにログインする。

サーバ側

Qt Downloads から実行環境に応じたバイナリをダウンロードする。今回はqt-opensource-linux-x64-5.6.2.runを選択。

$ chmod +x qt-opensource-linux-x64-5.6.2.run
$ ./qt-opensource-linux-x64-5.6.2.run



すると以下の画面が表示されるので必要事項を入力してインストール実行。

f:id:segmentation-fault:20170225190405p:plain



インストール場所の変更

デフォルトだとhome配下にインストールされるのでパスを変更する。
(インストール時に指定可能だがrootユーザor sudo実行では画面転送が上手くいかない場合がある)

/usr/local/shareへ変更

$ sudo mv Qt5.6.2 /usr/local/share



ld.so.confにライブラリパスを追記

$ sudo vi /etc/ld.so.conf
#/usr/local/share/Qt5.6.2/5.6/gcc_64/lib/ を追記
$
$ sudo ldconfig #追記したパスを反映
$


~/.bashrcにqmakeのパスを追記

$ vi ~/.bashrc
# 下記行を追記
# export QT_ROOT="/usr/local/share/Qt5.6.2/5.6/" 
# export PATH="$QT_ROOT/gcc_64/bin:$PATH"
$
$ source ~/.bashrc #追記したパスを反映
$








プログラム作成

事前準備が終ったのでプログラムを作っていく。


sample.cpp

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QLabel *label = new QLabel("Hello Qt!");

    label->show();

    return app.exec();
}



Makefileを生成する

$ qmake -project
$ qmake qt.pro



ここでmakeを実行するとコンパイルエラーが発生。

$ make
g++ -c -pipe -O2 -std=gnu++0x -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -I. -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtGui -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtCore -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/mkspecs/linux-g++ -o sample.o sample.cpp
sample.cpp:1:24: error: QApplication: No such file or directory
sample.cpp:2:18: error: QLabel: No such file or directory
sample.cpp: In function 'int main(int, char**)':
sample.cpp:6: error: 'QApplication' was not declared in this scope
sample.cpp:6: error: expected ';' before 'app'
sample.cpp:7: error: 'QLabel' was not declared in this scope
sample.cpp:7: error: 'label' was not declared in this scope
sample.cpp:7: error: expected type-specifier before 'QLabel'
sample.cpp:7: error: expected ';' before 'QLabel'
sample.cpp:11: error: 'app' was not declared in this scope
sample.cpp: At global scope:
sample.cpp:4: warning: unused parameter 'argc'
sample.cpp:4: warning: unused parameter 'argv'



こちらによるとqt.proに下記行を追加すれば良いらしい。

$ vi qt.pro
TEMPLATE = app
TARGET = qt
INCLUDEPATH += .
QT += widgets    # この行を追記

# Input
SOURCES += sample.cpp


再度ビルド実行

$ make
/usr/local/share/Qt5.6.2/5.6/gcc_64/bin/qmake -o Makefile qt.pro
g++ -c -pipe -O2 -std=gnu++0x -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtWidgets -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtGui -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtCore -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/mkspecs/linux-g++ -o sample.o sample.cpp
$


コンパイルが成功し実行するが謎のエラーが発生。

$ ./qt
This application failed to start because it could not find or load the Qt platform plugin "xcb"
in "".

Reinstalling the application may fix this problem.
Aborted


こちらによると環境変数 "QT_PLUGIN_PATH"を追加すれば良いらしい。
(stackoverflowさん、いつもありがとう)

$ vi ~/.bashrc
# export QT_PLUGIN_PATH="$QT_ROOT/gcc_64/plugins"  を追記
$

実行結果

$ ./qt


Windows上に下記画面が表示されれば成功。

f:id:segmentation-fault:20170225192900p:plain

シグナル受信をepollで監視

Linuxのsignalfdを使ってシグナルをepollで監視する。


signalfd.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <errno.h>

#define MAX_EVENTS 10
#define RET_OK (0)
#define RET_NG (-1)

#define PERROR(X) \
{\
    char __strerr[128] = {0};\
    int errcode = errno;\
    \
    strerror_r(errcode, __strerr, sizeof(__strerr));\
    printf(X " failed(%d:%s)\n", errcode, __strerr);\
}

static int createSignalFd(int* sfd);
static int waitSignalEvent(int sfd);

int main(void)
{
    int sfd = -1;
    int ret = RET_OK;

    do {
        ret = createSignalFd(&sfd);
        if (RET_OK != ret) {
            printf("createSignalFd() failed(%d)\n", ret);
            break;
        }

        ret = waitSignalEvent(sfd);
        if (RET_OK != ret) {
            printf("waitSignalEvent");
            close(sfd);
            break;
        }

        close(sfd);
    } while(0);


    return 0;
}

static int createSignalFd(int* sfd)
{
    int ret = RET_OK;
    sigset_t    mask;

    sigemptyset(&mask);

    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);

    do {
        if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
            PERROR("sigprocmask()");
            break;
        }

        *sfd = signalfd(-1, &mask, 0);
        if (*sfd < 0) {
            PERROR("signalfd()");
            ret = RET_NG;
            break;
        }
    } while(0);

    return ret;
}

static int waitSignalEvent(int sfd)
{
    int ret = RET_OK;
    int rc = 0;
    int epollfd = -1;
    int nfds = -1;
    struct epoll_event events[MAX_EVENTS];
    struct epoll_event evt = {
        .events = EPOLLIN,
        .data = {
            .fd = sfd,
        },
    };

    epollfd = epoll_create(MAX_EVENTS);
    if (epollfd < 0) {
        PERROR("epoll_create()");
        goto error_end;
    }

    rc = epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &evt);
    if (rc != 0) {
        PERROR("epoll_ctl()");
        close(epollfd);
        goto error_end;
    }

    for(;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            PERROR("epoll_wait()");
            close(epollfd);
            break;
        }

        int fd = 0;
        for (fd = 0; fd < MAX_EVENTS; fd++) {
            if (events[fd].data.fd == sfd) {
                struct signalfd_siginfo fdsi;
                ssize_t sz = read(sfd, &fdsi, sizeof(fdsi));
                if (sz < 0) {
                    PERROR("read()");
                    close(epollfd);
                    break;
                }

                printf("ssi_signo = %d\n", fdsi.ssi_signo);
            }
        }
    }

    close(epollfd);
error_end:

    return ret;
}


実行結果

$ gcc -std=gnu99 signalfd.c -o signalfd
$ ./signalfd
ssi_signo = 1
ssi_signo = 2
ssi_signo = 3
Terminated

#別端末から
$ killall -SIGHUP signalfd
$ killall -SIGINT signalfd
$ killall -SIGQUIT signalfd
$ killall -SIGTERM signalfd

TAILQでリスト管理

TAILQを使ってリストを実現する。
詳細はMan page of QUEUE参照。


tail.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>

typedef struct memEntry {
    TAILQ_ENTRY(memEntry) entry;
    int32_t     size;
    void*       ptr;
} memEntry_t;

typedef struct {
    TAILQ_HEAD(tq_head, memEntry) head;
    int entry_count;
} mngMemPool_t;

static mngMemPool_t mngMem;

void* allocateMemory(size_t  size);
void printAllMemory(void);
void freeAllMemory(void);

int main(void)
{
    TAILQ_INIT(&mngMem.head);

    for (int i = 0; i < 10; i++) {
        void* ptr = allocateMemory(i * sizeof(int));
    }

    printAllMemory();

    freeAllMemory();

    return 0;
}

void* allocateMemory(size_t size)
{
    memEntry_t* ent;

    ent = malloc(sizeof(memEntry_t));
    if (NULL == ent) {
        printf("out-of-memory");
        exit(EXIT_FAILURE);
    }

    ent->ptr = malloc(size);
    if (NULL == ent->ptr) {
        free(ent);
        printf("out-of-memory");
        exit(EXIT_FAILURE);
    }

    ent->size = size;

    TAILQ_INSERT_TAIL(&mngMem.head, ent, entry);

    return ent->ptr;
}

void printAllMemory(void)
{
    memEntry_t* np = NULL;
    for (np = mngMem.head.tqh_first; np != NULL; np = np->entry.tqe_next) {
        printf("size=%d\n", np->size);
    }
}

void freeAllMemory(void)
{
    while (mngMem.head.tqh_first != NULL) {
        memEntry_t* np = mngMem.head.tqh_first;
        TAILQ_REMOVE(&mngMem.head, np, entry);
        free(np->ptr);
        free(np);
    }
}

実行結果

$ gcc -std=gnu99 tailq.c  -o tailq
$ ./tailq
size=0
size=4
size=8
size=12
size=16
size=20
size=24
size=28
size=32
size=36

ソケットプログラミング(UDP)

LinuxUDPによる通信を行うサンプルプログラム。

この手の例はGoogle先生で検索すれば沢山でてくるけど、システムコールのリターン値を見てなかったり、openに対応するcloseをしていない例が多いことに気付くと思う。

もちろん動かすことだけ考えたら気にする必要ないし、この例も完璧とは言えないけど、出来るだけ丁寧な処理を書くことを心がけるようにしたい。



udp_client.c

#include "local.h"

int main(void)
{
    udpSocket_t udp;

    udp.sfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (udp.sfd < 0) {
        PERROR("socket()");
        goto error_end;
    }

    udp.addr.sin_family = PF_INET;
    udp.addr.sin_port = htons(PORTNO);
    udp.addr.sin_addr.s_addr = inet_addr(LOCALHOST);

    char msgbuf[] = "HELLO WORLD\n";
    ssize_t snd = sendto(udp.sfd, msgbuf, sizeof(msgbuf), 0,
                         (struct sockaddr *)&udp.addr, sizeof(udp.addr));
    if (snd < 0) {
        PERROR("sendto()");
        goto close_end;
    }

close_end:
    close(udp.sfd);
error_end:

    return 0;
}


udp_server.c

#include "local.h"

int main(void)
{
    udpSocket_t udp;
    char msgbuf[MSGBUF_SIZE] = {0};

    udp.sfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (udp.sfd < 0) {
        PERROR("socket()");
        goto error_end;
    }

    udp.addr.sin_family = PF_INET;
    udp.addr.sin_port = htons(PORTNO);
    udp.addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(udp.sfd,
             (struct sockaddr *)&udp.addr,
             sizeof(udp.addr)) < 0) {

        PERROR("bind()");
        goto close_end;
    }

    ssize_t rcv = recvfrom(udp.sfd, msgbuf, sizeof(msgbuf), 0,
                           (struct sockaddr*)&udp.addr, &udp.addrlen);
    if (rcv < 0) {
        PERROR("recvform()");
        goto close_end;
    }

    fprintf(stdout, "%s", msgbuf);

close_end:
    close(udp.sfd);
error_end:

    return 0;
}


local.h

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

#define PORTNO  (50001)
#define MSGBUF_SIZE (4096)
#define LOCALHOST "127.0.0.1"

#define PERROR(X) \
{\
    char _strerr[64] = {0};\
    int errcode = errno;\
    \
    strerror_r(errcode, _strerr, sizeof(_strerr));\
    printf(X" failed(%d:%s)\n", errcode, _strerr);\
}

typedef struct {
    int                 sfd;
    struct sockaddr_in  addr;
    socklen_t           addrlen;
} udpSocket_t;

実行例

$ gcc udp_client.c -o client
$ gcc udp_server.c -o server
$ ./server 
$ ./client   #別端末から実行
HELLO WORLD
$

Linuxでシグナル捕捉

使い方をすぐに忘れるsigactionでシグナルを捕捉する方法。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t RecvSigno = 0;

void signal_handler(int signo)
{
    RecvSigno = signo;
}

int main(void)
{
    struct sigaction sigact = {
        .sa_handler = signal_handler,
        .sa_flags = 0,
    };
    uint8_t siglist[] = {
        SIGINT,
        SIGTERM,
        SIGQUIT,
        SIGCHLD,
        SIGPIPE,
    };

    sigemptyset(&sigact.sa_mask);

    for(int entry = 0; entry < sizeof(siglist); entry++) {
        if(sigaction(siglist[entry], &sigact, NULL) < 0) {
            int errcode = errno;
            char strerr[64] = {0};
            strerror_r(errcode, strerr, sizeof(strerr));
            printf("sigaction() failed(%s)\n", strerr);
        }
    }

    while (1) {
        if (RecvSigno != 0) {
            printf("Recieved signal(%d)\n", RecvSigno);
            RecvSigno = 0;
        }

        sleep(1);
    }

    return 0;
}

実行結果

$ gcc -o signal -std=gnu99 signal.c
$ ./signal
^CRecieved signal(2)   # Ctrl+C実行
Hangup                 # 別端末からkill -SIGHUP <pid>を実行
$


新規のプログラムはsignal(2)ではなくsigaction(2)を使うことが推奨される。
シグナルの扱いについてはMan page of SIGNAL参照。