最近做了个项目,里面用了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,而且要连接的端口号就是服务端暴露出来的端口号。