システムプログラミング 演習6:ネットワークサーバ

2011年10月31日

ネットワークサーバ

本演習では,マルチスレッドのネットワークサーバの受付部分の作成を行う.

マルチスレッドサーバでは,あるスレッドでクライアントからの接続を待ち, 接続されたら,別スレッドを立ち上げてそのクライアントからの処理を行う.

以下はそのmainプログラムである.オプションで -d が指定されない場合, daemon(0, 0)でデーモンとなり,バックグランドで動作し, 標準入出力が利用できなくなるので注意すること.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "logutil.h"

#define DEFAULT_SERVER_PORT	10000
#ifdef SOMAXCONN
#define LISTEN_BACKLOG  SOMAXCONN
#else
#define LISTEN_BACKLOG  5
#endif

char *program_name = "sp7-server";

int
open_accepting_socket(int port)
{
	struct sockaddr_in self_addr;
	socklen_t self_addr_size;
	int sock, sockopt;

	memset(&self_addr, 0, sizeof(self_addr));
	self_addr.sin_family = AF_INET;
	self_addr.sin_addr.s_addr = INADDR_ANY;
	self_addr.sin_port = htons(port);
	self_addr_size = sizeof(self_addr);
	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock < 0)
		logutil_fatal("accepting socket: %d", errno);
	sockopt = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
	    &sockopt, sizeof(sockopt)) == -1)
		logutil_warning("SO_REUSEADDR: %d", errno);
	if (bind(sock, (struct sockaddr *)&self_addr, self_addr_size) < 0)
		logutil_fatal("bind accepting socket: %d", errno);
	if (listen(sock, LISTEN_BACKLOG) < 0)
		logutil_fatal("listen: %d", errno);
	return (sock);
}

void
usage(void)
{
	fprintf(stderr, "Usage: %s [option]\n", program_name);
	fprintf(stderr, "option:\n");
	fprintf(stderr, "\t-d\t\t\t\t... debug mode\n");
	fprintf(stderr, "\t-p <port>\n");
	exit(1);
}

int
main(int argc, char **argv)
{
	char *port_number = NULL;
	int ch, sock, server_port = DEFAULT_SERVER_PORT;
	int debug_mode = 0;

	while ((ch = getopt(argc, argv, "dp:")) != -1) {
		switch (ch) {
		case 'd':
			debug_mode = 1;
			break;
		case 'p':
			port_number = optarg;
			break;
		case '?':
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	if (port_number != NULL)
		server_port = strtol(port_number, NULL, 0);

	/* server_portでlistenし,socket descriptorをsockに代入 */
	sock = open_accepting_socket(server_port);

	if (!debug_mode) {
		logutil_syslog_open(program_name, LOG_PID, LOG_LOCAL0);
		daemon(0, 0);
	}

	/*
	 * 無限ループでsockをacceptし,acceptしたらそのクライアント用
	 * のスレッドを作成しプロトコル処理を続ける.
	 */
	main_loop(sock);

	/*NOTREACHED*/
	return (0);
}

[演習6-1]
上記のプログラムにおいて,main_loop() を実装し,動作の確認を行いなさい.main_loop() が生成するクライアント用のスレッドでは,EOFを待ち,EOFとなったら disconnected と表示して終了すること. 表示は debug_mode の場合はエラー出力に,debug_mode ではない場合は syslog に書き出すため,logutil.hlogutil.cを利用している. なお,syslog への書き出しは openlog および syslog で行う. 動作の確認は telnet で行うとよい.

シグナルスレッド

非同期割込のSIGINTとSIGTERMの処理を行う.処理を行うにあたり, まず main() で SIGINT と SIGTERM をブロックし, シグナルを処理するためのスレッドを生成する.

シグナルを処理するスレッドでは,対象となるシグナルを待ち, シグナルが送信されたら適切な処理を行う.

[演習6-2]
上記のプログラムにおいて,SIGINTとSIGTERMのシグナル処理を行うため のスレッドを生成し,シグナルが送信されたら bye と出力するようにすること. 出力先は EOF を受け取った場合と同様とすること. サーバを Ctrl-C あるいは kill -TERM で落とした場合に bye が表示されるか確認しなさい.

注意点:daemon()は内部的にfork(2)を呼び出しているため, それ以前に生成したスレッドは消滅してしまう. そのため,スレッド生成はdaemon()呼出し以後に行うこと.

提出締切は 11/6(日) 20:00とする.
レポートには,プログラム,実行結果,考察,授業の感想を必ず含めること.
課題は筑波大学 e-Lerning システム にアップロードすること.


Osamu Tatebe