最近做了個項目,裏面用了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,而且要連接的端口號就是服務端暴露出來的端口號。