Java第十三课——客户端与服务器单向多样化数据传输

Java第十三课——客户端与服务器单线多样化数据传输

之前我们完成了基本的服务器和客户端的额雏形,能实现单字节的发送,那么接下来就需要尝试多样化的数据传输。毕竟在我们日常生活中,聊天不可能每次都发送一个字节,我们所使用和汉字都是char(两个字节),那么如何来完成多样化传输,来发送大量文字,文件,视频呢?
这节课内容都按上节课的基础上来改动
服务器端:

public class Server {
	ServerSocket serversocket;
	public void create(){
		try{
			serversocket = new ServerSocket(9876);
			System.out.println("ServerSocket: loading successfully");
		} catch (IOException e){
			System.out.println("ServerSocket: loading error");
			e.printStackTrace();
		}
	}	
	
	public void conn(){
		try {
			//等待用户连接
			Socket socket = serversocket.accept();
			System.out.println("ServerSocket: user enter");		
					
			//获取输入输出流
			InputStream in = socket.getInputStream();
			OutputStream out = socket.getOutputStream();
						
			//读取数据
			int onebyte = in.read();
			char ch = (char)onebyte;
			System.out.println("ServerSocket: has read: " + ch);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		Server server = new Server();
		server.create();
		server.conn();
	}
}

客户端:

public class UserSocket {
	String ip = "0.0.0.0";//IP地址
	int port = 9876;
	OutputStream out;//客户端的输出流
	InputStream in;//客户端输入流
	
	public void userUI(){
		JFrame frame = new JFrame();
		frame.setSize(300,300);
		frame.setLayout(new FlowLayout());
		frame.setDefaultCloseOperation(3);
		
		JTextField field = new JTextField(20);//输入框
		JButton btn = new JButton("发送"); 
		frame.add(field);
		frame.add(btn);
		frame.setVisible(true);
		
		ActionListener l = new ActionListener() {		
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub
				try {
//					System.out.println(field.getText());
					out.write(field.getText().getBytes());
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		};	
		btn.addActionListener(l);
	}
	
	public void conn(){
		try {
			Socket socket = new Socket(ip, port);
			System.out.println("User: connected");			
			
			in = socket.getInputStream();
			out = socket.getOutputStream();			
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		UserSocket us = new UserSocket();
		us.userUI();
		us.conn();
	}
}

一、你画我猜

平时我们玩的你画我猜,就是在滑画板上画图,至于在画板上如何实现画图就很简单了,可以参考我前面的文章

Java第六课——画图板

这里面写了基本的划线和画圆,但你画我猜就是在基本画图板的基础上去扩展,在这里就不多重复了,那么这次我们要完成的核心便是数据的传输,先来看看单线通信下事件发生的次序:
客户端:在界面上画图——>监听器获取到轨迹,记录坐标——>发送出去
服务器:打开一个窗体——>获取到坐标——>在窗体上按照坐标再画出来

下面以画直线来举例:
1、从事件的发生顺序来写,那么首先客户端需要一个新的界面和Graphics

public void drawLineUI(){
	JFrame draw = new JFrame();
	draw.setSize(500, 500);
	draw.setTitle("你画我猜——客户端");
	draw.setLocationRelativeTo(null);
	draw.setDefaultCloseOperation(3);
		
	draw.setVisible(true);
	Graphics g = draw.getGraphics();
}

然后便是监听器MouseListener获取轨迹(为了减少篇幅总体长度,我用了MouseAdapter而没有创建一个新的类,建议写个新的类,实现ActionListener和MouseListener两个接口)

public void drawLineUI(){
	JFrame draw = new JFrame();
	draw.setSize(500, 500);
	draw.setTitle("你画我猜——客户端");
	draw.setLocationRelativeTo(null);
	draw.setDefaultCloseOperation(3);
	
	draw.setVisible(true);
	Graphics g = draw.getGraphics();
	
	MouseListener l = new MouseAdapter() {
		int x1, y1, x2, y2;
		@Override
		public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
			super.mousePressed(e);
			x1 = e.getX();
			y1 = e.getY();
		}
			
		@Override
		public void mouseReleased(MouseEvent e) {
			// TODO Auto-generated method stub
			super.mouseReleased(e);
			x2 = e.getX();
			y2 = e.getY();
		}
	};
	draw.addMouseListener(l);
}

接着从客户端把数据发送出去,但要注意的是发送出去的顺序,以怎样的顺序发送出去,就需要以怎样的顺序接收数据,这也就是通信协议,是自己规定的,是客户端和服务器之间通信的标准

@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
super.mouseReleased(e);
	x2 = e.getX();
	y2 = e.getY();
	//OutputStream out是全局变量
	try {
		out.write(x1);
		//输出以确认是否发成功
		System.out.println("user: has writed: x1=" + x1);
		out.write(y1);
		System.out.println("user: has writed: y1=" + y1);
		out.write(x2);
		System.out.println("user: has writed: x2=" + x2);
		out.write(y2);
		System.out.println("user: has writed: y2=" + y2);
		System.out.println("————————————————————————————");//分割线
		//记得也要画条线
		g.drawLine(x1, y1, x2, y2);
	} catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	}
}

