LoginSignup
16
15

More than 3 years have passed since last update.

Objective-CでJavaとSocket通信

Last updated at Posted at 2014-09-05

こんにちは

人類最大のクソ言語の寿命もそろそろ終わりですが、
Javaサーバーと通信することを目的として作ったプログラムを公開します。
objective-cの経験は浅いです。
ARC前提のプログラムです。
Javaの DataInputStream と DataOutputStream と通信ができます。
誰か雇って下さい。

使い方

openとread/write系メソッドは必ずメインスレッド以外でコールして下さい。
あとは直感で使用できるはずです。

ソース

JavaSocket.h
#import <Foundation/Foundation.h>

@interface JavaSocket : NSObject<NSStreamDelegate> {
    NSInputStream* inputStream;
    NSOutputStream* outputStream;
}

//  ホスト名・ポートを指定して初期化する
- (id)initWithHostName:(NSString*)hostName port:(int)port;

//  接続開始(ネットワーク&ブロック)
//  ミリ秒でタイムアウト時間を指定する 0含む以下で無制限
//  falseは失敗
- (bool)open:(int)timeoutMsec;

//  接続終了
- (void)close;

//  closeがコールされる
- (void)dealloc;

//  読み込み系メソッド(ネットワーク&ブロック)
//  ビッグエンディアンで受信してネイティブエンディアンに変換され取得される
//  失敗時は例外を投げる
- (signed char)readByte;
- (signed short)readShort;
- (signed int)readInt;
- (signed long long)readLong;
- (float)readFloat;
- (double)readDouble;
- (bool)readBool;
- (NSString*)readUTF;
- (NSData*)readBytes:(unsigned int)length;

//  書き込み系メソッド(ネットワーク&ブロック)
//  ビッグエンディアンで送信される
//  失敗時は例外を投げる
- (void)writeByte:(signed char)v;
- (void)writeShort:(signed short)v;
- (void)writeInt:(signed int)v;
- (void)writeLong:(signed long long)v;
- (void)writeFloat:(float)v;
- (void)writeDouble:(double)v;
- (void)writeBool:(bool)v;
- (void)writeUTF:(NSString*)v;
- (void)writeBytes:(NSData*)v;  //  単純にバイト列だけを書きだすので その前にv.lengthを送信することをオススメ

@end
JavaSocket.m
#import "JavaSocket.h"
#include <limits.h>

//  値は単純な割り当てではないので変更してはいけない
#define BYTEORDER_LITTLE_ENDIAN    0x01
#define BYTEORDER_BIG_ENDIAN       0x00

@implementation JavaSocket {
    dispatch_semaphore_t semaphore;
}

//  ネイティブエンディアンを取得する(端末のバイトオーダー)
//  BYTEORDER_LITTLE_ENDIAN     リトルエンディアン
//  BYTEORDER_BIG_ENDIAN        ビッグエンディアン
+ (int)getNativeByteOrder {
    int x = 0x01;
    return *(char*)&x;
}

//  バイトオーダを切り替える
//  リトルならビッグに ビッグならリトルになる
+ (void)changeByteOrder:(uint8_t*)data size:(int)size {
    for (int i=0; i<size/2; i++) {
        uint8_t temp = data[i];
        data[i] = data[(size-1)-i];
        data[(size-1)-i] = temp;
    }
}

//  ネイティブエンディアンのデータをリトルエンディアンに変換
+ (uint8_t*)toLittleEndian:(uint8_t*)data size:(int)size {
    if ([JavaSocket getNativeByteOrder] == BYTEORDER_BIG_ENDIAN) {
        [self changeByteOrder:data size:size];
    }
    return data;
}

//  ネイティブエンディアンのデータをビッグエンディアンに変換
+ (uint8_t*)toBigEndian:(uint8_t*)data size:(int)size {
    if ([JavaSocket getNativeByteOrder] == BYTEORDER_LITTLE_ENDIAN) {
        [self changeByteOrder:data size:size];
    }
    return data;
}

