通过socket传送客户端屏幕图片,但接收端好像始终有数据接收,而接收的数据量会大于送出的图片大小,比较原图片与收到的文件十六进制数据完全不同,本人小白,请各位给我点指导,谢部分代码如下:客户端public class Client
{ public static void main( String[] args )
{ try
{
Socket client = new Socket( "localhost", 5888 ); OutputStream sender = client.getOutputStream(); // 获取到屏幕图片数据
BufferedImage img = Screen.SnapShot();
// 生成文件 这里是可以生成图片的,大小大概为88.7kb
ImageIO.write(img, "png", new File("screen.png")); // 转为比特后发送
sender.write( Client.getCompressedImage( img ) ); client.close();
}
catch ( IOException e )
{
e.printStackTrace();
} } /**
* 图片转换为byte
* @param image
* @return
*/
public static byte[] getCompressedImage( BufferedImage image )
{
byte[] imageData = null; try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
imageData = baos.toByteArray();
}
catch ( IOException ex )
{
imageData = null;
} return imageData;
}
}
服务端public class Server
{ /**
* @param args the command line arguments
*/
public static void main( String[] args )
{
try
{
ServerSocket server = new ServerSocket( 5888 );
Socket client = server.accept(); InputStream inputStream = client.getInputStream();
Scanner sc = new Scanner( inputStream ); String result = "";
int counter = 0;
while( sc.hasNextLine() )
{
result += sc.nextLine();
// 直接限制到400行就停止了,不加的话会一直循环…
if ( counter++ == 400 ) break;
}
byte[] resultToBytes = result.getBytes(); FileOutputStream out = new FileOutputStream( "test.png" );
// 生成的文件大于88.7kb 而且16进制与原图片的不同
out.write( resultToBytes );
out.close(); client.close();
}
catch ( IOException e )
{
e.printStackTrace();
}
}
}
{ public static void main( String[] args )
{ try
{
Socket client = new Socket( "localhost", 5888 ); OutputStream sender = client.getOutputStream(); // 获取到屏幕图片数据
BufferedImage img = Screen.SnapShot();
// 生成文件 这里是可以生成图片的,大小大概为88.7kb
ImageIO.write(img, "png", new File("screen.png")); // 转为比特后发送
sender.write( Client.getCompressedImage( img ) ); client.close();
}
catch ( IOException e )
{
e.printStackTrace();
} } /**
* 图片转换为byte
* @param image
* @return
*/
public static byte[] getCompressedImage( BufferedImage image )
{
byte[] imageData = null; try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
imageData = baos.toByteArray();
}
catch ( IOException ex )
{
imageData = null;
} return imageData;
}
}
服务端public class Server
{ /**
* @param args the command line arguments
*/
public static void main( String[] args )
{
try
{
ServerSocket server = new ServerSocket( 5888 );
Socket client = server.accept(); InputStream inputStream = client.getInputStream();
Scanner sc = new Scanner( inputStream ); String result = "";
int counter = 0;
while( sc.hasNextLine() )
{
result += sc.nextLine();
// 直接限制到400行就停止了,不加的话会一直循环…
if ( counter++ == 400 ) break;
}
byte[] resultToBytes = result.getBytes(); FileOutputStream out = new FileOutputStream( "test.png" );
// 生成的文件大于88.7kb 而且16进制与原图片的不同
out.write( resultToBytes );
out.close(); client.close();
}
catch ( IOException e )
{
e.printStackTrace();
}
}
}
assert source != null : "source should not be null";
assert pattern != null : "pattern should not be null";
this.source = source;
delimPattern = pattern;
buf = CharBuffer.allocate(BUFFER_SIZE);
buf.limit(0);
matcher = delimPattern.matcher(buf);
matcher.useTransparentBounds(true);
matcher.useAnchoringBounds(false);
useLocale(Locale.getDefault(Locale.Category.FORMAT));
}
现在很傻瓜地能实现了功能(我不知道这样实现是否合理)但是这种传送方式我自己看起来都觉得有点白痴,是否应该使用线程传输?而且这样接收包数据会有错误,而我目前又有了新问题:随着屏幕比例的大小延迟更明显
不时会出现一些异常情况,比如包的数量莫名奇妙的变大为25,包大小变了负值,不知道将byte[]转为int那个方法是否有错?
还有程序占用了较多的内存这些虽然不与这个问题有关,但希望能再给我点提示,谢谢目前的部分代码服务端public class Server
{ /**
* @param args the command line arguments
*/
public static void main( String[] args ) throws InterruptedException
{
ImageFrame frame = new ImageFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setVisible( true ); // 无限制
boolean noLimit = true;
int totalImgNum = 80;
try
{
ServerSocket server = new ServerSocket( 5888 );
//server.bind( new InetSocketAddress( "200.200.200.117", 5888 ) );
Socket client = server.accept(); InputStream inputStream = client.getInputStream(); int counter = 0; while ( true )
{
if ( counter == totalImgNum && !noLimit )
{
System.out.println( "stop" );
break;
} // 接收开始符
byte[] newTagByte = new byte[5];
inputStream.read( newTagByte );
String tag = new String( newTagByte, 0, newTagByte.length );
System.out.println( tag );
while ( tag.equals( "start" ) )
{
++counter; // 图片数据总大小
int imgSize;
byte[] imgSizeBytes = new byte[4];
inputStream.read( imgSizeBytes );
imgSize = Server.bytesToInt( imgSizeBytes );
System.out.println( imgSize ); // 总包数
byte[] packageBytes = new byte[4];
inputStream.read( packageBytes );
// 竟然会有25的
int packageNum = Server.bytesToInt( packageBytes );
System.out.println( packageNum ); // 每包每包地读取数据
// 为数据开辟全部空间
byte[] result = new byte[imgSize];
// 总读取的字节数
int readByte = 0;
for ( int i = 1; i <= packageNum; i++ )
{
// 获取每包的大小
byte[] packageSizeByte = new byte[4];
inputStream.read( packageSizeByte );
int packageSize = Server.bytesToInt( packageSizeByte );
System.out.println( packageSize );
Thread.sleep( 90 ); // 开辟包 有时候会出现负值
byte[] tmp = new byte[packageSize];
inputStream.read( tmp ); // 装载到result中
System.arraycopy( tmp, 0, result, readByte, packageSize );
readByte += packageSize;
}
System.out.println( result.length );
// 生成文件
String imgName = "test";
File img = new File( imgName );
img.createNewFile();
// 写入文件
FileOutputStream out = new FileOutputStream( imgName );
out.write( result );
out.close(); // 显示
image = ImageIO.read( img );
frame.updateImage(); // 删除图片
img.delete(); // 接收结束符
byte[] endTagByte = new byte[3];
inputStream.read( endTagByte );
tag = new String( endTagByte, 0, endTagByte.length );
System.out.println( tag );
}
} client.close();
}
catch ( IOException e )
{
e.printStackTrace();
}
} public static int bytesToInt( byte[] bytes )
{
int num = bytes[0] & 0xFF;
num |= ((bytes[1] << 8) & 0xFF00);
num |= ((bytes[2] << 16) & 0xFF0000);
num |= ((bytes[3] << 24) & 0xFF000000);
return num;
}
public static Image image;
}class ImageFrame extends JFrame
{ private ImagePanel _panel;
static final int DEFAULT_WIDTH = 1024;
static final int DEFAULT_HEIGHT = 768; public ImageFrame()
{
setTitle( "test" );
setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT ); _panel = new ImagePanel();
add( _panel );
} public void updateImage()
{
_panel.refresh();
_panel.updateUI();
}
}class ImagePanel extends JPanel
{ private Image image; public ImagePanel()
{
image = Server.image;
} public void refresh()
{
image = Server.image;
} public void paintComponent( Graphics g )
{
super.paintComponent( g );
g.drawImage( image, 0, 0, this );
}
}
客户端public class Client
{ public static void main( String[] args ) throws InterruptedException
{
// 无限制
boolean noLimit = true;
int totalImgNum = 80;
try
{
Socket client = new Socket( "200.200.200.117", 5888 ); OutputStream sender = client.getOutputStream(); int counter = 0;
while ( true )
{
if ( counter++ == totalImgNum && !noLimit )
{
break;
} // 发送开始符
sender.write( "start".getBytes() );
//Thread.sleep( 5 ); // 获取到屏幕图片数据
BufferedImage img = Screen.SnapShot(); // 生成文件 这里是可以生成图片的,大小大概为88.7kb
File imgFile = new File( "screen.png" );
imgFile.createNewFile();
ImageIO.write( img, "png", imgFile );
byte[] imgToBytes = Client.getCompressedImage( img ); // 发送总大小
int imgSize = imgToBytes.length;
System.out.println( imgSize );
sender.write( Client.intToByte( imgSize ) );
//Thread.sleep( 10 ); // 拆包发送 // 每段字节数
int packageSize = 51200;
// 总包数
int packageNum = (imgSize % packageSize != 0) ? imgSize / packageSize + 1 : imgSize / packageSize;
// 发送总包数
sender.write( Client.intToByte( packageNum ) );
//Thread.sleep( 10 ); int pos = 0;
for ( int i = 1; i <= packageNum; i++ )
{
// 每包大小
int tmpSize = (i * packageSize > imgSize) ? imgSize - (i - 1) * packageSize : packageSize; // 发送每包的大小
sender.write( Client.intToByte( tmpSize ) );
//Thread.sleep( 10 ); // 装载数据并发送
byte[] tmp = new byte[tmpSize];
System.arraycopy( imgToBytes, pos, tmp, 0, tmpSize );
pos += tmpSize;
sender.write( tmp );
//Thread.sleep( 10 );
} // 发送结束符
//Thread.sleep( 30 );
sender.write( "end".getBytes() );
} client.close();
}
catch ( IOException e )
{
e.printStackTrace();
} } public static byte[] intToByte( int i )
{
byte[] bt = new byte[4];
bt[0] = (byte) (0xff & i);
bt[1] = (byte) ((0xff00 & i) >> 8);
bt[2] = (byte) ((0xff0000 & i) >> 16);
bt[3] = (byte) ((0xff000000 & i) >> 24);
return bt;
} /**
* 图片转换为byte
* @param image
* @return
*/
public static byte[] getCompressedImage( BufferedImage image )
{
byte[] imageData = null; try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
imageData = baos.toByteArray();
}
catch ( IOException ex )
{
imageData = null;
} return imageData;
}
}
sorry,我没看明白什么意思…但也谢谢
——正常,屏幕越大,压缩数据量越多,耗费内存越高
——比较值得改进的问题是,你的Client端程序做了两次:png压缩,而png压缩是压缩率较低且开销较高的压缩算法;
——建议先得到byte[] imgToBytes = Client.getCompressedImage( img );然后直接把这个字节数组写入文件就好了。
不时会出现一些异常情况,比如包的数量莫名奇妙的变大为25,包大小变了负值
——没有认真调试你的程序,不好说
——但我认为没必要拆包,直接整个byte数组扔给 OutputStream sender 就好了,拆包增加复杂度,浪费内存,浪费copy时间,而且关键是没看到带来了啥好处
不知道将byte[]转为int那个方法是否有错?
——除了最后那个以外,没啥问题:
byte[] targets = new byte[4];
targets[0] = (byte) (res & 0xff);// 最低位
targets[1] = (byte) ((res >> 8) & 0xff);// 次低位
targets[2] = (byte) ((res >> 16) & 0xff);// 次高位
targets[3] = (byte) (res >>> 24);// 最高位,无符号右移。
——但逻辑上来说,你原始值只要不是负数就应该不存在符号位问题
还有程序占用了较多的内存
——很正常,自己算算就知道了:
1024宽×768高×32位色彩 = 3MB
1920宽×768高×32位色彩 = 8.16MB
这都是实打实的内存开销。
非常感谢你的回复!是啊,客户端生成了文件然后读取再送是会减慢了速度,确实没想到png那里也会消耗太多,我再根据你说的与自己的思路再加以优化一下看看,再谢一个
Mice 我调整了服务端接收的数据不写入tmp文件了也不使用png了,响应速度快了很多,但是updateUI出现闪烁,查了一些关于双缓存的资料但没有头绪,试过手动设置this.setDoubleBuffered( true );也没用,是不是我的main使用了while的原因
其实也想跟你说去掉super的调用,因为super会先清屏,然后再进行重绘。而你这里实际上每次都是完整显示全图,所以根本不需要清屏。以上
mice!现在就差延迟问题了!我确定问题所在了,在压缩那里,如果能将压缩的用时再降低到50毫秒就可以很流畅了,有没有不经过ImageIO去从BufferedImage获取byte的办法或资料能给我参考?// 这是我调试客户端输出每部分占用的时间
图片总大小 37361
打包与发送总用时 0 毫秒
截取图片 15 毫秒
压缩用时 172 毫秒// 压缩部分
/**
* 图片转换为byte
* @param image
* @return
*/
public static byte[] getCompressedImage( BufferedImage image )
{
byte[] imageData = null; try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "jpg", baos );
imageData = baos.toByteArray();
}
catch ( IOException ex )
{
imageData = null;
} return imageData;
}
让我惊奇的是打包与发送那里竟然是0毫秒
1、如果你用的是JNI来截屏的话,考虑截屏时就完成压缩;另外截屏时,可以考虑降低色彩数,用16位色(65536)即可;
2、使用JNI调用DirectX的库来对图片进行压缩;这个确实比较麻烦的;
3、启用另一线程执行压缩,不过这个也只能节约掉“截取图片 15 毫秒”;
4、不压缩或使用压缩比更小速度更快的算法,如果你是在内网的话。
import com.sun.image.codec.jpeg.*; public static byte[] getCompressedImageAWT(BufferedImage image) {
byte[] imageData = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos);
encoder.encode(image);
imageData = baos.toByteArray();
} catch (IOException ex) {
ex.printStackTrace();
}
return imageData;
}
默认情况下会编译不通过:
Access restriction: The type JPEGImageEncoder is not accessible due to restriction on required library需要修改:
Eclipse 默认把这些受访问限制的API设成了ERROR。只要把Windows->Preferences->Java->Complicer-> Errors/Warnings里面的Deprecated and restricted API中的Forbidden references(access rules)选为Warning就可以编译通过。
import java.awt.Image;
import java.awt.Toolkit;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.net.Socket;
import java.util.Scanner;
import java.util.Timer;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;public class Server
{
/**
* @param args the command line arguments
*/
public static void main( String[] args ) throws InterruptedException
{
ImageFrame frame = new ImageFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setVisible( true ); // 无限制
boolean noLimit = true;
int totalImgNum = 6;
try
{
ServerSocket server = new ServerSocket( 5888 );
Socket client = server.accept(); InputStream inputStream = client.getInputStream(); int counter = 0;
long timeCounter = 0;
int imageCounter = 0;
while ( true )
{
if ( counter == totalImgNum && !noLimit )
{
System.out.println( "stop" );
break;
} // 接收开始符
byte[] newTagByte = new byte[5];
inputStream.read( newTagByte );
String tag = new String( newTagByte, 0, newTagByte.length );
System.out.println( "图片开始接收" );
while ( tag.equals( "start" ) )
{
// 开始接收时间
long startMillis = System.currentTimeMillis();
System.out.println( "开始接收" ); ++counter; // 图片数据总大小
int imgSize;
byte[] imgSizeBytes = new byte[4];
inputStream.read( imgSizeBytes );
imgSize = Server.bytesToInt( imgSizeBytes ); // 总包数
byte[] packageBytes = new byte[4];
inputStream.read( packageBytes );
// 竟然会有25的
int packageNum = Server.bytesToInt( packageBytes );
//System.out.println( packageNum ); // 每包每包地读取数据
// 为数据开辟全部空间
byte[] result = new byte[imgSize];
// 总读取的字节数
int readByte = 0;
for ( int i = 1; i <= packageNum; i++ )
{
long packageStartMillis = System.currentTimeMillis();
// 获取每包的大小
byte[] packageSizeByte = new byte[4];
inputStream.read( packageSizeByte );
int packageSize = Server.bytesToInt( packageSizeByte );
//System.out.println( packageSize );
Thread.sleep( 25 ); // 开辟包 有时候会出现负值
byte[] tmp = new byte[packageSize];
inputStream.read( tmp ); // 装载到result中
System.arraycopy( tmp, 0, result, readByte, packageSize );
readByte += packageSize; long packageEndMillis = System.currentTimeMillis();
System.out.println( "第" + i + "个包接收使用了 " + ( packageEndMillis - packageStartMillis ) + "毫秒" );
} long endMillis = System.currentTimeMillis();
System.out.println( "总数据大小 " + result.length );
System.out.println( "接收完毕 用了" + ( endMillis - startMillis ) + "毫秒" );
timeCounter += ( endMillis - startMillis ); // 接收结束符
byte[] endTagByte = new byte[3];
inputStream.read( endTagByte );
tag = new String( endTagByte, 0, endTagByte.length );
imageCounter++;
System.out.println( "图片接收完毕" ); // 如果超过1秒
if ( timeCounter >= 1000 )
{
System.out.println( "1秒内接收了 " + imageCounter + " 张图片" );
imageCounter = 0;
timeCounter = 0;
} // 显示
image = Toolkit.getDefaultToolkit().createImage( result, 0, result.length );
frame.updateImage();
}
} client.close();
}
catch ( IOException e )
{
e.printStackTrace();
}
} public static int bytesToInt( byte[] bytes )
{
int num = bytes[0] & 0xFF;
num |= ((bytes[1] << 8) & 0xFF00);
num |= ((bytes[2] << 16) & 0xFF0000);
num |= ((bytes[3] << 24) & 0xFF000000);
return num;
}
public static Image image;
}class ImageFrame extends JFrame
{ private ImagePanel _panel;
static final int DEFAULT_WIDTH = 1024;
static final int DEFAULT_HEIGHT = 768; public ImageFrame()
{
this.setTitle( "test" );
this.setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT ); _panel = new ImagePanel();
this.add( _panel );
} public void updateImage() throws InterruptedException
{
_panel.refresh();
_panel.repaint();
}
}class ImagePanel extends JPanel
{ private Image image; public ImagePanel()
{
this.setDoubleBuffered( true );
image = Server.image;
} public void refresh()
{
image = Server.image;
} public void paintComponent( Graphics g )
{
//super.paintComponent( g );
g.drawImage( image, 0, 0, this );
}
}
客户端package client;import java.awt.Robot;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.*;
import java.net.*;
import java.net.Socket;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;public class Client
{ public static void main( String[] args ) throws InterruptedException
{
// 无限制
boolean noLimit = true;
int totalImgNum = 80;
try
{
Socket client = new Socket( "200.200.200.117", 5888 );// 请根据自己实际更改IP OutputStream sender = client.getOutputStream(); int counter = 0;
while ( true )
{
if ( counter++ == totalImgNum && !noLimit )
{
break;
} long compressedStartTime = System.currentTimeMillis();
// 获取到屏幕图片数据
BufferedImage img = Screen.SnapShot();
System.out.println( "截取图片 " + (System.currentTimeMillis() - compressedStartTime) + " 毫秒" ); // 压缩图片数据
compressedStartTime = System.currentTimeMillis();
byte[] imgToBytes = Client.getCompressedImage( img );
System.out.println( "压缩用时 " + (System.currentTimeMillis() - compressedStartTime) + " 毫秒" ); // 发送开始符
sender.write( "start".getBytes() ); // 发送总大小
int imgSize = imgToBytes.length;
System.out.println( "图片总大小 " + imgSize );
sender.write( Client.intToByte( imgSize ) ); // 拆包发送
// 打包开始时间
long unpackageStartMillis = System.currentTimeMillis(); // 每段字节数
int packageSize = 51200;
// 总包数
int packageNum = (imgSize % packageSize != 0) ? imgSize / packageSize + 1 : imgSize / packageSize;
// 发送总包数
sender.write( Client.intToByte( packageNum ) ); int pos = 0;
for ( int i = 1; i <= packageNum; i++ )
{
// 每包大小
int tmpSize = (i * packageSize > imgSize) ? imgSize - (i - 1) * packageSize : packageSize; // 发送每包的大小
sender.write( Client.intToByte( tmpSize ) ); // 装载数据并发送
byte[] tmp = new byte[tmpSize];
System.arraycopy( imgToBytes, pos, tmp, 0, tmpSize );
pos += tmpSize;
sender.write( tmp );
} // 打包与发送完毕时间
long unpackageEndMillis = System.currentTimeMillis();
System.out.println( "打包与发送总用时 " + (unpackageEndMillis - unpackageStartMillis) + " 毫秒" ); // 发送结束符
sender.write( "end".getBytes() );
} client.close();
}
catch ( IOException e )
{
e.printStackTrace();
} } public static byte[] intToByte( int i )
{
byte[] bt = new byte[4];
bt[0] = (byte) (0xff & i);
bt[1] = (byte) ((0xff00 & i) >> 8);
bt[2] = (byte) ((0xff0000 & i) >> 16);
bt[3] = (byte) ((0xff000000 & i) >> 24);
return bt;
} /**
* 图片转换为byte
* @param image
* @return
*/
public static byte[] getCompressedImage( BufferedImage image )
{
byte[] imageData = null; try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
imageData = baos.toByteArray();
}
catch ( IOException ex )
{
imageData = null;
} return imageData;
} public static byte[] imageByteData( BufferedImage image )
{
WritableRaster raster = image.getRaster();
DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
return buffer.getData();
}
}class Screen
{
final static int WIDTH = 1024;
final static int HEIGHT = 250; /**
* 屏幕抓取
*/
public static BufferedImage SnapShot()
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
screenSize.setSize( WIDTH, HEIGHT );
Rectangle screenRectangle = new Rectangle( screenSize );
try
{
Robot robot = new Robot();
BufferedImage image = robot.createScreenCapture( screenRectangle );
return image;
}
catch ( AWTException e )
{
e.printStackTrace();
return null;
}
}
}
还是谢谢mice
A做压缩的时候,B就去截图;
A压缩完毕后,B基本截图完毕;
A去发送数据,B开始压缩;
A接着去截图,B继续压缩;B压缩完毕后,A基本截图完毕;
B去发送数据,A开始压缩;
B接着去截图,A继续压缩;A压缩完毕后,B基本截图完毕;
A去发送数据,B开始压缩;
A接着去截图,B继续压缩;......可以看到,在双核下的话,你的每次迭代周期都是 35毫秒,截图和发送的15毫秒,可以在迭代中同步完成。当然你也可以用3线程甚至4线程,从而企图让刷新周期进一步压缩,但如果不是多核CPU或多线程的话,就没有实际效果了。
好像行得通!我try try看