iPhoneでC言語を使ってUDP通信してみよう

iOS -> Mac with UDP


画像の拡大

PHPからプログラミングを入門した軟弱者の私としては、HTTP以下の低位レイヤーが苦手です。今までなんとなく誤魔化してきましたが、Arduinoに触れたことで学習意欲が湧きました。そこでiOSからMacへUDP送信を行なってみることにしました。学習がてらNSStreamを使わずに、あえてC言語で書いてみることにしました。

ところでUDPは、User Datagram Protocolの略です。データグラムは「配送が成功したかどうかの確認はしないので届くかどうか保証されないですよ」の意味です。

Mac側でUDPサーバーを立ち上げる

まずはMacのターミナルを開き、netcatコマンドを使ってUDPサーバーを立ち上げます。

たったの一行でUDPサーバーを立ち上げることができます。

nc -u -l 8888

ところで、ポートが解放されているかどうかの確認で少しハマりました。そもそもUDPは一方的な送信なので、ポートが開いているかどうかのスキャンはできないことが分かっていませんでした。

たとえば、8888ポートが開いているかどうかのコマンドは次のようになります。

nc -vz 192.168.100.101 8888

UDPサーバーを対象にはスキャンできません。次のように、TCPサーバーとして立ち上げた場合はポートスキャンできるようになります。

nc -l 8888

TCPのハンドシェイクを利用しているのでしょうね。

iOSプログラミング、UDPクライアントを作る

iOSのプログラミングを書いていきましょう。

Objective-CでC言語の関数を使いたい場合、.mファイルを.mmの拡張子にすれば良いようです。

#import "ViewController.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (IBAction)clickedSendButton:(id)sender {
    int sock;
    struct sockaddr_in addr;
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = inet_addr("192.168.100.101");
    
    sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));
    
    close(sock);
}
@end

UDPクライアントのプログラムは驚くほど簡単に書けることがわかりました。それでも慣れないC言語の関数が出てきたので少し調べてみることにしました。

まず、sockaddr_in構造体に送信先アドレスやポート番号を設定しています。そして、socketクラスを初期化してます。初期化の時に、SOCK_DGRAMを指定してあげればUDPで通信してくれるようになります。また、AF_INETはIPv4のインターネットプロトコルで通信することのようです。

さて、これで先ほどMac側で立ち上げたターミナル画面に"HELLO"メッセージが表示されるようになりました。

UDPのプロトコル仕様を確認してみる


画像の拡大

UDPプロトコルの仕様を確認してみると、ヘッダ部とデータ部に別れているのがわかります。とてもシンプルで、送信のイメージがつきやすいですね。

また、ポートが16ビットの長さなので、2の16乗 = 65536番まで指定できることがわかります。

Mac -> iOS with UDP


画像の拡大

UDPの理解が進んだところで、今度はMac側からUDPソケットをiPhoneへ送信しみたいと思います。

UDPサーバーのプログラミングを行なっていきます。先ほどのUDPクライアントのプログラムよりは複雑になってきますが、丁寧にみていけば難しいことはありません。注意するべきところは、UDPソケットを受信するためのrecv関数はメインスレッドで動かしてしまうと、UIが固まってしまいます。そのため、NSThreadを使ってスレッド処理をします。下記プログラムは、buf配列のメモリ解放を行ってないのでご注意ください。

//
//  ViewController.mm
//

#import "ViewController.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextView *textView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(reciveUdp) object:nil];
    [thread start];
}


-(void)updateTextView:(NSData *)data {
    NSString *str = [[NSString alloc] initWithData:data
                                          encoding:NSUTF8StringEncoding];
    _textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, str];
}

- (void) reciveUdp {
  
    int sock; // ソケットのファイルディスクリプター
    struct sockaddr_in addr;

    
    sock = socket(AF_INET, SOCK_DGRAM, 0); // SOCK_DGRAMはUDP通信
    
    addr.sin_family = AF_INET; // AF_INETはIPv4のインターネットプロトコル
    addr.sin_port = htons(8888); // 監視しするiPhone側のポート番号
    addr.sin_addr.s_addr = INADDR_ANY; // どのアドレスからの接続でも受け入れる
    
    bind(sock, (struct sockaddr *)&addr, sizeof(addr)); // ソケットへ名前をつける?

    while (1) {
        char buf[2048]; // 受信用の配列
        memset(buf, 0, sizeof(buf)); // buf配列の先頭から、最後までを0で埋めている -> ヌル終端文字列
        long size = recv(sock, buf, sizeof(buf), 0); // UDP受信開始
        
        if(size > 0) {
            printf("%s\n", buf);
            NSData *data = [NSData dataWithBytes:buf length:size];
            
            [self performSelectorOnMainThread:@selector(updateTextView:) withObject:data waitUntilDone:YES];

//            break;
        }
    }

//    close(sock);
}

@end

MacからUDPでメッセージを送ってみる

それでは最後に、実際にMacからiPhoneへUDPソケットを送ってみましょう。

Macのターミナルを開いて下記のコマンドを実行します。これはUDPクライアントを立ち上げる処理になります。

$ nc -u 192.168.100.119 8888

この後に、何か送信したいメッセージを入力し、リターンを押せばiPhone画面にメッセージが表示されるはずです。

Amazonでお得に購入するなら、Amazonギフト券がオススメ!

\Amazonギフトがお得/

コンビニ・ATM・ネットバンキングで¥5,000以上チャージすると、プライム会員は最大2.5%ポイント、通常会員は最大2%ポイントがもらえます!
Amazonギフト券

\この記事をシェアする/