Javaのクライアントでは、Socketクラスでサーバのアドレスとポート番号を指定してインスタンスを作ります。 このSocketインスタンスからInput/Output Streamが得られます。 これに対して、read()とかwrite()とかすれば通信出来ます。 例えばデータを読み書きしてくれるホストがあれば、クライアントは以下のようにアクセスすることができます。
Socket socket = new Socket("192.168.0.10",5550); //引数1は相手(ホスト)のアドレス、次はポート番号 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); //中略 oStream.write(data, 0, 10); //byte配列のdataの最初から10バイト送信 iStream.read(data, 0, 10); //byte配列のdataに10バイト読み込み
簡単ですね。一方、サーバの場合は、ServerScocketクラスをインスタンス化して、このインスタンスに.accept()メソッドを送ると、クライアントからの接続を待つことになります。
ServerSocket serverSocket = new ServerSocket(5550); //5550はポート番号 Socket socket = serverSocket.accept(); //クライアントからの接続を待ちます
accept()メソッドの戻り値がSocketになっているので、 あとはクライアントと同様にStreamを作ってread(), write()するだけです。
OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); //中略 oStream.write(data, 0, 10); //byte配列のdataの最初から10バイト送信 iStream.read(data, 0, 10); //byte配列のdataに10バイト読み込み
では実際の例をみてみましょう。これはクライアントから1バイトのデータを受け取って、その値を2倍にして返すサーバです。
import java.net.*; import java.io.*; public class ByteDoubler { public static void main(String[] args){ int port_no = 5550; //ポート番号とりあえず5550にする byte data[] = new byte[8]; int sleep_time = 10; //待ち時間 try{ //サーバソケットを作る ServerSocket serverSocket = new ServerSocket(port_no); //クライアントからの接続を待つ Socket socket = serverSocket.accept(); //出力・出力ストリームを取得 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); //クライアントからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); iStream.read(data,0,1);//1バイト読む System.out.println("received data = " + data[0]); data[0]=(byte)(data[0] * 2); //2倍する oStream.write(data,0,1); oStream.flush(); socket.close(); //ソケットを閉じる } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); } }//end of main }//end of Class
このサーバに12という1バイトのデータを送り結果を受け取るクライアントは以下のように書けます。
import java.net.*; import java.io.*; public class ByteSender { public static void main(String[] args){ int port_no = 5550; //ポート番号とりあえず5550にする String hostName = "localhost"; //サーバが同じマシンで動いている場合。実際には"192.168.0.xx"など byte data[] = new byte[8]; int sleep_time = 10; //待ち時間 try{ Socket socket = new Socket(hostName, port_no);// ソケットを生成 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); data[0]=12; System.out.println("sending a number" + data[0]); oStream.write(data,0,1); //sending data[0] oStream.flush(); //サーバからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); System.out.println(iStream.read()); socket.close(); //ソケットを閉じる } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); } }//end of main }//end of Class
結果は次のようになります。
$ java ByteSender sending a number12 24
もうすこしサーバらしく、ずっと動いているような例を考えてみます。 数値をもらうとひたすら2倍返しするサーバです。 ただし0をもらうとサーバが終了するようにしてみました。 サーバプログラムは次のようになります。
import java.net.*; import java.io.*; public class ByteDoubler { public static void main(String[] args){ int port_no = 5550; //ポート番号とりあえず5550にする byte data[] = new byte[8]; int sleep_time = 10; //待ち時間 try{ //サーバソケットを作る ServerSocket serverSocket = new ServerSocket(port_no); //クライアントからの接続を待つ Socket socket = serverSocket.accept(); //出力・出力ストリームを取得 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); data[0]=99;//クライアントのデータが0になるまで繰り返す while(data[0]!=0) { //クライアントからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); iStream.read(data,0,1);//1バイト読む System.out.println("received data = " + data[0]); data[0]=(byte)(data[0] * 2);//2倍する oStream.write(data,0,1); oStream.flush(); } socket.close(); //ソケットを閉じる } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); } }//end of main }//end of Class
このサバーに接続して、ユーザからキー入力を受け付けて、その数値をサーバに送り、 結果を受け取って表示するクライアントを書いてみました。 0を送るとサーバは終了するので、クライアントもそのとき終了するようにしました。
import java.net.*; import java.io.*; public class ByteSender { public static void main(String[] args){ int port_no = 5550; //ポート番号とりあえず5550にする String hostName = "localhost"; //アドレスは自分自身 byte data[] = new byte[8]; int sleep_time = 10; //待ち時間 //キーボード入力用 BufferedReader keyinput = new BufferedReader(new InputStreamReader(System.in)); try{ Socket socket = new Socket(hostName, port_no);// ソケットを生成 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); data[0]=99; while(data[0]!=0){ System.out.println("input a number (<127). Type 0 to end."); data[0]=(byte)Integer.parseInt(keyinput.readLine()); System.out.println("sending a number" + data[0]); oStream.write(data,0,1); //sending data[0] oStream.flush(); if(data[0]!=0) { //サーバからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); System.out.println(iStream.read()); }//end if }//end while socket.close(); //ソケットを閉じる } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); } }//end of main }//end of Class
これを稼働して、10,20,30,0という数字を入力すると以下のようになります。
$ java ByteSender input a number (<127). Type 0 to end. 10 sending a number10 20 input a number (<127). Type 0 to end. 20 sending a number20 40 input a number (<127). Type 0 to end. 30 sending a number30 60 input a number (<127). Type 0 to end. 0 sending a number0 $
このサーバは、0を受け取ると終了します。 実際のサーバでは、クライアントへのサービスが終了したら、次のクライアントのアクセスを待つのが普通です。 次のwebサーバの例ではそのようにしてみましょう。
また、このサーバは、一つのクライアントが接続すると、ほかのクライアントが接続できなくなります。これを避けるためには、クライアントが接続すると別のスレッドを作って、マルチスレッドで対応します。 次のwebサーバの例では、クライアントへの対応が短時間で終了するので、複数接続は考えていません。
上記のサーバはwebサーバにもなります。 webサーバにしておくとブラウザやcurlコマンドなどで簡単にアクセスできます。 いろいろな状況やセンサの結果などを簡単にクライアントに返すときに利用できると思います。 クライアントからはGETというリクエストが送られてきて、そのほかいろいろな条件が指定されるのですが、 いっさい無視して、勝手にテキストを返す例です。 クライアントからのリクエストは空行で終了するので、それを検出すると返答します。
import java.net.*; import java.io.*; public class SimpleWebServer { public static void main(String[] args){ int port_no = 8080; //待ち受けポート番号8080にする int sleep_time = 100; //待ち時間 try{ //サーバソケットを作る ServerSocket serverSocket = new ServerSocket(port_no); for(;;) { //クライアントからの接続を待つ Socket socket = serverSocket.accept(); //出力・出力ストリームを取得 OutputStream oStream = socket.getOutputStream(); InputStream iStream = socket.getInputStream(); //クライアントからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); Thread.sleep(sleep_time); //クライアントから空行(\r\n\r\n)が来るまで待つ //(タイムアウトしてもやっぱり送り返すことにしている) //デバッグ用にクライアントからのデータを表示するバージョン /* byte[] data = new byte[1024]; for (int i=0; iStream.available() != 0; i++){ data[i] = (byte)iStream.read(); if(data[i] != '\r') continue; else i++; data[i] = (byte)iStream.read(); if(data[i] != '\n') continue; else i++; data[i] = (byte)iStream.read(); if(data[i] != '\r') continue; else i++; data[i] = (byte)iStream.read(); if(data[i] != '\n') continue; else break; } System.out.println (new String(data, "US-ASCII")); */ while (iStream.available() != 0) { if(iStream.read()!='\r') continue; if(iStream.read()!='\n') continue; if(iStream.read()!='\r') continue; if(iStream.read()!='\n') continue; else break; } String reply = "HTTP/1.0 200 OK\r\ntext/html\r\n\r\n"; reply += "hello this is a test \r\n"; //返送したいメッセージ、HTMLなど oStream.write(reply.getBytes(),0,reply.length()); oStream.flush(); socket.close(); //ソケットを閉じる } } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); }//end of try }//end of main }//end of Class
ここではOutput/InputStreamを使ったのでバイト配列を送れるだけです。 Javaのファイル操作などでご存知のように、これからもっと高機能なクラスを利用出来ます。 たとえば
//OutputStreamからOutputStreamWriterを作り, さらにBufferedWriterを得る BufferedWriter bWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
とすればBufferつきのstreamを作れますし、
//OutputStreamからPrintWriterを作る PrintWriter osStr = new PrintWriter(socket.getOutputStream(), true);
とすればprint()メソッドが使えて変数値を簡単に送るstreamも作れます。
入力についても、
//InputStreamからInputStreamReaderを作ってBufferedReaderを作る InputStream iStream = socket.getInputStream(); BufferedReader bReader = new BufferedReader(new InputStreamReader(iStream));
とすればread()やreadLine()が使えます。ただreadLineは、サーバが改行を送るまでブロックされてしまうので注意です。
さらにはせっかくJava使っているので、URLというクラスを使えば、
//URLからInputStream, InputStreamReader, BufferedReaderを作る URL targetURL = new URL("http://localhost:8080/"); InputStream istream = targetURL.openStream(); InputStreamReader isreader = new InputStreamReader(istream, "JISAutoDetect"); BufferedReader breader = new BufferedReader( isreader );
としてreadLineが使えるようになります。こうすると、上記のwebサーバにアクセスするプログラムは以下になります。
webサーバプログラムが返すテキスト行を受け取ります。
public class URLTest { public static void main (String argv[]) { try { URL targetURL = new URL("http://localhost:8080/"); InputStream istream = targetURL.openStream(); InputStreamReader isreader = new InputStreamReader(istream, "JISAutoDetect"); BufferedReader breader = new BufferedReader( isreader ); String line; while((line=breader.readLine()) != null) System.out.println(line); } catch (IOException e) { System.out.println("error..."); } } }
上のURLクラス編で解決なのですが、ソケットレベルから 手抜きなwebクライアントを書いてみました。 上記の手抜きなWebサーバが動いていれば、そこからメッセージを受け取れます。またcurl風に、
java WebClient www.ocha.ac.jp:80
と書けば、そこからもダウンロードします。サーバの応答を冒頭に含んでいるので、本物のcurlとは結果が違います。またファイル名の指定やバイナリーのデータにも対応していませんのでjpegファイルなどはダウンロードできません。ちなみに、curl localhost:8080/filename としたときのfilenameは、GETコマンドあとに現れるので、これを使ってサーバが異なる結果を返すこともできそうです。
import java.net.*; import java.io.*; public class WebClient { public static void main(String[] args){ int port_no = 8080; //ポート番号とりあえず8080にする String hostName = "localhost"; //サーバが同じマシンで動いている場合。実際には"192.168.0.xx"など int sleep_time = 50; //待ち時間 if(args.length >= 1) { int colon = args[0].lastIndexOf(':'); if(colon == -1) { hostName = args[0]; } else { hostName = args[0].substring(0,colon); port_no = Integer.parseInt(args[0].substring(colon + 1)); } } try{ Socket socket = new Socket(hostName, port_no);// ソケットを生成 OutputStream oStream = socket.getOutputStream(); PrintWriter pWriter = new PrintWriter(oStream, true); InputStream iStream = socket.getInputStream(); BufferedReader bReader = new BufferedReader(new InputStreamReader(iStream)); pWriter.print("GET / HTTP/1.1\r\n"); //curlのメッセージを真似ました pWriter.print("User-Agent: siio_client\r\n"); pWriter.print("Host: " + hostName + ":" + port_no + "\r\n"); pWriter.print("Accept: */*\r\n\r\n"); //空行がデータ送れの合図 pWriter.flush(); //サーバからのデータが来るまで待つ while(iStream.available() == 0) Thread.sleep(sleep_time); Thread.sleep(sleep_time); String result=""; char[] cbuf = new char[256]; int num = cbuf.length; while(num == cbuf.length ) { num = bReader.read(cbuf, 0, cbuf.length); result += String.valueOf(cbuf,0,num); } System.out.println(result); socket.close(); //ソケットを閉じる } catch (InterruptedException e) { System.out.println("Error in Thread.sleep"); } catch(IOException e) { System.out.println("Error in socket communication"); } }//end of main }//end of Class
以下はけこたんがまとめました。
nc(NetCat)コマンドを使ってUDPソケット通信をした話
ソケット通信のプログラムにバグがある時、受信側と送信側のどちらが正しい動作していないのかを調べるのに使える。
Macの場合、 システム環境設定→ネットワーク にアクセスすることで見られる
ターミナル上で
nc -4ul 12345
を実行する。 オプションは、それぞれ
-4 IPv4 -u UDPモード -l listenモード
を表している。
12345はポート番号。適当な番号を指定する。
ターミナル上で
nc -u 192.168.108.147 12345
を実行する。
192.168.108.147の部分には、受信側のIPアドレスを指定する。
12345はポート番号。受信側と同じ番号を指定する。
送信側で値を入力すると、受信側で表示される。
http://d.hatena.ne.jp/miho36/20100420/1271753758
次は、AndroidからPCにデータを送る例です。
<uses-permission android:name="android.permission.INTERNET"/>をAndroidManifest.xmlに追加する。
DatagramSocket sendSocket = new DatagramSocket();で、ソケットを作り、
sendSocket.close();で、ソケットを閉じる。
DatagramPacket packet = new DatagramPacket(msg, msg.length, inetAddress, param.port);で送るパケットを生成する。
param.socket.send(packet);でパケットを送る。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.udptest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.udptest.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.udptest.MainActivity" tools:ignore="MergeRootFrame" > <EditText android:id="@+id/inputText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="94dp" android:ems="10" android:inputType="text" > <requestFocus /> </EditText> <Button android:id="@+id/sendButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/inputText" android:layout_centerHorizontal="true" android:layout_marginTop="50dp" android:text="@string/send_button" /> <Button android:id="@+id/quitButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/sendButton" android:layout_below="@+id/sendButton" android:layout_marginTop="78dp" android:text="@string/quit_button" /> </RelativeLayout>
MainActivity.java
package com.example.udptest; import java.net.DatagramSocket; import java.net.SocketException; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; public class MainActivity extends ActionBarActivity { private DatagramSocket sendSocket = null; EditText inputText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, new PlaceholderFragment()).commit(); } Button sendButton = (Button)findViewById(R.id.sendButton); sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendMessage(inputText.getText().toString()); } }); Button quitButton = (Button)findViewById(R.id.quitButton); quitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); inputText = (EditText)findViewById(R.id.inputText); } @Override protected void onResume() { super.onResume(); try { sendSocket = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment { public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater .inflate(R.layout.fragment_main, container, false); return rootView; } } @Override public void onStop() { super.onStop(); sendSocket.close(); } public void sendMessage(String body) { udpParam param = new udpParam(); param.body = body; param.socket = sendSocket; UDPSendRequest request = new UDPSendRequest(); request.execute(param); } class udpParam { String body = ""; String address = "192.168.108.147"; int port = 60000; DatagramSocket socket = null; } }
UDPSendRequest.java
package com.example.udptest; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import android.os.AsyncTask; import android.util.Log; import com.example.udptest.MainActivity.udpParam; public class UDPSendRequest extends AsyncTask<udpParam, Void, Boolean> { private String TAG = "UDBSendRequest"; private udpParam param; @Override protected Boolean doInBackground(udpParam... params) { param = params[0]; if (param.socket == null) { try { param.socket = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); return false; } } byte[] msg; try { msg = param.body.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return false; } InetAddress inetAddress; try { inetAddress = InetAddress.getByName(param.address); } catch (UnknownHostException e) { e.printStackTrace(); return false; } DatagramPacket packet = new DatagramPacket(msg, msg.length, inetAddress, param.port); try { param.socket.send(packet); } catch (IOException e) { e.printStackTrace(); return false; } return true; } @Override protected void onPostExecute(Boolean result) { if (result) { Log.v(TAG, "successed to send message"); } else { Log.e(TAG, "failed to send a message"); } } }
testApp.h
#pragma once #include "ofMain.h" #include "ofxUI.h" #include <iostream> #include <string> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <netinet/in.h> class testApp : public ofBaseApp{ public: void setup(); void update(); void draw(); void exit(); void keyPressed(int key); void keyReleased(int key); void mouseMoved(int x, int y); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); void dragEvent(ofDragInfo dragInfo); void gotMessage(ofMessage msg); ofxUICanvas *gui; void guiEvent(ofxUIEventArgs &e); bool drawPadding; int sock; // socket discreptor fd_set fd, readfd; //file discreptor int maxfd; // calculate max value of file discriptor. It is used by select. char buffer[8]; struct sockaddr_in from_address; socklen_t sin_size; };
testApp.cpp
#include "testApp.h" //-------------------------------------------------------------- void testApp::setup(){ gui = new ofxUICanvas(); gui->addLabelButton("EXIT", false); ofAddListener(gui->newGUIEvent, this, &testApp::guiEvent); struct sockaddr_in address; // create IPv4 UDP socket if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); OF_EXIT_APP(-1); } // sender IP and Port address.sin_family = AF_INET; address.sin_port = htons(60000); address.sin_addr.s_addr = INADDR_ANY; // receive all packet whatever to_address // bind if (bind(sock, (struct sockaddr*)&address, sizeof(address)) < 0) { perror("bind"); OF_EXIT_APP(-1); } // set sock non-blocking mode // if you change val to 0, you can set sock blocking mode // sock default is blocking mode int mode = 1; ioctl(sock, FIONBIO, &mode); } //-------------------------------------------------------------- void testApp::update(){ memset(buffer, 0, sizeof(buffer)); if (recv(sock, buffer, sizeof(buffer), 0) < 1){ if (errno != EAGAIN) { perror("recv"); } } else { cout << buffer << endl; if (string(buffer) == "end") { OF_EXIT_APP(1); } } } //-------------------------------------------------------------- void testApp::draw(){ } //-------------------------------------------------------------- void testApp::exit() { cout << "exit" << endl; close(sock); } //-------------------------------------------------------------- void testApp::guiEvent(ofxUIEventArgs &e) { string name = e.widget->getName(); int kind = e.widget->getKind(); if (kind == OFX_UI_WIDGET_LABELBUTTON) { OF_EXIT_APP(1); } } //-------------------------------------------------------------- void testApp::keyPressed(int key){ if (key == 'q') { OF_EXIT_APP(1); } } //-------------------------------------------------------------- void testApp::keyReleased(int key){ } //-------------------------------------------------------------- void testApp::mouseMoved(int x, int y){ } //-------------------------------------------------------------- void testApp::mouseDragged(int x, int y, int button){ } //-------------------------------------------------------------- void testApp::mousePressed(int x, int y, int button){ } //-------------------------------------------------------------- void testApp::mouseReleased(int x, int y, int button){ } //-------------------------------------------------------------- void testApp::windowResized(int w, int h){ } //-------------------------------------------------------------- void testApp::gotMessage(ofMessage msg){ } //-------------------------------------------------------------- void testApp::dragEvent(ofDragInfo dragInfo){ }