質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

Q&A

1回答

383閲覧

Javaで双方向の通信をするとき片方のみのポートフォワーディングで済ませる方法

EMem

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

0グッド

1クリップ

投稿2025/05/11 03:45

実現したいこと

今の状況を具体的な例を上げて言うと

2台のPCを準備して片方のPC(以後PC1と呼ぶ)にサーバーとしての役割をプログラミングで言うメソッドのように扱おうとしています。
もう片方のPC(以後PC2と呼ぶ)からTCP通信を用いて適当な値をPC1に送信します。
PC1はPC2から受け取った値を用いて特定の処理をして計算結果をPC2に返します。
このPC2から送る値を引数とするとPC1から返ってくる値が戻り値のようでひとつのメソッドの用に感じました。

現在、PC1をサーバーとして扱うのでPC1のあるグローバルIPアドレスのドメインを取得、DDNSの設定、プライベートIPアドレスの固定、そのIPアドレスにポート開放をすることでPC1にPC2からデータを送ることはできました。PC2も同様にPC1と同じ手順を追ってPC1からPC2へデータを送ることもできたのですがここで問題及び今回の質問の主題が発生しました。

発生している問題・分からないこと

この方法ではPC1からPC2に通信するときもPC2が位置するグローバルIPアドレスを把握してそのポート開放をする必要があります。ですが普段他のアプリケーションを使用するとき、例えばWebブラウザでWebページのアクセスするときは内部で相手サーバー側のポート番号を把握していたとしてもこちらは特段ポート開放していませんし(ルーターの設定)、IPアドレスへポートフォワーディングしてもいません。なのに情報が受信できると言うことはこちらが相手のポート番号を知ってたりする場合一時的に送受信できるトンネルがあると予想しました。(TCP通信の性質上、接続完了の情報をサーバーがクライアントに送り返すのもあったので)(最初はこちらが相手サーバー側に一時的にポート開放しその番号を送信しているのかと思いましたがポート開放するにはルーターやONUのパスワードが必要になるので除外しました。)

まとめるとPC2からPC1への通信を始めたときにPC1からPC2へデータを送り返すときPC2がポート開放をせずに受け取る方法を知りたいです。

下のソースコードだと一方方向の通信なので返信をしたいのですがこのとき可能ならばESTABLISHとなったSocketを使ってPC2側(Client.java)は受け取るポートを新たに指定せずに受信したいです。

該当のソースコード

