java同屏软件(优化手段:线程池 + 压缩)

一  同屏软件原理

客户端请求服务端,服务端截屏(如果需要录音,也可用第三方库调用录音设备,获取声音数据),转成字节数组,通过网络传输给客户端,客户端将原始的画面数据还原,展示在组件中(awt或swing组件都可以)。

服务端和客户端之间的多次传输、就可以将画面在可控的时间间隔内一张张的展示出来。形成一种屏幕"连续"展示效果。

具体而言可以有如下几种实现方式。

1  长连接

即客户端请求了服务端之后,从此不断开(除非异常需要重新发送连接请求之外),服务端保存客户端的连接(list.add(socket))。不释放(除非客户端异常需要移除无用连接,添加新连接之外)。

2  短连接。客户端和服务端一站式请求,即一次请求一次响应。此后断开连接。

两种方式各有利弊。

 
长连接

不需要每次都发送连接请求。避免了三次握手的额外开销。

①服务端必须使用容器来保存客户端连接。这个容器可以是数组、List、Map等。增加内存消耗。

②多客户端请求,意味着多线程。必须使用额外的代码保证多线程对连接的容器的安全访问。

③考虑到客户端有可能频繁断开连接,服务端必须增加额外的代码(可以是单独的管理线程)来剔除那些已经断开了的、或者已经发生了异常了的客户端连接。

短连接不会出现长连接的所有弊端。没有了长链接的优势。意味着每次都要发送连接请求服务端。客户端开销也比较大。

本项目应用场景是在公司内部搭建局域网(路由器 + 交换机)开会时使用。所以不需要传输声音。用了几个月,效果还可以,既不会出现卡顿。内存也还在本人可接受范围内。其实压力最大的是服务端。几个人的话,在200M左右。十几个人连接。高峰期在600-700M。吃内存大户。挺吓人。

二 代码实现

本案例使用短连接实现。

客户端、服务端、屏幕工具类、压缩工具类。一共四个文件

1 客户端代码   com.sharescreen.Client.java

package com.sharescreen;

import java.awt.Image; 
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.InputStream;
import java.net.Socket;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import com.utils.ScreenUtils;
 
public class Client {
	private static JLabel jl;
	private static String IP=null;
	private static int PORT=-1;
	private static JFrame jf;
	private static JPanel loginpanel;
	public static void main(String[] args) throws Exception{//先写再读  //先读再写
		initFrame();
		connect();
	}
	public static void connect() throws InterruptedException{
		 while(PORT ==-1|| IP ==null){
			 Thread.sleep(10);
		 }
		jf.remove(loginpanel);//移除登录框。
		while(true){
			Socket c = null;
			try {
				c = new Socket(IP,PORT);
				InputStream in = c.getInputStream();
				ZipInputStream zin = new ZipInputStream(in);
				zin.getNextEntry();
				Image img = ImageIO.read( zin );
				jl.setIcon(new ImageIcon(img));
				Thread.sleep(400);//客户端延时。可调。
			} catch (Exception e) {
				 e.printStackTrace();
			}  
		}
	}
	 
	public static void initFrame(){ 
		jf = new JFrame();
		jf.setUndecorated(true);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setBounds(new Rectangle(ScreenUtils.getScreensize()));
		JPanel jp = new JPanel();
			jl = new JLabel();
			jp.add(jl);
			jl.setBounds(new Rectangle(ScreenUtils.getScreensize()));
		jf.getContentPane().add(jp);
		
		loginpanel = new JPanel();
		jf.add(loginpanel,"North");
			loginpanel.add(new JLabel("IP"));
			JTextField ipinput = new JTextField(10);
			loginpanel.add(ipinput);
			loginpanel.add(new JLabel("端口"));
			JTextField portinput = new JTextField(10);
			loginpanel.add(portinput);
			JButton loginbtn = new JButton("登录");
			loginpanel.add(loginbtn);
		jf.getContentPane().add(loginpanel,"North");
		jf.setVisible(true);
		loginbtn.addMouseListener(new MouseAdapter() {
			   public void mouseClicked(MouseEvent e) {
				   String ip =  ipinput.getText();
				   String port = portinput.getText();
				   //未添加校验。
				   IP = ip;
				   PORT = Integer.parseInt( port );
			   }
		});
	}
}

2 服务端代码com.sharescreen.Server.java

package com.sharescreen;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import com.utils.ScreenUtils;
/**
 * 优化手段 : 加入线程池+压缩
 */
public class Server {
	public static void main(String[] args) throws Exception {
		ServerSocket s = new ServerSocket(8088);
		//创建一个带缓冲的线程池。
		//Executors.newCachedThreadPool();
		//通过比较发现,我的机子上。用固定线程池性能更好。
		ExecutorService pools = Executors.newFixedThreadPool(10);
		while (true) {
			Socket c = s.accept();// 接收客户端连接。
			System.out.println(c.getInetAddress().getHostAddress());
			pools.submit(new MyTask(c));//将任务加入线程池。
			c = null;
		}
	}
	private static class MyTask implements Runnable{
		private Socket c;
		public MyTask(Socket c) {
			this.c = c;
		}
		@Override
		public void run() {
			try {
				OutputStream out = c.getOutputStream();
				ZipOutputStream zout = new ZipOutputStream(out);
				zout.putNextEntry(new ZipEntry("test.jpg"));//指定入口。必须的。
				zout.setLevel(5);//设置压缩等级。0-9   
				BufferedImage buf = ScreenUtils.getFullScreen();
				ImageIO.write(buf, "jpg", zout);
				zout.closeEntry();
				c.shutdownOutput();
				//帮助垃圾回收期回收。
				out = null;
				zout = null;
				c = null;
				buf = null;
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

3 屏幕工具类  com.utils.ScreenUtils.java  

package com.utils;

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

public class ScreenUtils {
	private final static Dimension screensize=Toolkit.getDefaultToolkit().getScreenSize();
	private static  Robot robot = null;
	static{
		 try {
			robot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
		}
	}
	public static BufferedImage getFullScreen(){
		BufferedImage buf = robot.createScreenCapture(new Rectangle(screensize));
		return buf;
	}
	public static Dimension getScreensize() {
		return screensize;
	}
	
	 
}

4 压缩工具类 com.utils.ZipUtils.java

package com.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

 
public class ZipUtils {
	// 压缩
	public static byte[] zip(byte[] bs) throws IOException {
		if (bs == null || bs.length == 0) {
			return null;
		}
		try(	ByteArrayOutputStream out = new ByteArrayOutputStream();
				GZIPOutputStream gzip = new GZIPOutputStream(out); 
				
				){
			gzip.write(bs);
			gzip.close();
			return  out.toByteArray(); 
		}catch (Exception e) {
		}
		return null;
	}
	
	// 解压缩
	public static byte[] unzip(byte[] bs) throws IOException {
		if (bs == null || bs.length == 0) {
			return bs;
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ByteArrayInputStream in = new ByteArrayInputStream(bs);
		GZIPInputStream gunzip = new GZIPInputStream(in);
		byte[] readby = new byte[100*1024];
		int readnum;
		while (true) {
			readnum = gunzip.read(readby);
			if(readnum==-1)break;
			out.write(readby, 0, readnum);
		}
		gunzip.close();
		in.close();
		return out.toByteArray();
	}
}

 

转载于:https://my.oschina.net/lightled/blog/1817779

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值