顯示具有 Socket 標籤的文章。 顯示所有文章
顯示具有 Socket 標籤的文章。 顯示所有文章

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
附上實測證明可以用

2014年8月12日 星期二

[Android] UDP Receive / Send 完整-可執行-附檔案-教學

花了兩三個禮拜終於把Android UDP搞定!

首先,要先搞懂Thread / Handler是甚麼,不然這篇往下看沒有意義(因為會看不懂)

在MainActivity的部分,這邊負責主要的UI介面+Handler+Thread呼叫

package com.example.thread_example;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

import android.annotation.SuppressLint;
import android.net.wifi.WifiInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.support.v7.app.ActionBarActivity;
import android.text.format.Time;
import android.util.Log;
import android.widget.TextView;

@SuppressLint("NewApi")
public class MainActivity extends ActionBarActivity {

public static Handler exHandler;
private TextView txv;
private ChatServer chatserver;
private ChatSender chatsender;
public static InetAddress IP;
public WifiInfo mwifiInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (android.os.Build.VERSION.SDK_INT > 9) {
   StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
   StrictMode.setThreadPolicy(policy);
}

txv = (TextView) findViewById(R.id.textView);
//利用handler來顯示接收到的文字
exHandler=new Handler() {
       @Override
       public void handleMessage(Message msg) {
        super.handleMessage(msg);
        String msgString = (String)msg.obj;
        Log.d("Handler","Now in Handler");
        txv.setText(null);
        txv.setText(msgString+"\n"+txv.getText().toString());
       }
   };
        //獲得Wifi的內部網路IP
   StringBuilder IFCONFIG=new StringBuilder();
   try {
       for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
           NetworkInterface intf = en.nextElement();
           for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
               InetAddress inetAddress = enumIpAddr.nextElement();
               if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && inetAddress.isSiteLocalAddress()) {
               IFCONFIG.append(inetAddress.getHostAddress().toString()+"\n");              
               }
           }
       }
       txv.setText(IFCONFIG.toString());
       Log.d("LOG_Text", IFCONFIG.toString());
   } catch (SocketException ex) {
       Log.e("LOG_TAG", ex.toString());
   }


try{
chatserver = new ChatServer();
chatserver.start();
chatsender = new ChatSender();
chatsender.start();
Log.d("User","Thread start...");

}catch(Exception e)
{
String str = e.toString();
Log.e("Error by User", str);
}

}

protected void onPause(){
super.onPause();
if(chatserver != null)
{
if(!chatserver.isInterrupted())
{
chatserver.interrupt();
}
}
}

}

有一點長,這邊元件只有拉一個TextView而已,十分簡單。

IFCONFIG 

這邊負責顯示WIFI的IP

Thread--chatserver/chatsender

則是另外開兩個java檔(與mainActivity.java同層)分別做為接收與傳送,.start()就是讓他啟動而已。

exHandler

這邊負責顯示接收到的資訊(不能用static來傳值,因為Android不支援,至於為什麼我也不清楚),並把他印出來在textview上面。



Receiver部分(chatserver.java)

package com.example.thread_example;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

import android.os.Bundle;
import android.os.Message;
import android.util.Log;

public class ChatServer extends Thread{
private DatagramSocket server = null;
private static final int PORT = 8000;
public ChatServer() throws IOException {
server = new DatagramSocket(PORT);
Log.d("User","new server socket");
}
public void run(){

byte[] byte1024 = new byte[1024];
//Message msg = new Message();
//Bundle data = new Bundle();
DatagramPacket dPacket = new DatagramPacket(byte1024, 100);
String txt;
try{
Log.d("User","runing run()");
while(true){
server.receive(dPacket);
while(true)
{
//印出來到螢幕上
txt = new String(byte1024, 0, dPacket.getLength());
MainActivity.exHandler.sendMessage(MainActivity.exHandler.obtainMessage(1,txt));
Log.d("User","Handler send Message");
if(true) break;
}
//CloseSocket(client);//關閉
}
}
catch(IOException e)
{}
}
private void CloseSocket(DatagramSocket socket) throws IOException{
socket.close();
}
}

這邊宣告一個1024的byte來接收資料(按照你的資料量大小來調整宣告大小)

一定要宣告datagramSocket,datagramPacket,一個是連線,一個是接收封包用,至於handler這邊的

sendMessage就是回傳到MainActivity來讓textview顯示接收的資訊而已。結構上有兩個while(),第

一個while會卡在receive()這邊,一直到有封包進來才會往下跑到第二個while(),第二個則是印出資

訊,沒有甚麼特別的地方(Log是debug用的)



2014年8月11日 星期一

[Android/Java] Thread(執行緒)的使用+UDP Send / Receive+Socket

在使用Thread的時候,必須要先知道執行緒(Thread)是甚麼?

否則只是看看範例,卻只得其一不得其二。