服务器打开一个新窗体

public void drawLineUI(){
	JFrame draw = new JFrame();
	draw.setSize(400, 400);
	draw.setTitle("你画我猜——服务器端");
	draw.setLocationRelativeTo(null);
	draw.setDefaultCloseOperation(3);
		
	draw.setVisible(true);
	//Graphics g设为全局变量
	g = draw.getGraphics();
}

接收数据并画图,要注意的是,服务器不可能只是接收一次数据,画一条线就结束了,应该是不停的接收数据画图,所以要加上while(true)

public void conn(){
	try {
		//等待用户连接
		Socket socket = serversocket.accept();
		System.out.println("ServerSocket: user enter");	
			
		//获取输入输出流
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();	
			
		//读取数据
		//先设置int x1,y1,x2,y2的全局变量来接收发过来的数据
		while(true){
			//按照顺序接收
			x1 = in.read();
			System.out.println("ServerSocket: has read: x1=" + x1);
			y1 = in.read();
			System.out.println("ServerSocket: has read: y1=" + y1);
			x2 = in.read();
			System.out.println("ServerSocket: has read: x2=" + x2);
			y2 = in.read();
			System.out.println("ServerSocket: has read: y2=" + y2);
			System.out.println("————————————————————————————");//分割线
			//画线
			g.drawLine(x1, y1, x2, y2);
		}
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

在启动服务器客户端之前要记得修改主方法里要调用的方法和顺序
服务器:

public static void main(String[] args){
	Server server = new Server();
	server.create();
	server.drawLineUI();
	server.conn();
}

客户端:

public static void main(String[] args) {
	UserSocket us = new UserSocket();
	us.drawLineUI();
	us.conn();
}

试试效果!!
——————————————————————————————————————
如果试了效果会发现出现的问题
你画我猜1.0
左边的两条线还正常,右边的线就跑偏了,接下来看看输出
客户端的输出:
客户端
服务器端的输出
服务器端
那么不难看出发送的数据和接收的数据有出入,并且有问题的数据与原数据的差值是256,256正好是一个byte所能表示的数字范围,于是来看看out.write(int b)方法的源码:
write源码
上节课也说过,write方法每次只写一个字节,所以要传大于256的数字就需要换发送方法。那么会用到另一个输入输出流,可以很好的解决这个问题:DataOutputStream和DataInputStream,可以发送Int,String,long,float等等类型:

DataOutputStream dout = new DataOutputStream(OutputStream out);
DataInputStream din = new DataInputStream(InputStream in)

于是在客户端:

@Override
public void mouseReleased(MouseEvent e) {
	// TODO Auto-generated method stub
	super.mouseReleased(e);
	x2 = e.getX();
	y2 = e.getY();
	try {
		//变成了writeInt方法
		dout.writeInt(x1);
		System.out.println("user: has writed: x1=" + x1);
		dout.writeInt(y1);
		System.out.println("user: has writed: y1=" + y1);
		dout.writeInt(x2);
		System.out.println("user: has writed: x2=" + x2);
		dout.writeInt(y2);
		System.out.println("user: has writed: y2=" + y2);
		System.out.println("————————————————————————————");//分割线
		
		g.drawLine(x1, y1, x2, y2);
	} catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	}
}

服务器端:

public void conn(){
	try {
		//等待用户连接
		Socket socket = serversocket.accept();
		System.out.println("ServerSocket: user enter");	
			
		//获取输入输出流
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
			
		//换成DataInputStream
		DataInputStream din = new DataInputStream(in);
			
		//读取数据
		//先设置int x1,y1,x2,y2的全局变量来接收发过来的数据
		while(true){
			//按照顺序接收
			//方法变成了readInt
			x1 = din.readInt();
			System.out.println("ServerSocket: has read: x1=" + x1);
			y1 = din.readInt();
			System.out.println("ServerSocket: has read: y1=" + y1);
			x2 = din.readInt();
			System.out.println("ServerSocket: has read: x2=" + x2);
			y2 = din.readInt();
			System.out.println("ServerSocket: has read: y2=" + y2);
			System.out.println("————————————————————————————");//分割线
			g.drawLine(x1, y1, x2, y2);
		}			
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

再测试一下,就发现可以了你画我猜2.0

二、迷你视频

依据输入输出流来实现一个视频会议的效果,会用到webcam的jar包用来获取视频,链接如下:

webcam-capture

下载之后导包,这个就不发教程了,不会的可以直接去首页搜。
视频的播放其实就是一张一张图片连续播放,所以可以说视频的本质就是图片
先来缕一缕单线视频聊天事件发生次序:
客户端:获取图片——>把图片转成一个一个的像素点——>发送数据
服务器:接收数据——>把像素点转换成图片——>画出图片

首先便是获取图片
在上面那个网址上找到最便捷的图片获取方式:

// get default webcam and open it
Webcam webcam = Webcam.getDefault();
webcam.open();

// get image
BufferedImage image = webcam.getImage();

视频的实现就是加上while(true)然后用Graphics画出来:
在客户端写一个界面,用于获取视频和呈现视频

public void videoUI(){
	JFrame video = new JFrame();
	video.setSize(300, 300);
	video.setTitle("迷你视频——客户端");
	video.setLocationRelativeTo(null);
	video.setDefaultCloseOperation(3);

	video.setVisible(true);
	//把Graphics g设成全局变量
	g = video.getGraphics();
}

获取视频

public void video(){
	// get default webcam and open it
	Webcam webcam = Webcam.getDefault();
	webcam.open();

	// get image
	while(true){
		BufferedImage image = webcam.getImage();
		g.drawImage(image, 50, 50, null);
	}		
}

到这里可以先启动一下,看看视频能不能成功的画在界面上(这很重要),测试的时候别忘记改main方法里调用的方法
然后就是把图片转换成像素点,每转成一个就发出去一个
但发送时就需要注意,如何在接收时知道,这个像素的颜色应该在哪个位置。比如说我们获取的图片是200×200的,那在客户端这边就写两个for(int i = 0; i < 200; i++),但在服务器该这么写for循环?毕竟服务器并不知道图片的大小
这里先获取服务器的长和宽,然后先发送长和宽,然后用image.getRGB获取像素点对应的颜色,发送出去

public void video(){
	// get default webcam and open it
	Webcam webcam = Webcam.getDefault();
	webcam.open();

	// get image
	while(true){
		BufferedImage image = webcam.getImage();
		g.drawImage(image, 50, 50, null);
		//获取长和宽
		int w = image.getWidth();
		int h = image.getHeight();
		try {
			//先发送长和宽
			dout.writeInt(w);
			dout.writeInt(h);
			//把图片转换
			for(int i = 0; i < w; i++){
				for(int j = 0; j < h; j++){
					//用value记录像素点的颜色
					int value = image.getRGB(i, j); 
					dout.writeInt(value);
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}		
}

在这里用了DataOutputStream,所以必须要先连接服务器再打开视频
另外,如果像保存视频,可以用二维数组记录下保存的视频,写进文件里,再另外可以写一个打开文件并画出的视频程序,就实现视频保存了
服务器端:需要一个从窗体来呈现视频

public void videoUI(){
	JFrame video = new JFrame();
	video.setSize(300, 300);
	video.setTitle("迷你视频——服务器端");
	video.setLocationRelativeTo(null);
	video.setDefaultCloseOperation(3);

	video.setVisible(true);
	g = video.getGraphics();
}

窗体可以在连接上客户端之后打开
然后便是依据协议接收图片数据并画出,画点时用划线的方法,把起点终点定为同一点即可

public void conn(){
	try {
		//等待用户连接
		Socket socket = serversocket.accept();
		System.out.println("ServerSocket: user enter");	
			
		//获取输入输出流
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
			
		DataInputStream din = new DataInputStream(in);
		//打开窗体
		drawLineUI();
		//读取数据
		while(true){
			//先读取长和宽
			int w = din.readInt();
			int h = din.readInt();
			//然后就是读取图片像素点的数据
			for(int i = 0; i < w; i++){
				for(int j = 0; j < h; j++){
					int value= din.readInt();
					Color color = new Color(value);
					g.setColor(color);
					g.drawLine(i, j, i, j);
				}
			}
		}			
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

最后修改一下两边的主方法
客户端:

public static void main(String[] args) {
	UserSocket us = new UserSocket();
	us.videoUI();
	//一定要先连接,再通视频
	us.conn();
	us.video();		
}

服务器端:

public static void main(String[] args){
	Server server = new Server();
	server.create();
	server.conn();
}

最后启动一下,就实现了视频的通话,但可以看出视频非常卡顿,可以加入缓存或添加压缩方法来使视频更流畅。另外目前只是单向的通信,下节课来实现双向的交流,就可以完成群聊等效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值