2015年3月13日 星期五

[iOS] UDP Socket 實作-含程式碼 / 完整範例 / Youtube 實測

終於可以把udp socket 做出來了!

每次google 都是那幾篇.....而且每個都寫很不完整(很多都抄同一篇的)

搞了半天什麼CocoaAsyncSocket , GCDAsyncSocket , BSD Socket.....

沒有一篇寫比較完整,後來還好在github上有一個可以用的(不過是for Mac ,not for

iphone) 於是就仿照著寫了以後就可以用了!

--------------------------------------------------------------------------------------------------------------------------

(正文開始)

要寫socket 一定是要分兩部分 server & client , 然後在眾多的library裡面我選擇是:

AsyncUdpSocket

首先,先開一個project,然後add files to (project name)...

把AsyncUdpSocket.h / AsyncUdpSocket.m 加入(放哪邊不重要,不影響)




然後我們需要先到ViewController.h / Main.storyboard來宣告/拉一些物件


這邊(server)會需要一個輸入port 的textfield,一個按鈕 以及一個textview

別忘記要把他們兩邊做連結!


ViewControlloer.h:

//
//  ViewController.h
//  UdpServer
//
//  Created by daniel on 2015/3/13.
//  Copyright (c) 2015 daniel. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "AsyncUdpSocket.h"

@interface ViewController : UIViewController
{
    AsyncUdpSocket *udpSocket;

}

@property (weak, nonatomic) IBOutlet UITextField *port;
@property (weak, nonatomic) IBOutlet UIButton *start;
@property (weak, nonatomic) IBOutlet UITextView *textview;

- (IBAction)startButton:(id)sender;


@end


接下來就是

ViewController.m:

//
//  ViewController.m
//  UdpServer
//
//  Created by daniel on 2015/3/13.
//  Copyright (c) 2015 daniel. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize port;
@synthesize start;
@synthesize textview;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self];//初始化
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)startButton:(id)sender {
        //start udp server
        NSLog(@"Socket open");
        int port_int = [port.text intValue];
        if(port_int < 0 || port_int > 65535)
        {
            port_int = 0;
        }
        
        NSError *error = nil;
        
        if (![udpSocket bindToPort:port_int error:&error]) {
            return;
        }
        
        [udpSocket receiveWithTimeout:-1 tag:0];
        [port setEnabled:NO];
}

-(BOOL) onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port
{
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if(msg)
    {
        //textview.text  msg;
        NSLog(@"message is %@",msg);
        
        //以下這串是為了讓他能夠顯示在畫面上
        NSString *temp = [NSString stringWithFormat:@"%@\n",msg];
        NSMutableDictionary *attribute = [NSMutableDictionary dictionaryWithCapacity:1];
        NSAttributedString *as = [[NSAttributedString alloc] initWithString:temp attributes:attribute];
        //使用append的方式 ,但是他是NSAttributedString格式 所以才要先轉換,不能直接使用NSString!
        [[textview textStorage] appendAttributedString:as];
    }
    else
    {
        NSLog(@"converting UTF-8 Error");
    }
    [udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:0];
    [udpSocket receiveWithTimeout:-1 tag:0];
    return YES;
}
@end


在viewDidLoad這邊就是app一開始會跑的地方,當然是把socket初始化

然後在button按下去觸發的這個函式(IBAction)startButton:(id)sender )

我們需要把使用者輸入的port 放進int port_int裡面

然後如果使用者輸入數字超過0~65535這個區間 就直接給他port = 0

接著在-(BOOL) onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port

這一段,我們必須要先宣告一個NSData來接收資料(data),編碼這邊使用UTF-8

如果msg有東西則顯示在螢幕上

記得最後面要加

    [udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:0];

    [udpSocket receiveWithTimeout:-1 tag:0];

不然只能接收一次 !



--------------------------------------------------------------------------------------------------------------------------

然後就是 Client端了


一樣是把兩邊做連結(別忘了要import "AsyncUdpSocket.h")


//
//  ViewController.m
//  UdpClient
//
//  Created by daniel on 2015/3/13.
//  Copyright (c) 2015 daniel. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize addr;
@synthesize port;
@synthesize msg;
@synthesize sendButton;
@synthesize logView;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self];
    NSError *error = nil;
    
    if(![udpSocket bindToPort:0 error:&error])
    {
        NSLog(@"Error binding: %@",error);
        return;
    }
    
    [udpSocket receiveWithTimeout:-1 tag:0];
    
    NSLog(@"Ready");
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


-(IBAction)send:(id)sender
{
    NSString *host = [addr text];
    NSLog(@"Address: %@",host);
    
    
    int port_int = [[port text] integerValue];//跟原本的不太一樣
    if(port_int <=0 || port > 65535)
    {
        NSLog(@"Valid port require");
    }
    
    NSString *message = [msg text];
    if (message.length == 0) {
        NSLog(@"Message require");
    }
    
    NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
    
    if(data != nil)
        NSLog(@"data = %@",data);
    
    [udpSocket sendData:data toHost:host port:5000 withTimeout:-1 tag:tag];
}


-(void) onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag
{
    //you could add checks here
}

- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error
{
    // You could add checks here
}

-(BOOL) onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port
{
    
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if(msg)
    {
        NSLog(@"Receive: %@",msg);
    }
    else
    {
        NSLog(@"Receive Unknown message from %@:%hu",host,port);
    }
    [udpSocket receiveWithTimeout:-1 tag:0];
    return YES;
}

@end

在viewDidLoad這邊一樣要先初始化udpSocket,然後在sendbutton觸發這個action的

event這邊:
-(IBAction)send:(id)sender
{
    NSString *host = [addr text];
    把使用者輸入的ip放入host變數   

    NSLog(@"Address: %@",host);
    
    
    int port_int = [port text];
    
    把使用者輸入的port放進去port_int變數   

    if(port_int <=0 || port > 65535)
    {
        NSLog(@"Valid port require");
    }
    
    NSString *message = [msg text];

    把使用者輸入的message放進去message變數

    if (message.length == 0) {
        NSLog(@"Message require");
    }
    
    NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
    
   把要傳出去的message轉換為NSData型態,編碼使用utf-8

    if(data != nil)
        NSLog(@"data = %@",data);
    
    [udpSocket sendData:data toHost:host port:5000 withTimeout:-1 tag:tag];
    
    這邊為了測試方便直接把port 寫死5000,其實應該要寫port:port_int才對
}


好了!

這樣server / client 都有了 就可以開始通訊拉!




附上程式碼!


https://www.youtube.com/watch?v=wNX29RxA2K8&feature=youtu.be
https://www.youtube.com/watch?v=mDJv2UHnyZg&feature=youtu.be
附上實測證明可以用