◆PROCESSING 逆引きリファレンス
カテゴリー:Web・通信
ソケット通信を行うには(クライアント編)
【概要】
PROCESSING公式の通信ライブラリを使う事で、簡単にソケット通信が行えるようになります。
ソケット通信には、TCP/IP(ストリーム通信)とUDP/IP(データグラム通信)がありますが、PROCESSINGの通信ライブラリではTCP/IPをサポートしています。
Javaのライブラリなどで1から作ると小難しい(面倒くさい)処理なのですが、PROCESSINGの通信ライブラリを使えば簡単に実現できます。ビバ!。素晴らしい。
一般的に何らかの要求を送る側をクライアント、要求を待ち受けて処理を実行し、その結果を返す側をサーバと呼びます。
(画像URL:illust-AC 様:acworksさん、wayさん)
ここではまず、クライアント側の処理を作る方法を紹介します。サーバ側の処理については「ソケット通信を行うには(サーバ編)」記事を参照してください。
クライアント側のソケットは、Clientクラスのインスタンスとして作成します。
Clientクラスの主なメソッドには、以下のようなものがあります。
番号 | メソッド | 処理概要 |
---|---|---|
1 | active() | Clientソケットが有効か検査します |
2 | available() | Client側で受信すべきデータがあるか検査します |
3 | readBytes() | 受信したデータをByte配列に読み取ります |
4 | readString() | 受信したデータをStringとして読み取ります |
5 | readStringUntil() | 受信したデータを指定サイズだけ、Stringとして読み取ります |
6 | write() | データを送信します |
7 | stop() | ソケットを切断します |
8 | clientEvent() | 受信イベントを処理します |
なおPROCESSINGの通信ライブラリを使うには、プログラムの先頭に
import processing.net.*;
を記述してください。
【詳細】
Clientインスタンス作成
applet : PAppletインスタンス。通常は this を与える
ipAddr : 接続先のサーバIPアドレス
port : 接続先のサーバポート番号
接続先となるサーバIPアドレスとポート番号を指定して、クライアント側ソケットを生成します。
IPアドレスにはdot(.)区切りのIPv4形式のアドレスを指定します。
クライアント側ソケット生成時に、サーバ側で待ち受けの準備ができていない場合やIPアドレスが不正な場合は、例外エラーが発生します。
以下はローカルホストの5204番ポートへ接続するクライアントソケットの生成例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import processing.net.*; void setup(){ //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる Client myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } //いろいろな処理 } void draw(){ } |
ソケットの有効性を確認する
alive : 有効ならTrue
myClient : Clientインスタンス
クライアントソケットが有効(生成できた)場合はTrueが戻されます。
受信可能データの有無を検査する
bytes : 受信可能なデータのバイト数。0ならデータなし
myClient : Clientインスタンス
受信可能なデータがあるか調べます。受信可能なデータがある場合、そのバイト数を戻します。0なら受信可能なデータが無いという事になります。
以下はクライアントソケットを生成後、受信可能なデータがあるか調べながら読み取り処理(後述)を行う例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } //いろいろな処理 } void draw(){ //受信可能データの有無を調べる if( myClient.available() > 0 ){ //DATA READ処理 println("受信可能なデータがあります"); } } |
バイトデータを受信する
byte[] recvData = myClient.readBytes( maxSize ) ;
recvSize : 受信したデータサイズ
recvData : 受信用のbyte配列
myClient : Clientインスタンス
maxSize : 一度に受信する最大バイトサイズ
受信したデータをbyte配列として取り出します。
受信する方法は大きく2種類あります。1つは予め受信用のbyte配列を用意しておき、そこに受信する方法です。
もう1つは1度に受信する最大サイズを決めておいて、受信結果をbyte配列として受け取る方法です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } } void draw(){ //受信データがあるか確認する int readBytes = myClient.available(); if( readBytes > 0 ){ //受信データがある場合 //データを、1度に最大1024バイトずつ受信する byte[] byteBuffer = myClient.readBytes( 1024 ); println( byteBuffer.length + " byteを受信しました" ); } } |
上記は受信データがある場合、1回の受信(readBytes)で最大1024バイトのデータを受け取る例です。
もしも受信したデータがmaxSize(この例では1024)バイト以上ある場合は、複数回に分けて受信されます。
全データサイズ(available()で得られたサイズ)を指定して受信した場合は、すべてのデータが一度に受信されます。
文字列データを受信する
recvData : 受信した文字列
myClient : Clientインスタンス
受信したデータを文字列として取り出します。
この命令は受信する文字列がASCIIであると仮定して動作しています。送信側がPROCESSING以外の場合は、本命令で文字列を受信するためには、送信側で文字列をUTF-8形式のバイト配列として送り出す必要があります。
ただしPROCESSING同士(つまり送信側もPROCESSINGの通信ライブラリ)で文字列を送受信するのであれば、UTF-8形式の文字列を扱う限り、上記のようなことを意識する必要はありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } } void draw(){ //受信データがあるか確認する int readBytes = myClient.available(); if( readBytes > 0 ){ //受信データがある場合 //データを、文字列として受信する String recvData = myClient.readString(); println( "[" + recvData + "]を受信しました" ); } } |
上記例は受信データがある場合、任意の文字列データを受け取る例です。
決められた位置まで受信する
recvData : 受信した文字列
myClient : Clientインスタンス
interesting :区切りデータ
受信したデータを、区切りデータが見つかった位置まで文字列として取り出します。
このメソッドは、例えば改行コードなどで区切られた一連の文字列を受信したい場合に活用できます。
(画像URL:illust-AC 様:ちょろぎさん)
受信データの中に区切りデータが見つからない場合は、null が戻されます。
ただしnullが戻された場合でも、受信データが破棄されるわけではない事に注意してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } } void draw(){ //受信データがあるか確認する int readBytes = myClient.available(); if( readBytes > 0 ){ //受信データがある場合 //データを、文字列として受信する String recvData = myClient.readStringUntil( '\n' ); if( recvData == null ){ //区切りデータ以降のすべての文字列を受信する println( "[" + myClient.readString() + "]を受信しました" ); } else { //受信した区切りデータ単位の文字列を表示する println( "[" + recvData + "]を受信しました" ); } } } |
上記例では、送信側から「くじら\nイルカ\n最後は改行なし」というデータを受信する例です。
<出力例>
最後の文字列(「最後は改行なし」)には改行データが含まれていないため、この部分は readStringUntil() では受信できない事に注意してください。
データを送信する
void myClient.write( String sendData ) ;
sendData: 送信するデータ
myClient : Clientインスタンス
接続されているソケットに、データを書き込みます。
この命令は、すべてのデータをソケットに書き出すまでBLOCKする事(つまり上位に制御が戻ってこない事)に注意してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() ){ myClient.write( "データを書き込みます" ); } } void draw(){ } |
ソケットを切断する
myClient : Clientインスタンス
接続されているソケットを閉じます。ソケットを閉じると、そのソケットは再度接続するまで利用できません。
受信可能状態でソケットを切断すると、コンソール領域に
「Client SocketException: Socket closed」
のような赤いメッセージが表示されますが、これは異常ではありません。
受信イベントを処理する
someClient : データを受信したClientインスタンス
クライアントソケットが何某かのデータを受信した場合、clientEvent 関数が呼び出されます。
関数にはデータを受信したクライアントインスタンスが渡ってきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import processing.net.*; Client myClient; void setup(){ size(200,200); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } } void draw(){ } //データ受信した時に呼び出されるイベント関数 void clientEvent(Client someClient) { println("受信データが届きました"); int recvCount = 1; //すべてのデータを受信する while( true ){ int readBytes = someClient.available(); if( readBytes > 0 ){ //受信データがある場合 //データを、1度に最大1024バイトずつ受信する byte[] byteBuffer = someClient.readBytes( 1024 ); println( byteBuffer.length + " byteを受信しました (" + recvCount++ + ")回" ); } else { //もうデータはない break; } } } |
上記は本イベントの利用例です。
前述までのサンプルが draw() 関数の中で受信データを処理していたのに対し、本処理ではclientEvent 関数の中で処理しています。
draw() 関数の中で受信データを処理するよりは、こちらのほうが効率が良いですが、clientEvent 関数はデータを受信するたびに呼び出される事に注意してください。
例えば大きなサイズのデータを受信した場合、データが複数回に分けて分割受信される事があります。その場合は、clientEvent 関数が複数回呼び出されます。
【関連記事】
サンプルプログラム
イメージを受信する例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
/** * PROCESSING 3.0 Server/Client Sample * クライアント側処理例 * @auther MSLABO * @since 2019/06 1.0 */ import processing.net.*; import java.awt.image.BufferedImage; import javax.imageio.ImageIO; import java.io.ByteArrayInputStream; import java.awt.Image; Client myClient; PImage recvImage; boolean recvFlg; void setup(){ size( 300,300 ); textSize( 32 ); textAlign(LEFT,TOP); fill(0,0,255); //クライアントソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //でない場合、例外となる myClient = new Client(this,"127.0.0.1", 5204 ); if( myClient.active() == false ){ println("接続失敗"); exit(); } //未受信にする recvFlg = false; } void draw(){ background(200); //未受信なら受信データを確認し、 //受信済みなら表示する if( recvFlg == false ){ //未受信 recvCheckAndConvertImage(); } else { //受信済み recvImage.resize( width, height ); image( recvImage, 0, 0 ); text( "Client", 0, 0 ); } } //受信データがあるか検査し、データをイメージ変換する void recvCheckAndConvertImage(){ int readBytes = myClient.available(); if( readBytes > 0 ){ //受信データがある場合 byte[] recvData = myClient.readBytes( readBytes ); //受信データをイメージ変換する try{ BufferedImage bImg = ImageIO.read(new ByteArrayInputStream(recvData)); Image img = (Image)bImg; recvImage = new PImage(img); recvFlg = true; }catch( Exception e ){ e.printStackTrace(); } } } |
サーバ側からイメージデータを受信し、表示します。
<出力サンプル>
(画像URL:illust-AC 様:くみた柑さん)
対応するサーバ側のサンプルは、以下のようになります。利用しているサーバソケット命令の詳細については、「ソケット通信を行うには(サーバ編)」記事を参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
/** * PROCESSING 3.0 Server/Client Sample * サーバ側処理例 * @auther MSLABO * @since 2019/06 1.0 */ import processing.net.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; Server myServer; PImage sendImage; byte[] sendData; void setup(){ size( 300,300 ); textSize( 32 ); textAlign(LEFT,TOP); fill(255,255,0); //サーバソケットを生成する //本例ではローカルHOSTの5204番ポートが待ち受け状態 //となる myServer = new Server(this, 5204 ); if( myServer.active() == false ){ println("作成失敗"); exit(); } //送信対象イメージを読み込む sendImage = loadImage("witch.png"); Path file = Paths.get( dataPath( "witch.png" ) ); try{ sendData = Files.readAllBytes( file ); } catch( IOException e ){ e.printStackTrace(); } } void draw(){ background(sendImage); text( "Server", 0, 0 ); } //接続があったクライアントにイメージデータを送る void serverEvent(Server someServer, Client conClient) { println( "接続先にデータを送信:" + sendData.length ); conClient.write( sendData ); } |
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。