//  ビッグエンディアンのデータをネイティブエンディアンに変換
+ (uint8_t*)toNativeEndian:(uint8_t*)data size:(int)size {
    if ([JavaSocket getNativeByteOrder] == BYTEORDER_LITTLE_ENDIAN) {
        [self changeByteOrder:data size:size];
    }
    return data;
}

//  送信失敗例外を作る
+ (NSException*)createSendException {
    return [NSException exceptionWithName:@"IOException" reason:@"すべてのバイトを送信できませんでした。" userInfo:nil];
}

//  受信失敗例外を作る
+ (NSException*)createReceiveException {
    return [NSException exceptionWithName:@"IOException" reason:@"すべてのバイトを受信できませんでした。" userInfo:nil];
}

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    dispatch_semaphore_signal(semaphore);
}

- (id)initWithHostName:(NSString *)hostName port:(int)port {
    if ( !(self=[super init]) ) {
        return self;
    }

    //  値チェック
    assert(hostName != nil);
    assert(port>0 && port<65536);

    //  ストリーム用意
    CFReadStreamRef readStream = NULL;
    CFWriteStreamRef writeStream = NULL;
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostName, port, &readStream, &writeStream);
    inputStream  = CFBridgingRelease(readStream);
    outputStream = CFBridgingRelease(writeStream);
    [inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    inputStream.delegate = self;
    outputStream.delegate = self;

    return self;
}

