android基於TCP的Socket的使用

最近做了個項目,裏面用了socket來通信,今天總結下。Socket服務端設備需提供熱點供客戶端所在設備連接。

先講服務端:

因爲需要服務端提供熱點,所以我們先要去打開熱點並配置,方法如下:

public static boolean setWifiApEnabled(boolean enable,String wifiName,String passWord){

    WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);

    if(enabled){

    //wifi和熱點不能同時打開,所以打開熱點的時候需要關閉wifi

    wifiManager.setWifiEnabled(false);

    }

    try{

    WifiConfiguration apConfig = new WifiConfiguration();

    //熱點名稱

    apConfig .SSID = wifiName;

    //熱點密碼

    apConfig.preSharedKey = passWord;

    Method method = wifiManager.getClass().getMethod(“setWifiApEnabled”,WifiConfiguration.class,Boolean.TYPE);

    return (Boolean)method.invoke(wifiManager,apConfig,enabled);

    }catch(Exception e){

          return false;

    }

}

熱點配置完後,服務端需要提供端口,等待客戶端連接,因爲網絡操作不能直接寫在主線程裏,所以需要開一個線程去執行:

new Thread(){

    @Override

    public void run(){

    super.run();

    try{

     //服務端提供端口號

    ServerSocket serverSocket = new ServerSocket(7240);

    while(true){

    //等待連接

    Socket mSocket = serverSocket.accept();

    new Thread(new socketTask(mSocket)).start();

    }

    }catch(IOException e){

    e.printStackTrace();

    }

    }

}.start();

需要注意的是,端口號規定是16位,也就是65535個不同的端口,其中0-1023是分配給系統的端口號,1024-49151是登記端口號,主要分配給第三方應用使用,49152-65535是短暫端口號,留給客戶進程暫時使用,一個進程用完供其他進程使用。

因爲需要支持多客戶端連接,所以每當有一個客戶端連上時,新開線程去專門執行,這樣不同的線程可以操作不同的客戶端。

class socketTask implements Runnable{

    private Socket mSocket;

    private BufferedInputStream mBufferInputStream;

    private BufferedOutputStream mBufferOutputStream;

    public socketTask(Socket socket){

    this.mSocket = socket;

    if(mSocket ==null){

    return;

    }

    try{

    //設置不延時發送

    mSocket.setTcpNoDelay(true);

    //設置輸入輸出緩衝流大小

    mSocket.setSendBufferSize(8*1024);

    mSocket.setReceiveBufferSize(8*1024);

    mBufferInputStream = new BufferedInputStream(mSocket.getInputStream);

    mBufferOutputStream = new BufferedOutputStream(mSocket.getOutputStream);

}catch(Exception e){

    e.printStackTrace();

}

    }

    @Override

    public void run(){

    try{

    If(mSocket==null){

    return;

    }

    //開啓一個while循環的線程,一直去獲取輸入流數據

    ReadThread mReadThread = new ReadThread(mSocket,mBufferInputStream ,mBufferOutputStream );

    mReadThread.start();

    boolean bool = true;

    while(bool ){

    try{

    mSocket.sendUrgentData(0xFF);

    Thread.sleep(3000);

    }catch(Exception e){

    bool = false;

    mReadThread.stopThread();

    mReadThread = null;

    mBufferInputStream.close;

    mBufferInputStream = null;

    mBufferOutputStream.close;

    mBufferOutputStream= null;

    mSocket.close;

    mSocket = null;

    }

    }

}catch(Exception e){

e.printStackTrace();

}

    }

}

 注意,setSendBufferSize和setReceiveBufferSize設置輸入輸出流緩衝區大小的時候,系統默認的緩衝區大小是8*1024,一般情況下不要去改這個大小。改小的話,socket通信速度會快點,但有可能會造成緩衝區溢出;改大的話,速度會相對變慢,且有可能造成服務端接收到數據的時延越來越大。

sendUrgentData發送緊急心跳包,一般通過定時發送可以判斷當前的socket連接是否斷開,一旦報異常,則表明socket連接已斷開。特別注意的是,如果服務端運行在WIN7上,則不適用這種方法,因爲WIN7上連續調用sendUrgentData十幾次,就會出現異常,而其實socket並沒有斷開連接。(可能不止是WIN7上會發生這種情況)

當然你也可以定一條協議定時發送,以此來判斷連接狀態。

 

服務端開啓一個while循環的線程,一直去獲取輸入流數據

class ReadThread extends Thread{

    private boolean isRunning = true;

    private Socket socket;

    private BufferedInputStream mInputStream;

    private BufferedOutputStream mOutputStream;

    public ReadThread(Socket socket,BufferedInputStream mInputStream,BufferedOutputStream mOutputStream){

    this.socket = socket;

    this.mInputStream = mInputStream;

    this.mOutputStream = mOutputStream;

    }

    public void startThread(){

    try{

    //當解析錯誤時,獲取緩衝區裏的數據,全部拋掉

    int inCount = mInputStream.available();

    if(inCount >0){

    byte[] buf = new byte[inCount ];

    readData(mInputStream,buf,inCount);

    }

}catch(Exception e){

e.printStackTrace();

}

    }

    public void stopThread(){

    isRunning = false;

    }

    @Override

    public void run(){

    super.run();

    byte[] inputByte = new byte[1024];

    While(isRunning ){

    readData(mInputStream,inputByte ,1024);

    }

    }  

}

 服務端讀取輸入流數據

private int readData(BufferedInputStream mInputStream,byte[] buffer,int len){

    int r = -1;

    try{

    if(mInputStream!=null){

    int cnt;

    cnt = len;

    int dataLen = 0;

    while(cnt >0){

    r = mInputStream.read(buffer,dataLen,cnt);

    if(r>0){

    cnt -= r;

    dataLen += r;

    }else{

    throw new IOException();

    }

    }

    if(dataLen != len){

    throw new IOException();

    }

    return dataLen;

    }else{

    throw new IOException();

    }

    }catch(Exception e){

    e.printStackTrace();

    return r;

    }

}

服務端寫入輸出流

private int writeData(BufferedOutputStream mOutputStream,byte[] buffer,int len){

    try{

if(mOutputStream!=null){

mOutputStream.write(buffer,0,len);

mOutputStream.flush();

return len;

}else{

Throw new IOException();

}

    }catch(Exception e){

e.printStackTrace();

return -1;

    }

}

Socket的寫入操作在Android7.0以下,不需要寫在線程裏,而7.0以後,因爲審驗更嚴格,如果mOutputStream.write不是寫在線程裏,則會報主線程不能進行網絡操作的異常。

 

 

接下來講客戶端:

客戶端連接

InetAddress addr  =  InetAddress .getByName(“192.168.43.1”);

Socket mSocket = new Socket(addr,7240);

//設置不延時發送

mSocket.setTcpNoDelay(true);

//設置輸入輸出緩衝流大小

mSocket.setSendBufferSize(8*1024);

mSocket.setReceiveBufferSize(8*1024);

mBufferInputStream = new BufferedInputStream(mSocket.getInputStream);

mBufferOutputStream = new BufferedOutputStream(mSocket.getOutputStream);

客戶端數據的獲取和寫入跟服務端是一樣的寫法,這裏不就表述了。需要注意的是,android熱點方的ip地址固定是192.168.43.1,這是在底層寫死的,所以客戶端去連接服務端,所要連接的ip地址就是192.168.43.1,而且要連接的端口號就是服務端暴露出來的端口號。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章