根据前两周写的关于Socket编程的网络通信的代码,现在对有关知识和注意事项进行总结如下:
1.首先说下Android NIO中有关Socket编程的类:
1)ServerSocketChannel类:服务器套接字通道相当于传统IO下的ServerSocket,通过ServerSocketChannel的socket()可以获得传统的ServerSocket,反过来使用ServerSocket的getChannel()可以获得ServerSocketChannel对象;实例化ServerSocketChannel可以直接通过ServerSocketChannel的静态方法open()就可以了。
2)SocketChannel类:套接字通道相当于传统IO下的Socket,通过SocketChannel的socket()可以获得传统的Socket,反过来使用Socket的getChannel()可以获得SocketChannel对象;
3)Selector选择器:在NIO中注册各种事件的方法主要使用Selector来实现的,我们可以使用Selector类的静态方法open()来实例化。
4)SelectionKey类:是个选择键,在NIO中选择器和选择键是很重要的,SelectionKey描述了NIO中比较重要的事件,如OP_ACCEPT、OP_READ、OP_WRITE。
2.然后说下非阻塞和阻塞模式的区别以及非阻塞模式的实现:
1)非阻塞:所谓非阻塞就是说,服务器监听客户端连接的时候,如果没有客户连接,程序还接续执行,不会停在这里等待客户连接;或者客户连接上了,下一步就是等待客户发数据,如果不发,程序不会停留在这里,而是继续执行。反过来停留在这里,不继续执行就是阻塞。
2)非阻塞模式的实现:对于客户端来说,得到SocketChannel对象后,通过调用方法configureBlocking(false)来设置这个Socket为非阻塞状态。然后在配合Selector和SelectionKey的使用来与服务器进行交互。代码如下:
SocketChannel socket = new SocketChannel(ip,port);
socket.configureBlocking(false);
mSelector = Selector.open();
socket.register(mSelector, SelectionKey.OP_READ| SelectionKey.OP_WRITE);
while (true){
mSelector.select(0);
Set<SelectionKey> readKeys = mSelector.selectedKeys();
Iterator<SelectionKey> iterator = readKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
if (key.isReadable()) {.....
} else if (key.isWritable()) {.....
}
}
}
3. 消息处理机制—Message Handler Looper
Message消息 Handler处理发送和接收消息 Looper管理消息,就是一个手柄
在什么情况下使用消息呢?比如一个子线程,在执行过程中要更改主线程UI的一个控件值,这个时候就需要发送一个消息给主线程,而使用消息处理机制也可以不干扰主线程执行其他操作,在主线程中只要Handler感应到有信息过来它会自己接收,然后处理。代码如下:
在主线程:声明一个Handler
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
在子线程:
public void sendMessage(String message, int what) {
Message msg = mHandler.obtainMessage(what, 1, 1, message);
mHandler.sendMessage(msg);
}
注意子线程中的Handler必须是主线程声明的Handler,这样子线程发送的消息才能被主线程Handler认识接收。
4.遇到的问题
1)线程问题:Socket编程中,实现服务器与多个客户端的通信要尽量用多线程机制来实现,当服务器监听到一个客户端的连接就开辟一个线程专门负责与客户端的通信,如果客户端因为某个原因断开与服务器的连接就要及时关闭线程;当然如果是服务器断开了,那么就要关闭所有与客户通信的线程。Android开发中,要注意一点,比较耗时的操作最好开辟一个线程单独执行,不然主线程因为执行这个耗时的操作而耽误执行其他操作,这样导致程序效率很差。
2)网络问题:Android开发中,往往遇到服务器与客户端正在通信时候突然网络断了,这个时候就要关闭socket,但是在关闭socket的时候,双方要知道网络断了才能关闭,如果是人为关闭socket,另一方肯定能检测到,但是是网络突然断开,就不能检测到了,解决这样的方法就是,双方连接上后,每隔一段时间发一个保护数据包,这个数据包双方只管发送,接收不进行处理,如果在一段时间内,一方既没有发送正常的数据包也没有发送保护数据包,那就关闭socket,这样就解决了网络断开的问题导致一方没有及时关闭socket的问题。
3)socket关闭问题:在进行socket编程中,遇到异常或者其他需要断开连接的情况时,socket要及时关闭,而在关闭socket之前,要保证线程关闭;举个列子说下,客户端人为断开与服务器的连接,此时服务器就会检测到与这个客户的通道已经关闭而出现异常,此时服务器就要关闭线程和socket。
还有一个特别重要的问题,在Android开发中,如果服务器与客户端通信的时候,如果没有定义一个协议,只是简单的发送一个字符串,那么如果某一方发送一个空值,这个时候另一方就会默认的认为你没有发送数据。解决这个问题的办法就是,要么定义一个规范的协议发送数据,也就是每发送一个数据都有一个头,要么定义一个专门的数据包(如byte类型的 1 )代表一个空值。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
获取手机IP地址
第一种:
public int getIpAddress() {
WifiManager wifiManager = (WifiManager) this.context
.getSystemService(Context.WIFI_SERVICE);
// 判断wifi是否开启
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipAddress = wifiInfo.getIpAddress();
return ipAddress;
}
private String intToIp(int i) {
return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF)
+ "." + (i >> 24 & 0xFF);
}
第二种:
public String getInetIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> ipAddr = intf.getInetAddresses(); ipAddr
.hasMoreElements();) {
InetAddress inetAddress = ipAddr.nextElement();
return inetAddress.getHostAddress();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}