- (bool)open:(int)timeoutMsec {
    if (inputStream==nil || outputStream==nil ||
        inputStream.streamStatus!=NSStreamStatusNotOpen || outputStream.streamStatus!=NSStreamStatusNotOpen) {
        return false;
    }

    semaphore = dispatch_semaphore_create(0);

    //  タイムアウト時刻
    dispatch_time_t timeout = timeoutMsec<=0? DISPATCH_TIME_FOREVER:dispatch_time(DISPATCH_TIME_NOW, timeoutMsec*NSEC_PER_MSEC);

    //  ストリームオープン開始
    [inputStream open];
    [outputStream open];

    //  ストリームのオープンが完了するまでブロック
    while (true) {
        //  一度格納しないと同期していないから成功しても失敗になる可能性がある
        //  例えば、すぐ下のifの処理が終わった瞬間にオープン処理が完了したら、2つ下のifで成功してるのにエラーになる
        NSStreamStatus inState = inputStream.streamStatus;
        NSStreamStatus outState = outputStream.streamStatus;

        //  両方共オープン完了
        if (inState==NSStreamStatusOpen && outState==NSStreamStatusOpen) {
            break;
        }

        //  両方共オープン中じゃないならエラー
        if (inState!=NSStreamStatusOpening && outState!=NSStreamStatusOpening) {
            return false;
        }

        //  スレッドウェイト
        if (dispatch_semaphore_wait(semaphore, timeout)) {
            return false;   //  タイムアウト
        }
    }

    //  もう必要ない
    inputStream.delegate = nil;
    outputStream.delegate = nil;
    [inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

    return true;
}

- (void)close {
    if (inputStream.streamStatus != NSStreamStatusClosed) {
        [inputStream close];
    }
    if (outputStream.streamStatus != NSStreamStatusClosed) {
        [outputStream close];
    }
}

- (void)dealloc {
    inputStream.delegate = nil;
    outputStream.delegate = nil;
    [inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [self close];
}

//

//  inputStream.readの代わりにこのメソッドを使用することをオススメ
//  違いはinputStreamのは現在読み込めるバイトしか読み込まない なので転送が間に合っていないなら読み込めた分だけ読み込んで未完了のまま処理を返す
//  このメソッドは指定したバイト数読み込むまで処理を返さないでブロックする
- (void)read:(uint8_t*)buff readLength:(int)readLength {
    if (readLength == 0) {
        return;
    }

    int writeIndex = 0;
    while (true) {
        //  現在読み込める分だけ読み込む
        NSInteger length = [inputStream read:&buff[writeIndex] maxLength:readLength - writeIndex];
        if (length < 0) {
            @throw [JavaSocket createReceiveException];
        }
        writeIndex += length;

        //  すべてのバイトを読み込み終わった
        if (writeIndex == readLength) {
            break;
        }

        //  今回読み込めたバイトがゼロなら転送が間に合っていないので1ミリ秒スリープする
        if (length == 0) {
            [NSThread sleepForTimeInterval:0.001];
        }
    }
}

- (signed char)readByte {
    signed char v;
    [self read:(uint8_t*)&v readLength:1];

    return v;
}

- (signed short)readShort {
    signed short v;
    [self read:(uint8_t*)&v readLength:2];
    [JavaSocket toNativeEndian:(uint8_t*)&v size:2];

    return v;
}

- (signed int)readInt {
    signed int v;
    [self read:(uint8_t*)&v readLength:4];
    [JavaSocket toNativeEndian:(uint8_t*)&v size:4];

    return v;
}

- (signed long long)readLong {
    signed long long v;
    [self read:(uint8_t*)&v readLength:8];
    [JavaSocket toNativeEndian:(uint8_t*)&v size:8];

    return v;
}

- (float)readFloat {
    float v;
    [self read:(uint8_t*)&v readLength:4];
    [JavaSocket toNativeEndian:(uint8_t*)&v size:4];

    return v;
}

- (double)readDouble {
    double v;
    [self read:(uint8_t*)&v readLength:8];
    [JavaSocket toNativeEndian:(uint8_t*)&v size:8];

    return v;
}

- (bool)readBool {
    uint8_t v;
    [self read:&v readLength:1];

    return v != 0;
}

- (NSString*)readUTF {
    //  サイズ読み込み
    signed short length = [self readShort];

    //  サイズ0だとエラーになるので空文字を返す
    if (length == 0) {
        return @"";
    }

    //  文字データ読み込み
    uint8_t utfData[length+1];  //  NULL文字分+1
    [self read:utfData readLength:length];
    utfData[length] = '\0';

    //  NSStringに変換
    return [NSString stringWithUTF8String:(char*)utfData];
}

- (NSData*)readBytes:(unsigned int)length {
    //  サイズ0だとエラーになるので空データを返す
    if (length == 0) {
        return [NSData new];
    }

    //  データ読み込み
    uint8_t bytes[length];
    [self read:bytes readLength:length];

    return [NSData dataWithBytes:bytes length:length];
}

//

- (void)writeByte:(signed char)v {
    if ([outputStream write:(uint8_t*)&v maxLength:1] != 1) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeShort:(signed short)v {
    if ([outputStream write:[JavaSocket toBigEndian:(uint8_t*)&v size:2] maxLength:2] != 2) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeInt:(signed int)v {
    if ([outputStream write:[JavaSocket toBigEndian:(uint8_t*)&v size:4] maxLength:4] != 4) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeLong:(signed long long)v {
    if ([outputStream write:[JavaSocket toBigEndian:(uint8_t*)&v size:8] maxLength:8] != 8) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeFloat:(float)v {
    if ([outputStream write:[JavaSocket toBigEndian:(uint8_t*)&v size:4] maxLength:4] != 4) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeDouble:(double)v {
    if ([outputStream write:[JavaSocket toBigEndian:(uint8_t*)&v size:8] maxLength:8] != 8) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeBool:(bool)v {
    uint8_t b = v? 1:0;

    if ([outputStream write:&b maxLength:1] != 1) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeUTF:(NSString*)v {
    //  文字がない時
    if (v.length == 0) {
        [self writeShort:0];
        return;
    }

    //  文字コードをUFTに変換する
    NSData* strData = [v dataUsingEncoding:NSUTF8StringEncoding];

    //  データサイズオーバー(signed shortで表現できるバイトサイズである必要がある)
    if (strData.length > SHRT_MAX) {
        @throw [JavaSocket createSendException];
    }

    //  サイズ書き出し
    signed short length = (signed short)strData.length;
    [self writeShort:length];

    //  文字データ書き出し
    if ([outputStream write:strData.bytes maxLength:length] != length) {
        @throw [JavaSocket createSendException];
    }
}

- (void)writeBytes:(NSData*)v {
    if (v.length == 0) {
        return;
    }

    //  データ書き出し
    if ([outputStream write:v.bytes maxLength:v.length] != v.length) {
        @throw [JavaSocket createSendException];
    }
}

@end
16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15