Android与服务器的通讯方式主要有两种:html
二者的最大差别在于:java
Http
链接使用的是“请求-响应方式”,即在请求时创建链接通道,当客户端向服务器发送请求后,服务端才能向客户端返回数据。android
Socket
通讯则是在双方创建链接后,能够直接进行数据的传输,在链接时可实现信息的主动推送,而不须要每次由客户端向服务器发送请求。数组
那么,什么是socket?bash
socket又称套接字,在程序内部提供了与外界通讯的端口,即端口通讯。服务器
经过创建socket
链接,可为通讯双方的数据传输提供通道。socket的主要特色有数据丢失率低,使用简单且易于移植。markdown
socket
是一种抽象层,应用程序经过它来发送和接受数据,使用Socket能够将应用程序添加到网络中,与处于同一网络中的其余应用程序进行通讯。网络
简单来讲,Socket提供了程序内部与外界通讯的端口并为通讯双方提供数据传输通道。app
根据不一样的底层协议,Socket的实现是多样化的。在这主要介绍TCP/IP协议簇当中主要的Socket类型为流套接字(streamsocket
)和数据报套接字(datagramsocket
)。socket
流套接字将TCP
做为其端对端协议,提供了一个可信赖的字节流服务。
数据报嵌套字使用UDP
协议,提供数据打包发送数据。
服务端首先声明一个ServerSocket
对象而且指定端口号,而后调用Serversocket的accept()
方法接受客户端的数据。
accept()方法在没有数据进行接受时处于堵塞状态。(Socket socket = serversocket.accept()
),一旦接受数据,经过inputstream
读取接受的数据。
客户端建立一个Socket对象,执行服务器端的ip地址和端口号(Socket socket = new Socket("172.168.10.108", 8080);
),经过inputstream
读取数据,获取服务器发出的数据(OutputStream outputstream = socket.getOutputStream();
),最后将要发送的数据写入到outputstream
便可进行TCP协议的socket数据传输。
服务器端首先建立一个DatagramSocket
对象,而且指定监听端口。接下来建立一个空的DatagramSocket对象用于接收数据(byte data[] = new byte[1024]; DatagramSocket packet = new DatagramSocket(data, data.length);
),使用DatagramSocket
的receive()
方法接受客户端发送的数据,receive()
与serversocket
的accept()
方法相似,在没有数据进行接受时处于堵塞状态。
客户端也建立个DatagramSocket
对象,而且指定监听的端口。接下来建立一个InetAddress
对象,这个对象相似于一个网络的发送地址(InetAddress serveraddress = InetAddress.getByName("172.168.1.120")
)。定义要发送的一个字符串,建立一个DatagramPacket
对象,并指定要将该数据包发送到网络对应的那个地址和端口号,最后使用DatagramSocket的对象的send()
发送数据。
(String str = "hello"; byte data[] = str.getByte(); DatagramPacket packet = new DatagramPacket(data, data.length, serveraddress, 4567); socket.send(packet);
)
protected void connectServerWithTCPSocket() { Socket socket; try {// 建立一个Socket对象,并指定服务端的IP及端口号 socket = new Socket("192.168.1.32", 1989); // 建立一个InputStream用户读取要发送的文件。 InputStream inputStream = new FileInputStream("e://a.txt"); // 获取Socket的OutputStream对象用于发送数据。 OutputStream outputStream = socket.getOutputStream(); // 建立一个byte类型的buffer字节数组,用于存放读取的本地文件 byte buffer[] = new byte[4 * 1024]; int temp = 0; // 循环读取文件 while ((temp = inputStream.read(buffer)) != -1) { // 把数据写入到OuputStream对象中 outputStream.write(buffer, 0, temp); } // 发送读取的数据到服务端 outputStream.flush(); /** 或建立一个报文,使用BufferedWriter写入,看你的需求 **/ // String socketData = "[2143213;21343fjks;213]"; // BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( // socket.getOutputStream())); // writer.write(socketData.replace("\n", " ") + "\n"); // writer.flush(); /************************************************/ } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 复制代码
public void ServerReceviedByTcp() { // 声明一个ServerSocket对象 ServerSocket serverSocket = null; try { // 建立一个ServerSocket对象,并让这个Socket在1989端口监听 serverSocket = new ServerSocket(1989); // 调用ServerSocket的accept()方法,接受客户端所发送的请求, // 若是客户端没有发送数据,那么该线程就停滞不继续 Socket socket = serverSocket.accept(); // 从Socket当中获得InputStream对象 InputStream inputStream = socket.getInputStream(); byte buffer[] = new byte[1024 * 4]; int temp = 0; // 从InputStream当中读取客户端所发送的数据 while ((temp = inputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, temp)); } serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } 复制代码
protected void connectServerWithUDPSocket() { DatagramSocket socket; try { //建立DatagramSocket对象并指定一个端口号,注意,若是客户端须要接收服务器的返回数据, //还须要使用这个端口号来receive,因此必定要记住 socket = new DatagramSocket(1985); //使用InetAddress(Inet4Address).getByName把IP地址转换为网络地址 InetAddress serverAddress = InetAddress.getByName("192.168.1.32"); //Inet4Address serverAddress = (Inet4Address) Inet4Address.getByName("192.168.1.32"); String str = "[2143213;21343fjks;213]";//设置要发送的报文 byte data[] = str.getBytes();//把字符串str字符串转换为字节数组 //建立一个DatagramPacket对象,用于发送数据。 //参数一:要发送的数据 参数二:数据的长度 //参数三:服务端的网络地址 参数四:服务器端端口号 DatagramPacket packet = new DatagramPacket(data, data.length ,serverAddress ,10025); socket.send(packet);//把数据发送到服务端。 } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 复制代码
public void ReceiveServerSocketData() { DatagramSocket socket; try { //实例化的端口号要和发送时的socket一致,不然收不到data socket = new DatagramSocket(1985); byte data[] = new byte[4 * 1024]; //参数一:要接受的data 参数二:data的长度 DatagramPacket packet = new DatagramPacket(data, data.length); socket.receive(packet); //把接收到的data转换为String字符串 String result = new String(packet.getData(), packet.getOffset(), packet.getLength()); socket.close();//不使用了记得要关闭 System.out.println("the number of reveived Socket is :" + flag + "udpData:" + result); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 复制代码
public void ServerReceviedByUdp(){ //建立一个DatagramSocket对象,并指定监听端口。(UDP使用DatagramSocket) DatagramSocket socket; try { socket = new DatagramSocket(10025); //建立一个byte类型的数组,用于存放接收到得数据 byte data[] = new byte[4*1024]; //建立一个DatagramPacket对象,并指定DatagramPacket对象的大小 DatagramPacket packet = new DatagramPacket(data,data.length); //读取接收到得数据 socket.receive(packet); //把客户端发送的数据转换为字符串。 //使用三个参数的String方法。参数一:数据包 参数二:起始位置 参数三:数据包长 String result = new String(packet.getData(),packet.getOffset() ,packet.getLength()); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 复制代码
使用UDP方式,android端和服务器端接收能够看出,其实android端和服务器端的发送和接受截然不同,只要端口号正确,相互通讯就没有问题,TCP
使用的是流的方式发送,UDP
是以包的形式发送。
查看这部分代码主要是为了查看accept()
底层源码实现阻塞等待的原理。
public Socket accept() throws IOException { if (isClosed()) throw new SocketException("Socket is closed"); if (!isBound()) throw new SocketException("Socket is not bound yet"); Socket s = new Socket((SocketImpl) null); implAccept(s); return s; } 复制代码
在accept()
方法中调用implAccept()
方法
protected final void implAccept(Socket s) throws IOException { SocketImpl si = null; try { if (s.impl == null) s.setImpl(); else { s.impl.reset(); } si = s.impl; s.impl = null; si.address = new InetAddress(); si.fd = new FileDescriptor(); //核心代码 getImpl().accept(si); ...... } catch (IOException e) { ...... } s.impl = si; s.postAccept(); } 复制代码
然后调用PlainSocketImpl
类中的accept()
方法
protected synchronized void accept(SocketImpl s) throws IOException { if (s instanceof PlainSocketImpl) { // pass in the real impl not the wrapper. SocketImpl delegate = ((PlainSocketImpl)s).impl; delegate.address = new InetAddress(); delegate.fd = new FileDescriptor(); // 对应代码 impl.accept(delegate); // set fd to delegate's fd to be compatible with older releases s.fd = delegate.fd; } else { // 对应代码 impl.accept(s); } } 复制代码
此处再调用抽象类abstracPlainSocketImpl
类中的accept()
方法
protected void accept(SocketImpl s) throws IOException {
acquireFD();
try {
socketAccept(s);
} finally {
releaseFD();
}
}
复制代码
其中acquireFD()
方法的代码以下:
/* * "Acquires" and returns the FileDescriptor for this impl * * A corresponding releaseFD is required to "release" the * FileDescriptor. */ //“获取”并返回这个impl的文件描述符须要一个相应的releaseFD来“释放”文件描述符。 FileDescriptor acquireFD() { synchronized (fdLock) { fdUseCount++; return fd; } } 复制代码
然后再执行socketAccept()
方法
void socketAccept(SocketImpl s) throws IOException { int nativefd = checkAndReturnNativeFD(); if (s == null) throw new NullPointerException("socket is null"); int newfd = -1; InetSocketAddress[] isaa = new InetSocketAddress[1]; //等待阻塞代码 if (timeout <= 0) { newfd = accept0(nativefd, isaa); } else { configureBlocking(nativefd, false); try { waitForNewConnection(nativefd, timeout); newfd = accept0(nativefd, isaa); if (newfd != -1) { configureBlocking(newfd, true); } } finally { configureBlocking(nativefd, true); } } /* Update (SocketImpl)s' fd '*/ fdAccess.set(s.fd, newfd); /* Update socketImpls remote port, address and localport */ InetSocketAddress isa = isaa[0]; s.port = isa.getPort(); s.address = isa.getAddress(); s.localport = localport; } 复制代码
该部分即为无请求时的阻塞代码块,逐一查看accetp0()
方法、configureBlocking()
方法等发现这些代码使用native代码实现,提升效率。
于是具体没有找到accept()方法的阻塞机理
我的感受,configureBlocking()方法时等待阻塞的调用的方法,而accept0()方法是响应请求的方法。
(该部分纯属我的猜想,多是错误的,于是但愿你们指教)
flush()
方法很简单,就是刷新此输出流并强制写出任何已缓冲的输出字节
/** * Flushes this output stream and forces any buffered output bytes * to be written out. The general contract of <code>flush</code> is * that calling it is an indication that, if any bytes previously * written have been buffered by the implementation of the output * stream, such bytes should immediately be written to their * intended destination. * <p> * If the intended destination of this stream is an abstraction provided by * the underlying operating system, for example a file, then flushing the * stream guarantees only that bytes previously written to the stream are * passed to the operating system for writing; it does not guarantee that * they are actually written to a physical device such as a disk drive. * <p> * The <code>flush</code> method of <code>OutputStream</code> does nothing. * * @exception IOException if an I/O error occurs. */ public void flush() throws IOException { } 复制代码
最后附上Java测试socket,查看其底层源码实现机制,由于一直尝试网络请求没成功,只能debug一步一步查看底层实现机制。
感兴趣能够本身尝试下,查看具体实现原理。
import java.io.*; import java.net.*; public class socketTest { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = setSocket(); if(socket == null) System.out.println("socket is null"); InputStream inputStream = new FileInputStream("d://haha.txt"); OutputStream outputStream = socket.getOutputStream(); byte buffer[] = new byte[4 * 1024]; int temp = 0; while((temp = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, temp); } outputStream.flush(); listener(); } public static Socket setSocket() throws IOException{ String ip = "127.0.0.5"; int port = 8080; Socket socket = new Socket(); //设置最长等待时间 socket.setSoTimeout(8000); //进行链接请求 socket.connect(new InetSocketAddress(ip, port)); return socket; } //服务端监听方法 public static void listener() throws IOException{ //正常状况下,此处的"9999"和客户端中的端口号port须要相同,才能完成网络请求, //由于测试不成功,可是又想查看实现原理,于是才这样作!!! ServerSocket server = new ServerSocket(9999); Socket socket = null; int i = 0; while(true){ i++; socket = server.accept();//这也是个阻塞的方法来的 System.out.println("有" + i + "个用户链接了服务器"); new Thread(new socketTest().new ServerDoThread(socket)).start(); } } class ServerDoThread implements Runnable { Socket socket; InputStream inputStream; public ServerDoThread(Socket socket) { this.socket = socket; try { this.inputStream = socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { byte buffer[] = new byte[1024 * 4]; int temp = 0; try { while((temp = inputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, temp)); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 复制代码