UDP视频聊天室
一、简介
通过UDP传输实现文本聊天、视频通讯
实现这个功能需要三个类,一个负责绘制界面和图片、显示消息,一个负责将文本、图片转换为数据包并通过UDP发送,一个负责接收数据并还原成文本和图片
二、核心问题
1、利用Opencv打开摄像头获取图片
2、文本、图片转化为字节数组,利用字节数组的第一个位置进行图片和文本的标记
3、将字节数组打包成数据包,设置好目标IP及端口,并通过DatagramSocket发送
4、启用线程接收数据包,根据标志转换为文本或图片并显示
调用摄像头
导包:这是JavaCV 开发所有jar包
链接:https://pan.baidu.com/s/1JnusoGzZ-xcyN88-cHcZGQ
提取码:92xj
OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
grabber.start();
Java2DFrameConverter converter = new Java2DFrameConverter();
while (true) {
BufferedImage self_image = converter.getBufferedImage(grabber.grabFrame());
g.drawImage(self_image, 0, 0, 200, 200, null);//画笔g取自JPanel
}
UDP数据包及发送
public class Sender {
public DatagramSocket dSender;
public SocketAddress localAddr;
public SocketAddress destAdd;
private Sender() {
try {
// 1、创建本机地址端口对象(获取本机地址:InetAddress.getLocalHost().getHostAddress())
localAddr = new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), 12000);
// 2.创建发送的Socket对象
dSender = new DatagramSocket(localAddr);
// 3、创建对方地址端口对象( 测试暂用本机地址)
destAdd = new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), 13000);
} catch (Exception e) {
}
}
private static Sender sender = new Sender();
//此处纯粹练习一下单实例模型,主类中发送图片和发送文字是两个方法
//但是在一个类中不会创建多个实例,所以没必要使用单实例
public static Sender getInstance() {
return sender;
}
public void sendPackage(byte[] buffer) { //转换好的需要传输的字节数组
try {
// 4.创建要发送的数据包,指定内容,指定目标地址
DatagramPacket dp = new DatagramPacket(buffer, buffer.length,destAdd);
// 5、发送数据包
dSender.send(dp);
} catch (Exception e) {
}
}
}
文本及图片转换为字节数组
public void send(String text) {
byte[] temp = text.getBytes();
byte[] buffer = new byte[temp.length+1];
buffer[0] = 1;//作为文本消息的标记
System.arraycopy(temp, 0, buffer, 1, temp.length); //将temp(偏移量为0)中内容复制到buffer(偏移量为1)中
sendPackage(buffer);//调用方法发送数据包
}
public void send(BufferedImage img) {
try {
//图片转为字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, "jpg", bos);
byte[] temp = bos.toByteArray();
bos.close();
byte[] buffer = new byte[temp.length+1];
buffer[0] = 2;
System.arraycopy(temp, 0, buffer, 1, temp.length);
sendPackage(buffer);
}catch (Exception e){
}
}
通过线程接收数据包并处理
public class Receive extends Thread{
static BufferedImage img;
public void run() {
DatagramSocket recvSocket = null;
try {
recvSocket = new DatagramSocket(13000); //跟发送方的目标端口要一致
while (true) {
// 2.指定接收缓冲区大小:每个包30000字节
byte[] buffer = new byte[30000]; // 3.指定接收缓冲区大小:每个包20字节
// 3.创建接收数据包对象
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
// 4.阻塞等待数据到来,如果收到数据,存入packet中的缓冲区中
System.out.println("UDP服务器等待接收数据:");
recvSocket.receive(packet);
// 5、处理接收到的数据
dataHanlder(buffer);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
recvSocket.close();
}
}
private void dataHanlder(byte[] buffer) {
if(buffer[0]==1) {
int len = getValidLength(buffer); //获取有效长度
byte[] data = new byte[len-1];
System.arraycopy(buffer, 1, data, 0, data.length);
String text = new String(data)+"\r\n";
SendMain.setText("收到消息:"+text);
}else if(buffer[0]==2) {
byte[] data = new byte[buffer.length-1];
System.arraycopy(buffer, 1, data, 0, data.length);
ByteArrayInputStream bis = null;
try {
bis = new ByteArrayInputStream(data);
img = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private int getValidLength(byte[] buffer) {
int i=0;
while(buffer[i]!='\0') i++;
return i;
}
}
三、错误及解决
1、从接收的数据包中提取文本数据并转换为文本后显示在JTextArea中为空白,但是用Syso输出有 显示
原因是用于接收数据的字节数组定义的长度是30000,没有考虑数据包的有效长度,也没有对转换后的文本字符串进行去空白字符(用.trim()),所以显示为空白
2、线程接收的图片需要绘制在JPanel上,而摄像头图片的获取及绘制也是在while循环中,所以相当于两个线程需要使用同一个画笔,导致了线程安全问题,现象是小图片所在位置出现闪烁
处理方式:原本对于接收图片 绘制的方式是像文本信息的显示一样,调用窗体所在类中的绘制方法,把图片作为参数传递,窗体类中摄像头获取的图片绘制也通过这个方法,再对这个方法加一个synchronized,处理过后仍闪烁
解决:最后考虑问题可能不是线程问题,而是两个线程对重合部分的交替绘制不可避免地出现闪烁的情况,所以最终把接收的图片设置为静态变量,在窗体类中对两个图片一同绘制,从而解决闪烁的问题
四、扩展功能、及问题
实现超时重传(丢包重传)、接收数据包的大小定义问题、线程问题的处理