Android 客户端和 web服务器通信

本篇简单介绍Android客户端和web服务器使用socket进行通讯,向客户端发送文件的demo

socket

套接字使用TCP提供了两台计算机之间的通信机制。客户端创建一个套接字,并尝试连接服务端的嵌套字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

java.net.Socket类代表一个套接字,并且 java.net.ServerSocket类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送

服务端

为了实现向客户端发送文件,我们基于前面的jFinal 文件上传 来完成。


public class ServerUtils {

	private volatile static ServerUtils serverInstance;

	private static ServerSocket serverSocket;

	private ServerUtils() {
	}

	public static ServerUtils getServerInstance() {

		if (serverInstance == null) {
			synchronized (ServerUtils.class) {
				if (serverInstance == null) {
					serverInstance = new ServerUtils();
				}
			}
		}
		return serverInstance;
	}

	public void init(int port) {
		try {
			if (serverSocket == null) {
				serverSocket = new ServerSocket(port);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static ServerSocket getServerSocket() {
		return serverSocket;
	}

     /**
       *  发送文件线程
       **/
	public static class SendThread implements Runnable {
		static Socket socket;
		File file;
		static FileInputStream fis;
		static DataOutputStream dos;

		public SendThread(File file) {
			this.file = file;
		}

		@Override
		public void run() {

			try {
                 // 监听并接受到此嵌套字的连接 (该方法会阻塞等待,直到客户端连到服务端的指定端口)
				socket = getServerSocket().accept();
              
				// 上传的模型文件
				File getFile = file;
				fis = new FileInputStream(getFile);

				// 获取嵌套字的输出流
				dos = new DataOutputStream(socket.getOutputStream());
                  // 嵌套字的输入流
				//dis = new DataInputStream(socket.getInputStream());
              
				// 模型名称和大小
				dos.writeUTF(getFile.getName());
				dos.flush();
				dos.writeLong(getFile.length());
				dos.flush();

				byte[] bytes = new byte[1024];
				int length = 0;

				while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
					dos.write(bytes, 0, length);
					dos.flush();
				}

			} catch (IOException e) {
				e.printStackTrace();

			} finally {
				if (fis != null)
					fis.close();
				if (dos != null)
					dos.close();
				if (socket != null) {
					socket.close();
				}
			}

		}
	}

}

UploadController:



private static ThreadPoolExecutor threadPool;
private static SendThread sendThread;

// jfinal 获取 Web Uploader 上传的文件 
final File getFile = getFile().getFile();

threadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

// 初始化 ServerSocket
ServerUtils.getServerInstance().init(port);

sendThread = new SendThread(getFile);

// 创建一个线程 向客户端发送文件
threadPool.submit(sendThread);


客户端



   final Socket socket = new Socket();
        final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        threadPool.submit(new Runnable() {
            @Override
            public void run() {

                try {
                    socket.connect(new InetSocketAddress("192.168.0.100", 6001));
                    // 获取嵌套字输入流
                    final DataInputStream dis = new DataInputStream(socket.getInputStream());

                    // 获取文件名
                    String fileName = dis.readUTF();
                    // 获取文件大小
                    final long fileLength = dis.readLong();
                    File file = new File(App.RECEIVE_PATH + File.separatorChar + fileName);
                    final FileOutputStream fos = new FileOutputStream(file);

                    threadPool.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int length = 0;
                                byte[] bytes = new byte[1024];

                                while ((length = dis.read(bytes, 0, bytes.length)) != -1) {
                                    fos.write(bytes, 0, length);
                                    fos.flush();

                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                    });

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

应用场景

这里只是一个最基本的上传demo,每次用户web页面上传文件,服务端都会开启一个线程来发送文件。客户端连接并接收上传的文件,文件发送结束 关闭客户端。

关于socket 优化以及深入了解

可参见Java Socket编程基础及深入讲解

NIO

上述的demo是基于阻塞式api的,当程序输入或输出操作后,在操作返回前会一直阻塞线程。服务器需要为每一个客户端提供一个线程进行处理。当有大量的客户端的时候,性能比较底下。

JDK1.4 开始提供了 NIO(new io)开发高性能的服务器,可以使服务器使用一个或有限几个线程同时处理所有客户端

IO和NIO的区别

面向流和面向缓冲区

  • io面向流,从上面的demo可以看出所有输入输出都是通过流来完成。每次从流中读出数据它们没有被缓存在任何地方,直到被读完。需要需要前后移动数据需要先将数据缓存到一个缓冲区。

  • nio面向缓冲区,面向块。使用channel(通道)模拟传统输入输出流,所有从channel读取的数据或是发送的数据都需要先放到buffer(缓冲)中。程序不能直接对channel直接操作。使数据操作更加灵活

阻塞式和非阻塞式

  • 阻塞式如上面所述,需要阻塞直到数据完全读取写入。

  • 非阻塞式,一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞io的空闲时间用于在其它通道上执行io操作,所以一个单独的线程现在可以管理多个输入和输出通道。

Selector

nio通过Selector(选择器)来实现一个线程对多个channel的管理。

服务器上的所有channel都需要向Selector注册,而Selector负责监视这些channel的状态。当有任意个channel有可用的io操作时,Selectorselect()方法会返回一个大于0的值,表示当前有多少channel有可用的io操作。可以通过selectedKeys()获取channel集合。所以只需要一个线程不断调用select()方法即可。

Tips:无可用的channel时,调用select()方法的线程会被阻塞。

Netty

Netty 是业界流行的 NIO 框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都说首屈一指的,也已经得到了成百上千商用项目的验证。

优点有很多..... 反正就是很好用 很耐用,而且开发门槛低。.

Netty Api

posted @ 2018-09-17 15:46  可爱的黑精灵  阅读(2172)  评论(0编辑  收藏  举报