import java.io.DataInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { ServerSocket svSock; public Server(int port) { try { //サーバーのポート番号を指定 svSock = new ServerSocket(port); svSock.setSoTimeout(0); } catch (IOException e) { throw new RuntimeException(e); } } public int getID() { try { //アクセスを待ち受け Socket sock = svSock.accept(); //受信ストリームの取得(DataInputStreamでラップ) DataInputStream in = new DataInputStream(sock.getInputStream()); //int型データを受信 int getID = in.readInt(); //受信データの表示 System.out.println("「" + getID + "」を受信しました。"); //受信ストリームの終了 in.close(); /* //サーバー終了 svSock.close(); */ return getID; } catch (IOException e) { e.printStackTrace(); return 0; } } }
import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; public class Client { public Client(String hostname, int port, int sendID) { try { //送信先のIPアドレス(ドメインなどの名前)とポートを指定 Socket sock = new Socket(hostname, port); //送信ストリームの取得 DataOutputStream out = new DataOutputStream(sock.getOutputStream()); /* //文字列をUTF-8形式のバイト配列に変換して送信 out.write(sendData.getBytes("UTF-8")); */ out.writeInt(sendID); //送信データの表示 System.out.println("「" + sendID + "」を送信しました。"); //送信ストリームを表示 out.close(); //終了 sock.close(); } catch (IOException e) { e.printStackTrace(); } } }

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

参考にしたサイトを並べておきます。
【Java】TCP通信を利用したデータ送受信
JavaでTCP通信によるデータ送受信

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

utm.

2025/05/11 05:07

興味があって調べたことがあるのですが、簡単にまとめると ・クライアントPCがエフェメラルポートから自動割り当てを行う ・サーバーがそのポートに情報を返す という流れのようです。 Httpのプロトコル(以下で説明しますが実際にはTCP)でそう決まっていて、 サーバーの80ポートにクライアントPCがリクエストを投げるときにソケットを作成しその際にバインドされていなければOSがエフェメラルポートを自動決定するため、ユーザーが意図的にポート開放をしなくていいということになっている見たいです。 これはTCPプロトコルで決まっていることですね。 要はインタフェース(このメソッドはこの型を返す)というのが決まっているということです。 Javaは詳しくありませんが、少し調べてみたところ クライアント側のnew Socketがブロッキング処理ということで、通信して同期的に処理されます。 (当然その際にクライアントのポートも割り当てられます) (後述するinput outputは全てブロッキング処理のようです) 通信が完了したら、サーバー側の Socket sock = svSock.accept();が処理されます。 scketのgetInputStreamで送信された値を受け取ります。 getOutputStreamで送信します。 クライアント側でも同様のメソッドでストリームへ書き込み、読み取り、を行えます。 (どちらとも、DataOutputStreamやDataInputStreamでラップする必要があるようです) DataOutputStream.writeでバッファに送る、明示的にフラッシュしなければおそらく勝手に送られるのだろうと思います。 ちなみにTCPだけではなく双方向通信を確立するプロトコルなどもあると思うので調べてみると役に立つかも...?
TakaiY

2025/05/11 07:05 編集

> このPC2から送る値を引数とするとPC1から返ってくる値が戻り値のようでひとつのメソッドの用に感じました。 最初の段落の部分は、ネットワーク上の通信では一般的な考え方で、ポピュラーなのはHTTPをベースとしたWebAPIでしょうか。 調べてみると理解が深まると思います。 (質問を読み違えていたので消しました)
jimbe

2025/05/12 07:51

最初の方はRPCの何かかと思いましたが、結局は(javaに限らない)TCP/IPの解説が欲しいと言うことでしょうか。 それともそれはそれとして、ご提示のコードを元にクライアント←→サーバのデータの送受信が出来れば良いのでしょうか。
TakaiY

2025/05/12 08:26

PC1とPC2やルータの位置関係とか、ネットワークやNAT(NAPT)の設定がわからないので、何が問題なのかわかりにくいです。図にして提示いただけるとわかりやすいのですが。
68user

2025/05/12 14:16

> まとめるとPC2からPC1への通信を始めたときにPC1からPC2へデータを送り返すときPC2がポート開放をせずに受け取る方法を知りたいです。 PC2からPC1への通信を始めたときにPC1からPC2へデータを送り返すときPC2がポート開放をせずに受け取れるのは、ルータ等が内部で管理しているNATテーブルに「PC2のポートxxxからPC1のポートyyyへ通信中」と記憶しており、PC1のポートyyyからルータにデータが届いたら「PC2のポートxxxからPC1のポートyyyへ通信中だから、PC2のポートxxxにデータを渡してあげよう」とルータ内部で判断されているからです。 PC2からPC1へ通信を始めていないのにPC1からPC2へデータを送りたい場合は、PC2がグローバルIPアドレスを持つとか、ルータでポートフォワード設定が必要です。
tamoto

2025/05/13 00:26

> PC2からPC1への通信を始めたときにPC1からPC2へデータを送り返す Java の Socket の仕様はよく知らないですが、Client(PC2) から Server(PC1) に接続を確立した後に Server から Client へのデータ送信がしたいだけで、それって質問のコードに加えて Server 側で OutputStream を、Client 側で InputStream を扱うだけの話ではないのでしょうか?
shiketa

2025/05/13 01:35

tamotoさんのいうとおりだとおもいます。 > この方法ではPC1からPC2に通信するときもPC2が位置するグローバルIPアドレスを把握してそのポート開放をする必要があります。 ここの認識が違っているようにおもいます。 PC1での処理結果を返すために、PC1からPC2に「改めて」接続するわけではない。 # 寡聞にして、そーゆーことしていたのは、ftpのactiveモードくらいしか知らん。 PC1は、接続要求のSocketを使って、PC1での処理結果をPC2に返すのだから。 PC1側。サーバ側。 1. ServerSocketを作る。 * https://docs.oracle.com/javase/jp/8/docs/api/java/net/ServerSocket.html 2. acceptで接続要求を待つ。 3. PC2からの接続要求を受ける。Socketが返ってくる。 4. Socket#getInputStream()から要求をreadする。 * https://docs.oracle.com/javase/jp/8/docs/api/java/net/Socket.html#getInputStream-- 5. 結果を求める。 6. Socket#getOutpuStream()に結果をwriteする。 * https://docs.oracle.com/javase/jp/8/docs/api/java/net/Socket.html#getOutputStream-- 7. 御用が済んだらSocket#close()する。 PC1がプライベートIPだろうがグローバルIPだろうが関係ない。PC1とPC2のあいだにプロキシが入ろうが入るまいが(そのポートを通してくれるなら)関係ない。ということだとおもいますけど。
guest

回答1

0

とりあえずローカルで足し算するサーバ/クライアントです。

java

1package teratail_java.q_02oo9ib67fnq6b; 2 3import java.io.*; 4import java.net.ServerSocket; 5import java.net.Socket; 6import java.util.StringJoiner; 7 8public class AdditionServer { 9 public static final String HOST = "localhost"; 10 public static final int PORT = 12345; 11 12 public static void main(String[] args) throws IOException { 13 ServerSocket ss = new ServerSocket(PORT); 14 try { 15 while(true) { 16 Socket s = ss.accept(); 17 new Thread(new Addition(s)).start(); 18 } 19 } finally { 20 ss.close(); 21 } 22 } 23 24 private static class Addition implements Runnable { 25 private Socket s; 26 Addition(Socket s) { 27 this.s = s; 28 } 29 @Override 30 public void run() { 31 String r = null; 32 StringJoiner sj = new StringJoiner("+"); 33 try { 34 //パラメータ受信 35 DataInputStream dis = new DataInputStream(s.getInputStream()); 36 double a = 0; 37 int len = dis.readInt(); 38 for(int i=0; i<len; i++) { 39 String s = dis.readUTF(); 40 double v = Double.parseDouble(s); 41 sj.add(s); 42 a += v; 43 } 44 r = "" + a; 45 } catch(Exception e) { 46 r = e.getMessage(); 47 } 48 //結果送信 49 try { 50 DataOutputStream dos = new DataOutputStream(s.getOutputStream()); 51 dos.writeUTF(r); 52 System.out.println(sj + "=" + r); 53 } catch(IOException e) { 54 e.printStackTrace(); 55 } finally { 56 try { s.close(); } catch(Exception ignore) {}; 57 } 58 } 59 } 60}

java

1package teratail_java.q_02oo9ib67fnq6b; 2 3import java.io.*; 4import java.net.Socket; 5import java.net.UnknownHostException; 6 7public class AdditionClient { 8 public static void main(String[] args) throws UnknownHostException, IOException { 9 try(Socket s = new Socket(AdditionServer.HOST, AdditionServer.PORT)) { 10 //送信 11 DataOutputStream dos = new DataOutputStream(s.getOutputStream()); 12 dos.writeInt(args.length); 13 for(String v : args) dos.writeUTF(v); 14 15 //受信 16 DataInputStream dis = new DataInputStream(s.getInputStream()); 17 System.out.println(dis.readUTF()); 18 } 19 } 20}

コマンドプロンプトを2つ開き、1つで

>java teratail_java.q_02oo9ib67fnq6b.AdditionServer

とサーバを実行、もう1つで

>java teratail_java.q_02oo9ib67fnq6b.AdditionClient 1 2 3.5

とクライアントをパラメータを付けて実行すると、サーバのほうは

>java teratail_java.q_02oo9ib67fnq6b.AdditionServer 1+2+3.5=6.5

クライアントのほうは

>java teratail_java.q_02oo9ib67fnq6b.AdditionClient 1 2 3.5 6.5

となります。

で、ルータを通すにしても(ちゃんと設定するなら)、サーバのアドレス/ポートをクライアントから見えるようにするだけで済むでしょう。

投稿2025/05/12 09:42

jimbe

総合スコア13320

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問