はじめに
Java でネットワークプログラミングを行う際、ServerSocket や DatagramSocket を使ってポートをバインドするのは基本的な操作です。
しかし、Linux 環境で複数のネットワークインターフェース(NIC)を持つサーバでは、「どのインターフェースにバインドするか」を制御することが重要になります。
例えば以下のようなケースです。
- 同一サーバ上で内外ネットワークを分離して運用したい
- ロードバランサや仮想ネットワーク上で特定の IP にのみ応答したい
- UDP ベースのアプリケーションで、送信インターフェースを明示したい
本記事では、Java で複数インターフェースやポートのバインドを制御する方法を、Linux ネットワークの観点から詳しく解説します。
1. Linux 環境におけるネットワークインターフェースの確認
まず、Linux で利用可能なインターフェース一覧を確認します。
$ ip addr show
出力例:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 10.0.0.10/24 brd 10.0.0.255 scope global eth1
このように、eth0(社内LAN)と eth1(外部用)といった複数NICがある場合、JavaアプリがどちらのIPを利用するかを明示することが可能です。
2. ServerSocketで特定のIPにバインドする
通常のServerSocket(全インターフェースにバインド)
ServerSocket server = new ServerSocket(8080);
上記のようにIPを指定しない場合、0.0.0.0:8080 にバインドされ、全インターフェースで待受けが行われます。
特定インターフェースへのバインド
InetAddress bindAddr = InetAddress.getByName("192.168.1.10");
ServerSocket server = new ServerSocket(8080, 50, bindAddr);
これにより、192.168.1.10(eth0)にのみバインドされます。
つまり、eth1(10.0.0.10)経由のアクセスは拒否されます。
3. DatagramSocket(UDP)の場合
UDP でも同様に、InetAddressを指定してバインドできます。
InetAddress localAddr = InetAddress.getByName("10.0.0.10");
DatagramSocket socket = new DatagramSocket(new InetSocketAddress(localAddr, 9999));
これで eth1(10.0.0.10)上のポート9999で受信します。
送信インターフェースを明示的に指定する(NetworkInterface)
送信時に特定インターフェースを使用したい場合:
NetworkInterface ni = NetworkInterface.getByName("eth1");
socket.setNetworkInterface(ni);
これにより、パケット送信が指定インターフェースから行われます。
4. NIOを使った複数ポート・インターフェース制御
NIO(java.nio.channels)を使うと、非同期・マルチポートでの待受けが容易になります。
try (Selector selector = Selector.open()) {
InetSocketAddress address1 = new InetSocketAddress("192.168.1.10", 8080);
InetSocketAddress address2 = new InetSocketAddress("10.0.0.10", 9090);
ServerSocketChannel server1 = ServerSocketChannel.open();
server1.bind(address1);
server1.configureBlocking(false);
server1.register(selector, SelectionKey.OP_ACCEPT);
ServerSocketChannel server2 = ServerSocketChannel.open();
server2.bind(address2);
server2.configureBlocking(false);
server2.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
System.out.println("接続: " + client.getRemoteAddress());
}
}
selector.selectedKeys().clear();
}
}
このコードでは、複数のインターフェース・ポートを同時に待受け、Selectorでイベント駆動的に処理します。
5. ポート競合・バインドエラー対策
複数プロセスや同一アプリ内でバインド競合が起こると、以下の例外が発生します
java.net.BindException: Address already in use
この場合の対策は:
- 別ポートを使用する
- プロセスを確認して停止する
$ sudo lsof -i :8080 SO_REUSEADDRを設定する(TCP再利用)
ServerSocket socket = new ServerSocket();
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress("192.168.1.10", 8080));
6. IPv6 対応と dual stack の挙動
Java では、デフォルトで IPv4/IPv6 のデュアルスタックが有効です。
IPv6 アドレスでバインドする場合は
InetAddress addr6 = InetAddress.getByName("fe80::1");
ServerSocket server = new ServerSocket(8080, 50, addr6);
必要に応じて java.net.preferIPv4Stack プロパティで制御可能です
-Djava.net.preferIPv4Stack=true
7. まとめ
| 要件 | 使用クラス | 設定例 |
|---|---|---|
| TCPで特定IPにバインド | ServerSocket | new ServerSocket(port, backlog, bindAddr) |
| UDPで特定NICから送受信 | DatagramSocket + setNetworkInterface() | |
| 複数ポート・非同期通信 | ServerSocketChannel + Selector | |
| IPv6対応 | InetAddress (IPv6指定) |
Linuxサーバ上でJavaを運用する際、インターフェースやポートのバインド制御を正しく行うことで、
セキュリティ・性能・柔軟性の高いネットワークアプリケーションを構築できます。

