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();
}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在MicroPython中使用TLS单向认证连接MQTT服务器并发送消息需要使用到umqtt.simple库。 首先需要在本地创建证书文件(如ca.pem)。然后使用umqtt.simple.MQTTClient类创建MQTT客户端对象,并设置连接参数,如服务器地址、端口、客户端ID、证书文件路径等。 在连接时使用.set_ssl()函数设置TLS,并将ca.pem文件传入。 最后使用.connect()函数连接服务器,并使用.publish()函数发布消息。 例如: ```python from umqtt.simple import MQTTClient client = MQTTClient("my_client_id", "mqtt.example.com", port=8883) client.set_ssl("ca.pem") client.connect() client.publish("topic", "message") ``` 需要注意的是,如果使用的是micropython, 需要使用umqtt.robust库,并且在使用的时候需要把证书文件内容转成字符串传入。 ```python from umqtt.robust import MQTTClient import ubinascii # 读取证书文件 with open('ca.pem', 'r') as f: ca = f.read() client = MQTTClient(client_id, server, port=8883, user=None, password=None, ssl=True, ssl_params={'ca_certs':ca,'cert_reqs':ssl.CERT_REQUIRED}) client.connect() client.publish("topic", "message") ``` ### 回答2: 在Micropython中,要使用TLS单向认证(客户端验证服务器)连接MQTT服务器并发送消息,需要遵循以下步骤: 1. 导入必要的库:首先,需要导入`network`和`ussl`库,`network`用于网络连接,`ussl`用于TLS连接。 2. 建立网络连接:使用`network`库建立与MQTT服务器的网络连接。可以使用`WLAN`或`LAN`类,具体方法取决于设备和网络类型。 3. 创建TLS连接:通过使用`ussl.wrap_socket`方法,创建TLS连接。使用此方法,需要提供已建立的网络连接和TLS协议的版本(如`ussl.PROTOCOL_TLSv1_2`)。 4. 连接MQTT服务器:使用`usocket`库中的套接字方法,建立与MQTT服务器的连接。需要提供服务器的主机名和端口号。 5. 发送连接请求:使用MQTT协议的相关方法,发送连接请求给服务器。需要提供客户端ID、用户名、密码等信息。 6. 发布消息:使用MQTT协议的相关方法,向服务器发布消息。需要提供主题、消息内容和QoS等信息。 以下是一个示例代码: ```python import network import ussl import usocket import ubinascii from umqtt.simple import MQTTClient # 1. 导入必要的库 # 2. 建立网络连接 # 3. 创建TLS连接 sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM) sock = ussl.wrap_socket(sock, server_hostname="mqtt.example.com") # 4. 连接MQTT服务器 mqtt_client = MQTTClient("client_id", "mqtt.example.com", ssl_sock=sock) # 5. 发送连接请求 mqtt_client.connect() # 6. 发布消息 mqtt_client.publish("topic", "message") # 关闭连接 mqtt_client.disconnect() sock.close() ``` 以上示例代码用于连接名为"mqtt.example.com"的MQTT服务器,并向主题"topic"发布消息"message"。请根据实际情况修改主机名、端口号、客户端ID、用户名、密码、主题和消息内容等参数。 ### 回答3: 在Micropython中,使用TLS单向认证连接MQTT服务器并发送消息需要按照以下步骤进行: 1. 首先,确保您的Micropython设备以及MQTT服务器支持TLS协议。如果不支持,需要升级或更换设备和服务器。 2. 获取MQTT服务器的根证书(CA证书)以及客户端的证书和私钥。在确认服务器支持TLS时,可以向服务器管理员申请根证书,并从服务器获得客户端证书和私钥。 3. 将根证书、客户端证书和私钥文件分别保存到Micropython设备的文件系统中,以便后续使用。 4. 在Micropython设备上使用TLS单向认证时,需要安装TLS支持库。可以通过安装`umqtt.robust`库来实现MQTT协议的通信,并安装`urequests`库来支持HTTPS协议,用于下载证书文件。 5. 设定MQTT服务器的连接参数,包括服务器的地址、端口号和MQTT客户端的ID。 6. 配置TLS客户端的参数,包括根证书文件路径、客户端证书和私钥文件路径等。 7. 在Micropython中,可以使用`import socket`和`import ssl`模块来实现TLS连接。使用`socket`模块创建一个socket对象,然后使用`ssl.wrap_socket()`方法将其包装为TLS连接。 8. 使用MQTT的连接请求消息来连接MQTT服务器。 9. 连接成功后,可以使用MQTT的发布消息来发送消息到服务器。 以上是在Micropython中使用TLS单向认证连接MQTT服务器并发送消息的大致步骤。具体实现过程可能因设备、服务器和框架版本而有所不同,因此需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值