FreeBSD kernel SOCKET I/F 探検

第2回 足掛かりとなるプログラムの提示

2013.08.19

必要とする知識など:
  • UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。

前回は、これから扱う内容に関して説明を行ないました。今回は、kernel 内に踏み込む足掛かりとして、簡単なサーバとクライアントプログラムを提示します。

本連載の主題で、「FreeBSD kernel SOCKET I/F 探検」と宣言しているので、さっさと「kernel のソースコードを覗いて行こう」と言いたいところですが、まずどこから覗いていけば良いのか?いきなり kernel のソースコードを覗いてみても、どこから手を付ければ良いのか良く分からないと思います。

そこでまず、ユーザ空間上 [1] で動作する SOCKET I/F を使用したプログラムを掲載します。このプログラムが使用しているシステムコールを足掛かりとして、kernel 内を覗いていこうと思います。

それでは、サーバとクライアントのプログラム (server.c, client.c)、および Makefile を以下に提示します。

server.c

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

/**
 * Main
 */
int
main(int argc, char *argv[])
{
       int port;
       int sd;
       int result;
       struct sockaddr_in sa;
       struct sockaddr_in from;
       socklen_t addrlen;
       ssize_t rbytes;
       ssize_t sbytes;
       char buf[1024];

       /* Is not enough argument? */
       if (argc != 2) {
               fprintf(stderr, "Usage: server PortNo\n");
               exit(1);
       }
       port = atoi(argv[1]);

       /* Create a socket */
       sd = socket(PF_INET, SOCK_DGRAM, 0);
       if (sd < 0) {
               perror("socket");
               exit(1);
       }

       /* Bind the port number */
       bzero(&sa, sizeof (sa));
#ifdef __FreeBSD__
       sa.sin_len = sizeof (sa);
#endif /* __FreeBSD__ */
       sa.sin_family = AF_INET;
       sa.sin_port = htons(port);
       result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
       if (result < 0) {
               perror("bind");
               exit(1);
       }

       while (1) {
               /* Receive data */
               addrlen = sizeof (from);
               rbytes = recvfrom(sd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &addrlen);
               if (rbytes < 0) {
                       perror("recvfrom");
                       continue;
               }
               printf("Received %dbytes\n", rbytes);

               /* Send data */
               sbytes = sendto(sd, buf, rbytes, 0, (struct sockaddr *)&from, addrlen);
               if (sbytes < 0) {
                       perror("sendto");
                       continue;
               }
               if (sbytes != rbytes) {
                       fprintf(stderr, "Length of the transmitted data does not matched: Tried to send %dbytes, Transmitted %dbytes.\n", rbytes, sbytes);
               }
       }

       /* Success */
       return 0;
}

client.c

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

#define NLOOPS 10
#define MAX_VAL 10
#define DATA_LENGTH 16

/**
 * Main
 */
int
main(int argc, char *argv[])
{
       char *addr;
       int port;
       int sd;
       int result;
       struct addrinfo hints;
       struct addrinfo *res;
       struct sockaddr_in from;
       struct sockaddr_in to;
       socklen_t addrlen;
       ssize_t rbytes;
       ssize_t sbytes;
       int i;

       /* Is not enough argument? */
       if (argc != 3) {
               fprintf(stderr, "Usage: client host PortNo\n");
               exit(1);
       }
       addr = argv[1];
       port = atoi(argv[2]);

       /* Get address information */
       bzero(&hints, sizeof (hints));
       hints.ai_family = AF_INET;
       hints.ai_socktype = SOCK_DGRAM;
       result = getaddrinfo(addr, 0, &hints, &res);
       if (result != 0) {
               fprintf(stderr, "getaddrinfo: %s: %s\n", addr, gai_strerror(result));
               if (result == EAI_SYSTEM) {
                       fprintf(stderr, "getaddrinfo: %s: %s\n", addr, strerror(errno));
               }
               exit(1);
       }
       bzero(&to, sizeof (to));
       bcopy(res->ai_addr, &to, res->ai_addrlen);
       to.sin_port = htons(port);
       addrlen = res->ai_addrlen;
       freeaddrinfo(res);

       /* Create a socket */
       sd = socket(PF_INET, SOCK_DGRAM, 0);
       if (sd < 0) {
               perror("socket");
               exit(1);
       }

       /* Bind a port number */
       bzero(&from, sizeof (from));
#ifdef __FreeBSD__
       from.sin_len = sizeof (from);
#endif /* __FreeBSD__ */
       from.sin_family = AF_INET;
       from.sin_port = 0;
       result = bind(sd, (struct sockaddr *)&from, sizeof (from));
       if (result < 0) {
               perror("bind");
               exit(1);
       }

       /* Bind the destination address and port number */
       result = connect(sd, (struct sockaddr *)&to, addrlen);
       if (result < 0) {
               perror("connect");
               exit(1);
       }

       for (i = 0; i < NLOOPS; i++) {
               int j;
               char sbuf[DATA_LENGTH];
               char rbuf[DATA_LENGTH+1];

               /* Inisialize send buffer */
               for (j = 0; j < DATA_LENGTH; j++) {
                       sbuf[j] = '0' + ((i + j) % MAX_VAL);
               }

               /* Send data */
               sbytes = send(sd, sbuf,  sizeof (sbuf), 0);
               if (sbytes < 0) {
                       perror("send");
                       break;
               }
               if (sbytes != sizeof (sbuf)) {
                       fprintf(stderr, "Length of the transmitted data does not matched: Tried to send %dbytes, transmitted %dbytes.\n", sizeof (sbuf), sbytes);
               }

               /* Receive data */
               bzero(&rbuf, sizeof (rbuf));
               rbytes = recv(sd, rbuf, sbytes, 0);
               if (rbytes < 0) {
                       perror("recv");
                       break;
               }
               if (sbytes != rbytes) {
                       fprintf(stderr, "Length of the received data does not matched: Transmitted %dbytes, received %dbytes\n", sbytes, rbytes);
               }

               /* Print data */
               printf("Data: Transmitted: \"%s\", Received: \"%s\"\n", sbuf, rbuf);
       }

       /* Success */
       return 0;
}

