C で覚えたことまとめメモ vol.4

[vol.1](https://qiita.com/aki_55p/items/9829f7150d4d85715f9f)  
[vol.2](https://qiita.com/aki_55p/items/befee0aa4782961c310e)  
[vol.3](https://qiita.com/aki_55p/items/e65acd6cb4be3cbbd3f0)  
の続きです。

- ネットワークプログラミング(サーバ)  
    * サーバサイドのソケットでのやりとり  
BLAB: Bind, Listen, Accept, Begin  
ヘッダーは`sys/socket.h`  
    * 一個一個解説すると大変なので、以下にサンプルコード貼っときます。  
シグナルのサンプルとかも入ってる。  
合言葉形式でサーバから返答が返ってくるサンプル。  

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

//exitラッパー
void error (char *msg) {
    fprintf (stderr, "%s :%s\n", msg, strerror(errno));
    exit (1);
}

//sigactionラッパー
int catch_signal (int sig, void (*handler)(int)) {
    struct sigaction action;
    action.sa_handler = handler;
    sigemptyset (&action.sa_mask);
    action.sa_flags = 0;
    return sigaction (sig, &action, NULL);
}


//sendラッパー
int say (int socket, char *s) {
    int result = send (socket, s, strlen(s), 0);
    if (result == -1)
        fprintf (stderr, "クライアントとの接続エラー！: %s¥n", strerror(errno));
    return result;
}

//recvラッパー
int read_in (int socket, char *buf, int len) {
    char *s = buf;
    int slen = len;

    int c = recv (socket, s, slen, 0);
    while ((c > 0) && (s[c-1] != '\n')) {
        s += c;
        slen -= c;
        c = recv (socket, s, slen, 0);
    }

    if (c < 0)
        return c;
    else if (c == 0)
        buf[0] = '\n';
    else
        s[c-1] = '\0';

    return len - slen;
}

int listener_d;

//for SIGINT handler
void must_die (int sig) {
    if (listener_d)
        close (listener_d);
    fprintf (stderr, "シャットダウン\n");
    exit (0);
}

int main () {
    //set signal
    if (catch_signal (SIGINT, must_die) == -1)
        error ("割り込みハンドラ設定失敗！\n");

    //create listener socket
    listener_d = socket (PF_INET, SOCK_STREAM, 0);
    if (listener_d == -1)
        error ("ソケット開くの失敗！\n");

    //set up listener socket
    struct sockaddr_in server;
    server.sin_family = PF_INET;
    server.sin_port = (in_port_t)htons(PORT);  //ポートは適当に設定。サーバ側の設定でポート開放も忘れずに。
    server.sin_addr.s_addr = htonl (INADDR_ANY); //INADDR_ANY は、同じホストのすべてのIPアドレスを割り当てるの意。

    //socket reuse set up 
    int reuse = 1;
    if (setsockopt (listener_d, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof (int)) == -1)
        error ("ソケットの再利用設定失敗！\n");
    
    //bind 
    if (bind (listener_d, (struct sockaddr *)&server, sizeof(server)) !=0)
        error ("バインド失敗！\n");

    //listen
    if (listen (listener_d, 10) != 0)
        error ("リッスン失敗！\n");

    //create connect socket
    struct sockaddr_storage client;
    unsigned int client_len = sizeof (client);
    
    puts ("接続待機");
    char buf[255];

    while (1) {
        //accept
        int connect_d = accept (listener_d, (struct sockaddr *)&client, &client_len);
        if (connect_d == -1)
            error ("接続ソケット開くの失敗！\n");

        //fork for concect
        if (!fork()) {
            close (listener_d);

            say (connect_d, "合言葉\n"); 

            read_in (connect_d, buf, sizeof (buf));
            if (strncmp (buf,  "nobara", 6) != 0) { 
                say (connect_d, "きさまら　はんらんぐんだな！\n");
                close (connect_d);
                exit (0);
            }

            say (connect_d, "ダイナミック\n"); 

            read_in (connect_d, buf, sizeof (buf));
            if (strncmp (buf,  "daikuma", 7) != 0) { 
                say (connect_d, "きさまら　はんらんぐんだな！\n");
                close (connect_d);
                exit (0);
            }

            say (connect_d, "通ってよし！\n");
            close (connect_d);
            exit (0);
        }

        close (connect_d);
    }

    close (listener_d);

    return 0;
}
```  

- ネットワークプログラミング(クライアント)  
    * クライアントサイドのソケットでのやりとり  
これも、一個一個解説するとめっちゃ長くなるのでサンプルコード貼っときます。  
`getaddrinfo()`の使い方がポイント。ヘッダーは`netdb.h`。  
wikipedia からページの html を引っ張ってくるプログラム。  
    * Webサーバには、「GETコマンド」、「ホスト名」、「空行」を送る必要あり。  

```c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

//exitラッパー
void error (char *msg) {
    fprintf (stderr, "%s :%s\n", msg, strerror(errno));
    exit (1);
}

//sendラッパー
int say (int socket, char *s) {
    int result = send (socket, s, strlen(s), 0);
    if (result == -1)
        fprintf (stderr, "クライアントとの接続エラー！: %s¥n", strerror(errno));
    return result;
}

//set up socket function
int open_socket (char *host, char *port) {
    //set up socket struct
    struct addrinfo *res;
    struct addrinfo hints;
    memset (&hints, 0 ,sizeof(hints));

    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if (getaddrinfo (host, port, &hints, &res) == -1)
        error ("アドレスがわからん\n");

    //create socket
    int d_sock = socket (res -> ai_family, res -> ai_socktype, res -> ai_protocol);
    if (d_sock == -1)
        error ("ソケット開くの失敗！\n");

    //connect
    int c = connect (d_sock, res -> ai_addr, res -> ai_addrlen);
    freeaddrinfo (res);
    if (c == -1)
        error ("コネクト失敗！\n");

    return d_sock;
}

int main (int argc, char *argv[]) {
    //create socket
    int d_sock = open_socket ("en.wikipedia.org", "80");
    
    //http request
    char buf[255];
    sprintf (buf, "GET /wiki/%s http/1.1\r\n", argv[1]);
    say (d_sock, buf);
    say (d_sock, "HOST: en.wikipedia.org\r\n\r\n");

    //get and show http
    char rec[256];
    int bytesRcvd = recv (d_sock, rec, 255, 0);
    while (bytesRcvd) {
        if (bytesRcvd == -1)
            error ("サーバーから読み取れない！\n");

        rec [bytesRcvd] = '\0';
        printf ("%s", rec);
        bytesRcvd = recv (d_sock, rec, 255, 0);
    }
    close (d_sock);
    return 0;
}
```

- スレッド<font color="DarkRed">（※C11 から標準ライブラリにもスレッド機能が組み込まれているとのこと。コメントでご指摘いただきました。API 体系はだいたい一緒の模様。詳しく知りたい人は検索してみてください。）</font>  
    * POSIXスレッドライブラリ(pthread)を使用する。  
    * ヘッダーは`pthread.h`  
    * `pthread_t`データ構造体を作成  
    * `int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);`  
`attr`はスレッド属性。デフォルトは`NULL`。詳しくは調べるべし。  
`start_routine`はスレッドで実行する関数。`arg`は関数の第一引数。何もなければNULL。  
    * `int pthread_join(pthread_t th, void **thread_return);`でスレッドの終了を待つ。  
`th`の終了した時の返り値が`thread_return`に格納される。  
    * ミューテックス  
共有のデータリソースにアクセスする際、2つ以上のスレッドでの同時アクセスを防ぐ。  
`pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER`でミューテックスを作成。  
ロックしたい部分を、  
`pthread_mutex_lock (&a_lock);`  
`pthread_mutex_unlock (&a_lock);`  
で挟んでロック。  

いじょー！プロセスとかネットワークとかスレッドあたりは正直覚えることとか、テクニックとかが多すぎてすごい表面の基礎的なとこしかできなかった。  
今後はこれを元に精進していきたいと思います。

かしこ