Java之通信画板

本次我们主要实现了画板之间的通信,客户端画板可以转递一条直线给服务端画板,服务端画板也可以传递一条直线给客户端画板,两端直线显示在相同位置上,效果如下:
在这里插入图片描述
涉及到的知识点主要有以下两点:
1,Java Socket通信。
2,int数据和byte数据的转换。

一,Socket通信

1,TCP/IP协议
TCP是一种面向连接的、可靠的、基于字节流的传输层协议,IP是互联网协议,Java Socket就是基于TCP/IP的网络通信。
我们在进行连接时要知道主机地址和端口号,主机地址就类似于知道机器的位置,端口号就类似于该机器上的某个具体插口,只有知道了这两个东西才能实现具体的服务端和客户端的通信。
端口号的范围为0~65535,其中0到1023是系统保留的端口号,我们设置的时候要设置1023之后的端口号。
2,ServerSocket
在Java中如果要使用TCP协议编写服务端的话就要用ServerSocket,ServerSocket负责接收来自客户端的连接请求,并生成与客户端连接的Socket。
3,Socket
Socket的中文释义是套接字,套接字是两台机器间通信的端点。Socket是作为一个通讯端的存在,这个Socke对象如果是自己创建的,那么该类就是客户端,如果是从ServerSocket中拿到的,那该类就是服务端。理论上可以有无数个Socket端来连接ServerSocket端,在ServerSocket端每有一个Socket发来连接请求时,就会创造一个与之相对应的Socket对象,示意如下:
在这里插入图片描述
4,InputStream和OutputStream
<1>Socket类可以用来编写客户端,ServerSocket可以用来编写服务端。创建ServerSocket对象时需要绑定一个端口号,这样客户端才可以通过该端口号进行连接通信;创建Socket对象时需要声明一个IP地址和ServerSocket对象的端口号,这样才能对服务端发送连接请求。
<2>当客户端发送连接请求服务端接收了连接请求之后

//客户端                       IP地址        端口号
Socket socket = new Socket("192.168.50.117",9050);

//服务端                           端口号
ServerSocket sc = new ServerSocket(9050);
Socket socket;
socket = sc.accept();//进行阻塞   得到连接

Socket客户端就会产生输入流和输出流用来接收消息或者发送消息

//客户端    
//输出流
OutputStream out = socket.getOutputStream();//得到输出流 
//输出流
InputStream in = socket.getInputStream();//得到输出流

ServerSocket创建的Socket对象也会产生输入流和输出流用来接收或者发送消息

//服务端    
//输出流
OutputStream out = socket.getOutputStream();//得到输出流 
//输出流
InputStream in = socket.getInputStream();//得到输出流

在这里插入图片描述

二,int数据和byte数组之间的转换

一个int数据由四个byte组成,由于输入流和输出流中传送的都是字节流,因此要将信息准确地传输就需要对数据进行转化。
1,int转为长度为4的byte数组
假设由四个字节组成的整型数据num从高位到低位依次为:①②③④,定义一个长度为4的字节数组bytes用来存储转换后的数据。
<1>将num数据①②③④向右移24位变成了000①(0代表一个byte的0数据),0xff是二进制的11111111,000①和0xff&操作之后取后8位,变成了000①,强制转为byte类型直接取后8位变成①,赋给byte[0]。
<2>将num数据①②③④向右移16位变成00①②,和0xff&操作之后变成000②,强制转型为byte类型后变成②存入byte[1]中。
依次类推,bytes数组从第0位到第3位依次存入①②③④,代码如下:

    //将1个int拆为长度为4的byte数组
	public byte[] devide(int num) {
		byte[] bytes = new byte[4];
		bytes[0]=(byte)((num)>>24&0xff);      
		bytes[1]=(byte)((num>>16)&0xff);
		bytes[2]=(byte)((num>>8)&0xff);
		bytes[3]=(byte)((num)&0xff);
		return bytes;
	}

2,将长度为4的byte数组转为int整数
按照上面的转化结果bytes数组中依次存入了整型数组的①②③④四个字节。
<1>由于函数返回的是int类型,当系统检测到byte类型数据可能要转化成int类型时就会将byte内存的高位自动按符号补位(为了保持二进制补码的一致性),然后将该32位数据和0xff&操作取后八位。byte[0]&0xff之后变成000①,向左移位24位变成①000.
<2>byte[1]&0xff之后变成000②,向左移位16位之后变成0②00,和<1>结果|操作之后变成①②00.
依次类推,最后返回的int数据为①②③④,代码如下:

	//将长度为4的byte数组合并为1个int
	public int combine(byte[] bytes) {
		return (bytes[0]&0xff)<<24
			  |(bytes[1]&0xff)<<16
			  |(bytes[2]&0xff)<<8
			  |(bytes[3]&0xff);
	}

三,通信画板的实现

1,客户端和服务端实现连接
客户端:

package com.yzd0319.convertPad;

import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JFrame;