Makefile

CC     = cc
CFLAGS = -g
C_OBJS = client.o
S_OBJS = server.o
SRCS   = client.c server.c
OBJS   = $(C_OBJS) $(S_OBJS)
TARGETS        = client server

.PHONY: all clean

all: $(TARGETS)

client: $(C_OBJS)
        $(CC) -o $@ $(C_OBJS)

server: $(S_OBJS)
        $(CC) -o $@ $(S_OBJS)

.c.o:
        $(CC) $(CFLAGS) -c $<

clean:
       rm -f $(TARGETS) $(OBJS)

各プログラムの説明を行ないます。

サーバは、以下のように起動します。第一引数で指定した数値をポート番号とした UDP 用のソケットを用意しクライアントから送られてきたデータをそのまま返信します。

$ ./server ポート番号

クライアントは、以下のように起動します。第一引数は上記のサーバを起動したマシンのホスト名、第二引数は上記サーバのポート番号を指定します。第一引数は、ホスト名ではなく IP アドレスを指定することも出来ます。

$ ./client ホスト名 ポート番号

Makefile は、make コマンドを使用して server.c と client.c から server と client を作成します。

以下に実際の実行例などを掲載します。

まずは、server を起動します。

$ ./server 10000

server を起動したマシン上で netstat コマンドを実行すると、以下のように「Local Address」に「 *.10000」が表示されます。これにより、ポート番号 10000番が受信可能になっていることが分かります。

$ netstat -a -p udp
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address          Foreign Address        (state)
udp4       0      0 *.10000                *.*
udp4       0      0 *.syslog               *.*
udp6       0      0 *.syslog               *.*

次に、client を起動します。今回は server と同一マシン上で起動するので、ホスト名は localhost を指定します。

$ ./client localhost 10000
Data: Transmitted: "0123456789012345'", Received: "0123456789012345"
Data: Transmitted: "1234567890123456'", Received: "1234567890123456"
Data: Transmitted: "2345678901234567'", Received: "2345678901234567"
Data: Transmitted: "3456789012345678'", Received: "3456789012345678"
Data: Transmitted: "4567890123456789'", Received: "4567890123456789"
Data: Transmitted: "5678901234567890'", Received: "5678901234567890"
Data: Transmitted: "6789012345678901'", Received: "6789012345678901"
Data: Transmitted: "7890123456789012'", Received: "7890123456789012"
Data: Transmitted: "8901234567890123'", Received: "8901234567890123"
Data: Transmitted: "9012345678901234'", Received: "9012345678901234"
$

client は、一回の送受信完了時毎に上記のように送受信したデータを表示します。一方 server は、一回の受信毎に受信したバイト数を表示します。

$ ./server 10000
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes
Received 16bytes

なお、client は以下のようにホスト名の変わりにIPアドレスも指定できます。

$ ./client 127.0.0.1 10000
Data: Transmitted: "0123456789012345'", Received: "0123456789012345"
Data: Transmitted: "1234567890123456'", Received: "1234567890123456"
Data: Transmitted: "2345678901234567'", Received: "2345678901234567"
Data: Transmitted: "3456789012345678'", Received: "3456789012345678"
Data: Transmitted: "4567890123456789'", Received: "4567890123456789"
Data: Transmitted: "5678901234567890'", Received: "5678901234567890"
Data: Transmitted: "6789012345678901'", Received: "6789012345678901"
Data: Transmitted: "7890123456789012'", Received: "7890123456789012"
Data: Transmitted: "8901234567890123'", Received: "8901234567890123"
Data: Transmitted: "9012345678901234'", Received: "9012345678901234"
$

一通りプログラムの解説をおこなったところで、いよいよ kernel のソースコードを覗きたいところですが、今回はここで終了です。

今回は、kernel 内に踏み込む足掛かりとなるプログラムの提示と解説を行ないました。次回は、いよいよ kernel のソースコードを覗いていきます。

[1]ユーザランド (user land) とも言います。通常利用者が使用しているプログラムが動作する 層・範囲 です。これに対して、カーネル空間があります。

著者プロフィール

asou

好きな OS は、FreeBSD です。が、最近購入した Mac mini がなかなか快適なので、Mac でいいかなと思うようになってきました。

記事一覧Index