執行緒是一種可以在程式背景執行的一個Process(程序),同時可以有多個執行緒一起執行。

由於可以多線程,因此大大增加可用性。

Thread的結構大概就是

public class ChatServer extends Thread{

private DatagramSocket server = null;
private static final int PORT = 4000;
        //宣告變數

public ChatServer() throws IOException {
server = new DatagramSocket(PORT);
Log.d("User","new server socket");
}
        //建構子

public void run(){
byte[] byte100 = new byte[100];
DatagramPacket dPacket = new DatagramPacket(byte100, 100);
String txt;
try{
Log.d("User","runing run()");
while(true){
server.receive(dPacket);
Log.d("User","While 1");
while(true)
{
//印出來到螢幕上
Log.d("User","While 2");
if(true) break;
}
//CloseSocket(client);//關閉
}
}
catch(IOException e)
{}
}
        //主要在跑的地方(主程式)

在呼叫Thread的時候,以上面範例為例,我們呼叫時使用:  

       chatserver = new ChatServer();
       new這個class,他會跑進建構子裡面,但還不會進去run()函式
       chatserver.start();
       執行run()這個函式,由於run裡面有while()迴圈,所以會一直跑不停

       (在這兩行外面要夾try catch否則會有error)

-----------------------------------------------------------------------------------------------------------
上面解釋完Thread了,下面要講的是如何在Android上面使用執行緒的概念做UDP的發送與接收,

但這目前有個問題就是模擬器(emulator)與電腦的連線還沒有試做成功,在Emulator-Emulator是可

以成功傳送-接收的。

傳送部分:

目標:寫一個Thread,讓他不停傳送給指定IP+指定Port

內容:

public class ChatSender extends Thread{

public void run(){
DatagramSocket socket = null;
try
{
socket = new DatagramSocket();
InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
Log.d("IP Address", serverAddress.toString());
String str = "Test Thread sender";
               //創建一個用於發送的DatagramPacket對象
               DatagramPacket packet=new DatagramPacket(str.getBytes(),str.length(),serverAddress,4000);
            //發送數據
            while(true)
            {
            socket.send(packet);
            Thread.sleep(1000);
            }
}
catch(SocketException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(UnknownHostException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(IOException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(Exception e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
finally{
if(socket != null){
socket.close();
}

}
}
}

大概可以看到DatagramSocket+DatagramPacket這兩個API,

DatagramSocket我的理解是用於Socket連線,而DatagramPacket則是負責傳送的封包內容。

*DatagramPacket(封包的byte[]陣列,封包的長度,IP位址,Port); 

至於InetAddress的部分是為了要確定本機IP是多少。


接收部分:

目標:寫一個Thread,讓他不停到指定的Port接收封包,直到有接收到才印出來,之後繼續hold住等待封包傳送過來。

內容:

public class ChatServer extends Thread{

private DatagramSocket server = null;
private static final int PORT = 4000;

public ChatServer() throws IOException {
server = new DatagramSocket(PORT);
Log.d("User","new server socket");
}
public void run(){
byte[] byte100 = new byte[100];
DatagramPacket dPacket = new DatagramPacket(byte100, 100);
String txt;
try{
Log.d("User","runing run()");
while(true){
server.receive(dPacket);
Log.d("User","While 1");
while(true)
{
//印出來到螢幕上
Log.d("User","While 2");
if(true) break;
}
//CloseSocket(client);//關閉
}
}
catch(IOException e)
{}
}

private void CloseSocket(DatagramSocket socket) throws IOException{
socket.close();
}
}

UDP接收的部分同樣也需要DatagramSocket/DatagramPacket,不過剛剛是Send 現在則是receive

為了存放接收的資料,前面會先new一個byte[]陣列用以存放資料。

在server.receive(dPacket);這邊如果沒有資料進來的話,他不會往下跑,會一直等待在這邊,直到接

收到資料才會執行下一行。這是必須特別注意的!


MainActivity的部分:

public class MainActivity extends ActionBarActivity {

private Handler exHandler;
private TextView txv;
private ChatServer chatserver;
private ChatSender chatsender;
public static InetAddress IP;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txv = (TextView) findViewById(R.id.textView);
try{
//IP = InetAddress.getLocalHost();
chatserver = new ChatServer();
chatserver.start();
chatsender = new ChatSender();
chatsender.start();
Log.d("User","Thread start...");
}catch(Exception e)
{
String str = e.toString();
Log.e("Error by User", str);
}
}
protected void onPause(){
super.onPause();
if(chatserver != null)
{
if(!chatserver.isInterrupted())
{
chatserver.interrupt();
}
}
}

然後要記得AndroidManifest.xml這邊要增加INTERNET的Uses-Permission

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.thread_example"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    <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.thread_example.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>


結論: UDP 傳送及接收其實不容易,看似簡單但很多細節如果沒有注意到就會失敗!