public class Client {
	//主函数   程序入口
	public static void main(String[] args) throws UnknownHostException, IOException {
		Client client = new Client();
		client.loginClient();
	}
	//输出流
	OutputStream out;
	//输出流
	InputStream in;

	
	//通信函数     可以用来接收函数
    public void loginClient() throws UnknownHostException, IOException {
       //                             IP地址        端口号
    	Socket socket = new Socket("192.168.50.117",9050);
	


}

服务端:

package com.yzd0319.convertPad;

import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JFrame;

public class Server {
	//主函数   程序入口
	public static void main(String[] args) throws IOException {
		Server server = new Server();
		server.loginServer();
	}
	
	//输出流
	OutputStream out;
	
	//通信函数      主要用来接收数据
	public void loginServer() throws IOException {
		//Socket socket = new Socket("192.168.50.117",9050);
		//ServerSocket负责接收客户连接请求     并生成与客户端连接的Socket
		ServerSocket sc = new ServerSocket(9050);
		Socket socket;
				
		
		//不断尝试连接
		while(true) {
		    socket = sc.accept();//得到连接   只需要服务端进行尝试连接就好了     服务端不需要尝试连接    一旦服务端尝试连接成功    服务端和客户端双边都连接成功
			out = socket.getOutputStream();//得到输出流
了一个画图板界面
			InputStream in = socket.getInputStream();//得到输入流

		}
	}

}

2,实现画板界面,画一条直线
在这里我们使用匿名内部类和鼠标适配器,只重写鼠标按下和鼠标释放这两个函数。

	//界面函数    主要用来发送数据
	public  Graphics showUI() {
		JFrame jf = new JFrame("客户端画板发送");
		jf.setSize(300, 400);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
		//得到画笔参数
		Graphics g = jf.getGraphics();
		//绑定鼠标监听器   使用鼠标适配器  匿名内部类
		jf.addMouseListener(new MouseAdapter() {
			int xDown;//鼠标按下坐标
			int yDown;
			int xUp;//鼠标释放坐标
			int yUp;
			//鼠标按下
		    public void mousePressed(MouseEvent e) {
		    	xDown=e.getX();
		    	yDown=e.getY();
		    }

		    //鼠标释放
		    public void mouseReleased(MouseEvent e) {
		    	xUp=e.getX();
		    	yUp=e.getY();
		    	g.drawLine(xDown, yDown, xUp, yUp);//在鼠标按下和释放的位置画一条直线

		    	

		    	
		    }
		});
		return g;
	}

3,一端将直线数据发送出去
定义一个dataLine数组用来存放直线起点和终点坐标,画出直线之后将数组中的数据依次发送出去。

g.drawLine(xDown, yDown, xUp, yUp);//在鼠标按下和释放的位置画一条直线
int[] dataLine = new int[4];//接受直线数据的数组
dataLine[0] = xDown;
dataLine[1] = yDown;
dataLine[2] = xUp;
dataLine[3] = yUp;
for(int i=0;i<dataLine.length;i++) {
	try {
	    out.write(dataLine[i]);
	    //将1个int型数据转化为byte数组发送出去
		out.flush();
		} catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
		}
	}

4,另一端接收直线数据并显示
也同样定义一个dataline数组用来接收直线的四个数据,由于write每次只能读入一个数据,因此定义一个xy暂时存储write方法读到的数据,当全部数据都读取到后再将直线画出

socket = sc.accept();//得到连接   只需要服务端进行尝试连接就好了     服务端不需要尝试连接    一旦服务端尝试连接成功    服务端和客户端双边都连接成功
out = socket.getOutputStream();//得到输出流
InputStream in = socket.getInputStream();//得到输入流
Graphics g = showUI();//得到画笔参数    同时创建了一个画图板界面
int[] dataLine = new int[4];//接受直线数据的数组
int xy;//暂时接受输入流
int index=0;//数据数组下标
while(index<4) {
		xy=in.read();//得到输入数据  每次一个

		//System.out.println("服务端读取成功");
		dataLine[index]=xy;
		index++;
		while(index==4) {//当四个数据全部读取到的时候   将直线画出
			g.drawLine(dataLine[0], dataLine[1], dataLine[2], dataLine[3]);
							
			index=0;//将index重新置为0   这样就能一直读取数据
			System.out.println("服务端接收到"+dataLine[0]+" "+dataLine[1]+" "+dataLine[2]+" "+dataLine[3]);
							
			}
	}

2、3、4步代码为客户端和服务端相同代码,分别运行客户端和服务端代码得到的效果如下:
在这里插入图片描述
我们发现虽然可以实现客户端和服务端两端之间的直线通信,但直线显示的位置却不一致,这是因为以直线起点坐标x1为例,x1是一个整型数据,占4个byte,而write方法发送数据的时候每次只能发送一个byte的数据,read方法读取的时候也只能每次读取一个byte的数据,结合代码具体分析:

