socketプログラミングとか

今まで避けていたんだけど、そろそろC言語でsocketを使う必要が出てきたので作ってみます。Webで調べてもなかなか最小構成のプログラムがなくて、初心者には敷居が高いような・・・?ということでHTTPクライアントを作ってみました。多分これが最小かと。

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

#define BUF_LEN 256

int main(void){
     int s, size;
     struct sockaddr_in server;
     char buf[BUF_LEN+1];
     
     server.sin_family       = AF_INET;
     server.sin_port         = htons(80);
     server.sin_addr.s_addr  = htonl((127<<24) + 1);

     s = socket(AF_INET, SOCK_STREAM, 0);
     connect(s, (struct sockaddr *)&server, sizeof(server));

     sprintf(buf, "GET / HTTP/1.0\r\n\r\n");
     send(s, buf, strlen(buf), 0);
     
     while (1){
          if((size = recv(s, buf, BUF_LEN, 0)) > 0){
               buf[size] = '\0';
               printf("%s", buf);
          }
          else break;
     }
     close(s);
     return 0;
}

まずはsocketから

socketというのはUnixから見ればファイルディスクリプターの一種。
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/socket.2.html

#include <sys/types.h> 
#include <sys/socket.h> 
int socket(int domain, int type, int protocol);

内部的にはsocreateが実行されるらしく、上の3つのパラメーターによってプロトコルが決定されるらしい。

connect

socketを作ったらconnectでサーバーに接続。サーバー情報はsockaddr_inという構造体で定義するようですが、汎用性のためconnectに指定する引数はスーパークラスのsockaddr構造体としてキャストする必要があるみたいです。

struct sockaddr {
        sa_family_t     sa_family;      /* address family, AF_xxx       */
        char            sa_data[14];    /* 14 bytes of protocol address */
};
#define __SOCK_SIZE__   16              /* sizeof(struct sockaddr)      */
struct sockaddr_in {
  sa_family_t           sin_family;     /* Address family               */
  unsigned short int    sin_port;       /* Port number                  */
  struct in_addr        sin_addr;       /* Internet address             */

  /* Pad to size of `struct sockaddr'. */
  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
                        sizeof(unsigned short int) - sizeof(struct in_addr)];
};

結構強引なことをしているような。C++のクラスの実装とかも、実体はこんな感じなのかしら。

struct in_addr {
        in_addr_t       s_addr;
};
typedef unsigned int __u32;

なんにしても、connectするには

  • protocol family
  • port number
  • IP address

の3点セットが必要になります。

htoXXの関数は以下のマニュアル参照。多分"host byteorder to network byteorder short/long"の略だと思う。Pentiumとかはリトルエンディアンなので、server.sin_addr.s_addrなんかをダンプしてみると"0x100007F"になってるはず*1

http://www.linux.or.jp/JM/html/LDP_man-pages/man3/byteorder.3.html

read/write

connectしたら、あとはsocketに対してread/writeすればよい。socketはファイルディスクリプターなので、ファイルに対するようにread/writeすればよい。

*1:127.0.0.1を16進IPアドレスにすると0x7F000001、リトルエンディアンだとメモリー上は0x0100007F。なのでネットワークバイトオーダー(ビッグエンディアン)にするにはメモリー上で0x7F000001にしておく必要があって、そうするとOSからみると0x0100007Fになってる