2007/05/23(水)UDPをTCPにクローンするプログラム

UDPを受信してTCPにクローンするプログラム(複数クライアント対応)。本当はマルチスレッドで書いてあげるべきなのですが、そこまですると(実現する処理に対して)高級するぎる気がしたのでポーリングにしました。

というのも、TCPの通信エラーとかが出たときに(ブロックしないはずの)ソケットへの書き込み(データの送信)がバッファ一杯になるとストールして(止まって)しまうんですよね。もちろんソケットや書き込み時の関数はノンブロッキングに設定しておいても、です。C ではまだ確認していませんが、Perlで書いたときはそうでした。

TCP/IPはただ通信するだけなら簡単ですが、この手のエラー処理を考えはじめると非常に頭を使います。

プログラム

メイン部のみ。適当に main ルーチンを書けば Windows でも Unix系 でも動きます。

int	max_connections=100;
char	buffer[0x10000];
int	connection_pool[max_connections];

int accept_client(int listen_sock) {
	int sock, len;
	struct sockaddr_in sin;

	// accept
	len = sizeof(sin);
	sock = accept(listen_sock, (struct sockaddr *)&sin, &len);
	if (sock<0) error_return("client accept error");

	// 接続者情報
	printf("[%02d] Connection from %s\n", sock, inet_ntoa(sin.sin_addr));
	// ソケットの設定
	set_non_blocking(sock);
	return sock;
}

//////////////////////////////////////////////////////////////////////////////
// server main
//////////////////////////////////////////////////////////////////////////////
int udp2tcp_server_main(int udp_sock, int tcp_sock) {
	int i;
	char buf[1024];
	// select用の設定
	fd_set fdbits;
	fd_set fdbits_org;
	FD_ZERO(&fdbits_org);
	FD_SET(udp_sock, &fdbits_org);
	FD_SET(tcp_sock, &fdbits_org);

	while(1) {
		// Select
		memcpy(&fdbits, &fdbits_org, sizeof(fdbits_org));
		select(FD_SETSIZE, &fdbits, NULL, NULL, NULL);

		// New connection from TCP
		if ( FD_ISSET(tcp_sock, &fdbits) ) {
			int newsock = accept_client(tcp_sock);
			if (newsock>0) {
				// search for free connection pool point
				for(i=0; i<max_connections; i++)
					if (!connection_pool[i]) break;

				if (i<max_connections) {	// ソケット番号をセーブ
					FD_SET(newsock, &fdbits_org);
					connection_pool[i] = newsock;
					dbg("[%02d] save to connection_pool[%d]\n", newsock, i);
				} else {		// 接続を切る
					printf("Connections max\n");
					close(newsock);
				}
			}
		}

		// Recieved from UDP
		int size = 0;
		if ( FD_ISSET(udp_sock, &fdbits) ) {
			size = recv(udp_sock, buffer, BUF_SIZE, MSG_DONTWAIT);
			if (size<0) error_exit2("UDP recv error(%d)", size);
			dbg("[%02d] Recieved UDP %d bytes\n", udp_sock, size);
		}

		// TCP接続クライアントにデータを送信
		for(i=0; i<max_connections; i++) {
			int sock = connection_pool[i];
			if (!sock) continue;
			// socket からデータ受信
			if ( FD_ISSET(sock, &fdbits) ) {
				int s = recv(sock, buf, 1024, MSG_DONTWAIT);
				if (s<0) {
					FD_CLR(sock, &fdbits_org);
					connection_pool[i] = 0;
					dbg("[%02d] clear to connection_pool[%d]\n", sock, i);
					printf("[%02d] Connection close\n", sock);
					close(sock);
					continue;
				}
			}
			// データ送信
			if (size>0) {
				//int s = write(sock, buf, size);
				int s = send(sock, buf, size, MSG_DONTWAIT);
				dbg("[%02d] write TCP %d bytes\n", sock, s);
			}
		}
	}
}