//发送数据   将四个点的数据发送出去   一个一个发送
for(int i=0;i<dataLine.length;i++) {
	try {
		//将int型数据转化为byte数组发送出去
		out.write(dataLine[i]);//错误代码   没有进行数字转换    直线显示位置不同
		out.flush();//将流中的数据发送出去
		//System.out.println("客户端发送成功");
	} catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	}
}
//接收数据
while(index<4) {
    xy=in.read();//错误代码   没有数据转换   直线位置没显示在同一端
    dataLine[index]=xy;
    index++;
    while(index==4) {//当直线数据全部接收到后将该直线画出
    g.drawLine(dataLine[0], dataLine[1], dataLine[2], dataLine[3]);
    index=0;//将index重新置为0   这样就能不断接收数据
    System.out.println("客户端接收到"+dataLine[0]+" "+dataLine[1]+" "+dataLine[2]+" "+dataLine[3]);          				
    }
}

<1>发送端:以发送整型dataline[0]为例,我们将dataline[0]这个数据的前1个byte用write方法发送出去了,然后就进入i=1的下一次循环了,因此dataline[0]的后3个比特的数据并没有发送出去,和原始数据x1这个四个byte长的数据不一致。
<2>接收端:当调用in.read()方法读取时,只能读1个byte的数据,然后在该方法内部将该byte数据自动转型为int类型(前24位自动补零)赋给dataline[0],将该dataline[0]认为是直线坐标x1.
因此当我们在客户端通过鼠标按下和释放得到的直线数据x1,和我们在客户端发送出去的x1’以及在服务端读到的x1’不一致,所以直线显示的位置不一样。
5,数据转换
在客户端和服务端都添加如下函数:

//将1个int拆为长度为4的byte数组
public byte[] devide(int num) {
    byte[] bytes = new byte[4];
	bytes[0]=(byte)((num)>>24&0xff);
	bytes[1]=(byte)((num>>16)&0xff);
	bytes[2]=(byte)((num>>8)&0xff);
	bytes[3]=(byte)((num)&0xff);
	return bytes;
}
//将长度为4的byte数组合并为1个int
public int combine(byte[] bytes) {
	return (bytes[0]&0xff)<<24
		  |(bytes[1]&0xff)<<16
		  |(bytes[2]&0xff)<<8
		  |(bytes[3]&0xff);
}

将发送数据和接收数据的代码修改如下:

//发送数据
//将四个点的数据发送出去   一个一个发送
for(int i=0;i<dataLine.length;i++) {
   try {
		//将int型数据转化为byte数组发送出去
		byte[] tempbytes=devide(dataLine[i]);
		out.write(tempbytes);//将数据写入流中
		//out.write(dataLine[i]);//错误代码   没有进行数字转换    直线显示位置不同
		out.flush();//将流中的数据发送出去
		//System.out.println("客户端发送成功");
	} catch (IOException e1) {
	    // TODO Auto-generated catch block
	    e1.printStackTrace();
	}
}
//接收数据
while(index<4) {
    //read方法每次读一个字节并自动转化为整型 (0~255)
    //一个整型数据由4个byte组成
    byte[] bytes = new byte[4];
    for(int i=0;i<4;i++) {
    //read函数每次读取一个字节并自动转为int
    //当int的数据超过255时需要用4个字节表示,如果只读8位信息丢失   因此需要连续读四次  将四个字节全部读出拼成一个长度位4的byte数组
    //然后将该byte数组转化为相应的int值
    bytes[i]=(byte)in.read();
    }
    			
   	xy=combine(bytes) ;	
    dataLine[index]=xy;
    index++;
    while(index==4) {//当直线数据全部接收到后将该直线画出
         g.drawLine(dataLine[0], dataLine[1], dataLine[2], dataLine[3]);
         index=0;//将index重新置为0   这样就能不断接收数据
         System.out.println("客户端接收到"+dataLine[0]+" "+dataLine[1]+" "+dataLine[2]+" "+dataLine[3]);           				
    }
}

<1>发送端:在发送数据的时候将int型数组调用divide()方法转成长度为4的byte类型数组tempbytes数组,调用write(temptypes)可以每次按tempbytes数组的长度发送字节流,即每次一次性将管道中temptypes数据的4个字节全部发送出去了。
<2>接收端:由于read()方法每次只能读取1个byte的数据,而发送端每次发送了4个byte的数据,因此直线每个点的数据如x1需要连续读4次,依次存入bytes数组的4个位置,再调用combine()方法将bytes数组转化为整数存入dataline数组中成为直线坐标x1,然后index++读取第二个数据y1:

    //一个整型数据由4个byte组成
    byte[] bytes = new byte[4];
    for(int i=0;i<4;i++) {
    //read函数每次读取一个字节并自动转为int
    //当int的数据超过255时需要用4个字节表示,如果只读8位信息丢失   因此需要连续读四次  将四个字节全部读出拼成一个长度位4的byte数组
    //然后将该byte数组转化为相应的int值
    bytes[i]=(byte)in.read();
    }
    			
   	xy=combine(bytes) ;	
    dataLine[index]=xy;//得到一个直线坐标数据
    index++;//读取直线的下一个坐标

基于以上内容我们就能得到一开始通信画板的效果啦,完整代码如下:
百度网盘链接—提取码:v78e

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值