C言語でUDPサーバーを立てる

# C言語でサーバーを立てること

c言語でサーバーを立てることは難しい。
本記事は新人や、サーバーを書いたことのない人でも、とりあえず動かせて、ちょっと質問されてもある程度答えられるようになることを目的にしたものです。
あなたがサーバー構築をしなければいけなくて、今仕事中であるならば、下記のコードをコピペして、業務仕様に合わせて書き換えていけば時間短縮になると思います。  
今回はUDPの中でも非常にシンプルな作りになります。 
実際の要件ではこれでは済まない場合があると思います。 
しかしまったく手も足も出ない状態よりは、一応つながっている状態の方が先に進みやすいので 
ここから色々いじって要件に合ったコードにしてください。  
この記事ではUDPとは何か、といった説明はありません。インターネットに腐るほど解説がありますのでそちらでお願いします。 
書籍なら`コンピュータネットワーク5版`とか良いんですけど、値段と厚さがアレなんで…

# 環境

一応試した環境をのせるけど、比較的最近のlinuxなら動くと思う。

```
Ubuntu 18.04.2 LTS
gcc 7.4.0
```

# 簡単なUDPサーバー

UDPは実装から見るとシンプルなTCPと考えることができる。 


簡単なサーバーとして 

- データは`localhost` `9002`から受け取る
- 受け取ったデータを表示する

とりあえず、コピペすれば動くコードはこんな感じ。 

```c
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

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

int main ()
{
  //1. 受信するアドレスを構築する
  struct addrinfo hints;
  memset (&hints, 0, sizeof (hints));
  hints.ai_socktype = SOCK_DGRAM;

  struct addrinfo *bind_address;
  getaddrinfo ("localhost", "9002", &hints, &bind_address);


  //2. ソケットを作成する
  int  socket_listen;
  socket_listen = socket (bind_address->ai_family,
              bind_address->ai_socktype,
              bind_address->ai_protocol);
  if (socket_listen < 0)
  {
      fprintf (stderr, "socket() failed. (%d)\n", errno);
      return 1;
  }


  //3. ローカルアドレスをソケットにバインドする
  if (bind (socket_listen, bind_address->ai_addr, bind_address->ai_addrlen))
  {
      fprintf (stderr, "bind() failed. (%d)\n", errno);
      return 1;
  }
  freeaddrinfo (bind_address);


  //4. クライアントの接続を待つ
  while (1)
  {
      struct sockaddr_storage client_address;
      socklen_t client_len = sizeof (client_address);

      char read[4096];
      memset(read, 0, sizeof(read));
      int bytes_received = recvfrom (socket_listen, read, 4096, 0,
                     (struct sockaddr *) &client_address,
                     &client_len);
      if (bytes_received < 1)
      {
        fprintf (stderr, "connection closed. (%d)\n", errno);
        return 1;
      }
      printf("receiving: %s", read);
  }

  //5. ソケットを閉じる
  close(socket_listen);
  printf ("Finished.\n");

  return 0;
}
```

# ちょっと解説的なもの

## 1.受信するアドレスを構築する
接続用の構造体を構築する方法はいくつかる。 

- 手作業でメンバーを一つ一つ設定する方法 
- `gethostbyname関数`を使う方法
- `getaddrinfo関数`を使う方法

今回は3つ目の方法で構築した。 

`getaddrinfo関数`を使うには３つの作業がいる。

1. 受信するアドレスを決める
2. 受信するポート番号を決める
3. ヒントになる`addrinfo構造体`を定義する

3の`addrinfo構造体`は普通、ip4かip6、TCPかUDPかを設定する。
ipは0であればip4になる。
`SOCK_DGRAMは`データグラムのことで、設定するとUDPになる。  
今回は使ってないが、`getaddrinfo関数`の戻り値はerrnoなので、ちゃんとやるなら受け取った方が良い。 

## 2.ソケットを作成する
これは誰がやってもこうなる完全手癖で書くやつ。 
ソケットとは（プログラム上の）通信の端子のことで、OSはソケットを通じて受送信する。
`socket関数`はソケットディスクリプターを作成する。
ソケットディスクリプターはファイルディスクリプターの一種である。  
ファイルディスクリプタとはファイルをOSが扱えるようにするための一意の番号である。 
linuxは様々なものをファイルとして扱う。
テキストファイル、デバイス（USBメモリとか）など。
ネットワークに対してもファイルと同じように読み書きができるようにするのが 
ソケットディスクリプターの役割である。  
`socket関数`の戻り値がソケットディスクリプターである。

## 3.ローカルアドレスをソケットにバインドする
これも手癖。 
`bind関数`で受信するアドレスをソケットに関連付ける。
これで受信の準備が整う。

## 4.クライアントの接続を待つ
もっとも重要な処理。 
本チャンではここで色々やるんだろうね。
普通サーバーは落とさないので無限ループする。 
受信するには`recvfrom関数`を使う。 
`recvfrom関数`は送信元の情報を受け取ります。
`sockaddr_storage構造体`はip4でもip6でも表現できる構造体。

## 5.ソケットを閉じる
ソケットもメモリーやスレッドと同じようにリークを起こすので、開放する必要がある。
`close関数`はファイルを閉じる時に使う。 
ソケットもファイルとして扱えるので、ファイルと同じ`close関数`で開放することができる。

# 一応クライアントも
いらない気もするけどね。 
UDPなのにconnect関数読んでます。 
実はUDPでもconnect関数使えます。
サーバーでもconnect関数を使った書き方ができます。
UDPでconnect関数を使った場合、TCPで使った時と違う動きをします。 
ここの説明は……うーん。


```c
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

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

int main(void)
{
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_DGRAM;
    struct addrinfo *peer_addr;

    if(getaddrinfo("localhost","9002",&hints, &peer_addr))
    {
        fprintf(stderr, "getaddrinfo() failed. (%d)\n", GETSOCKETERRNO());
        return 1;
    }

    char addrbuf[100];
    char servbuf[100];
    getnameinfo(peer_addr->ai_addr, peer_addr->ai_addrlen, addrbuf, sizeof(addrbuf), servbuf, sizeof(servbuf), NI_NUMERICHOST);
    printf("%s %s\n", addrbuf, servbuf);

    puts("Creating socket");
    int socket_peer;
    socket_peer = socket(peer_addr->ai_family, peer_addr->ai_socktype, peer_addr->ai_protocol);
    if(!ISVALIDSOCKET(socket_peer))
    {
        fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
        return 1;
    }

    if(connect(socket_peer,peer_addr->ai_addr, peer_addr->ai_addrlen))
    {
        fprintf(stderr,"connect() failed. (%d).\n", GETSOCKETERRNO());
        return 1;
    }

    freeaddrinfo(peer_addr);

    puts("Connected");

    while(1)
    {

        {
            char read[4096];
            if(!fgets(read, 4096, stdin)) break;

            int bytes = send(socket_peer, read, strlen(read), 0);
            printf("send %d bytes\n", bytes);
        }

    }
    puts("Closing socket");
    CLOSESOCKET(socket_peer);
    return 0;
}
```