同步通讯简版(Java)


前言

提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、同步通讯的原理

java的socket套接字是可以用来实现同一台机器上的通信的,之前我们也尝试过通过简单地从客户端向服务器发送消息,再有服务器接收。其实,服务器和客户端的区别在于连接的时候,一方处于被动等待,一方处于主动连接;当两者相互传递消息的时候,对消息包装和解包的过程是完全一样的,因此我们可以通过复制客户端的功能到服务端,实现双向通信。

二、具体实现

首先,我们需要一个服务器类和一个客户端类

服务器类

public class DrawServer {
    ServerSocket serverSocket;
    OutputStream out;
    InputStream in;
    Socket socket;
}

首先我们需要知道服务器类需要干什么?最基本的功能是要额能够监听一个端口,其次要能够接收访问这个端口的客户端,然后为了发送和接收数据,需要得到服务器的输入输出流。

//监听服务短连接的端口,这是第一个不同
    public void listenServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    //连接客户端,同时获取客户端输入输出流
    public boolean connClinet() throws IOException {
        socket = serverSocket.accept();//得到连接的客户端
        out = socket.getOutputStream();
        in = socket.getInputStream();
        return true;//如果接收不到会阻塞,不返回
    }

注意一下,这里对于连接函数有一个返回值,因为如果服务器没有连接到客户端,服务器线程就会阻塞在accept方法上,直到连接了才能执行return true,这样就可以用来判断是否产生了连接。

然后就要实现消息的接收功能

public int get_Mes(Graphics g) throws IOException {
        byte[] row_mes = new byte[16];
        int type = in.read();//读取类型
        if(type == 1){//画直线,四个坐标,16个字节
            for(int i=0;i<16;i++){
                row_mes[i] = (byte) in.read();//对于大于127的怎么办?
            }
        }
        int[] position = new int[4];
        for(int i=0;i<4;i++){
            position[i] = byte_to_int(row_mes[i*4],row_mes[i*4 +1],row_mes[i*4+2],row_mes[i*4+3]);
        }
        g.drawLine(position[0],position[1],position[2],position[3]);
        return type;
    }

注意,这里一个整数有四个字节,我目前实现的是直线的传输,需要四个整数,16个字节。处理方法是,先读到接下来的数据类别(发送的格式为type — 数据),然后划定指定长度的空间来接收十六个字节,然后把接收到的字节转化为整数,最后画出来。(这里画布作为参数传入,就不需要一个属性来表示)

下面是整数转字节数组和字节数组转整数的方法

public byte[] int_to_bite(int num){
//        byte[] mes = new byte[4]; byte : -128 - 127;in.read() : 0 - 255,
//        一个大于127的数转成byte会丢失
        byte[] mes = new byte[4];
        mes[0] = (byte) (num >> 24 & 0xFF);
        mes[1] = (byte) (num >> 16 & 0xFF);
        mes[2] = (byte) (num >> 8 & 0xFF);
        mes[3] = (byte) (num >> 0 & 0xFF);
        return mes;
    }
    public int byte_to_int(byte mes1,byte mes2,byte mes3,byte mes4){//假设是从高位向低位
        return ((int) mes1 << 24) | ((int) mes2 << 16) | ((int) mes3 << 8) | ((int) mes4 << 0);
    }

这里还有一个问题,因为in.read()读到的字符范围是0 - 255,而byte接受到的是-128 - 127 ,这就会导致在接收的时候会有很大的可能失真。

下面是发送直线的功能

public void send_line(int x1,int y1,int x2,int y2) throws IOException{
        byte[] x1s = int_to_bite(x1);
        byte[] y1s = int_to_bite(y1);
        byte[] x2s = int_to_bite(x2);
        byte[] y2s = int_to_bite(y2);
        out.write(1);//数据种类不会太多
        out.write(x1s);
        out.write(y1s);
        out.write(x2s);
        out.write(y2s);
    }

把给定参数转成字节,在前面加上消息类型,发出去。

客户端类

客户端类的定义其实和服务器基本相同,除了申请发送和建立连接与服务器不同。

import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
//连接服务器
//发消息,读消息的体系是共用的,因为是双向通信,所以收发方法是很相似的
public class DrawClient {
    Socket socket;
    OutputStream out;
    InputStream in;
    //1.客户端连接到服务端
    public boolean loginServer(String ip,int port) throws IOException {
        try{
            socket = new Socket(ip,port);
            //通过指定ip和端口连接
        }catch (Exception e){
            return false;
        }
        out = socket.getOutputStream();
        in = socket.getInputStream();
        return true;//判断是否连接成功,客户线程接收到成功之后才收发数据
    }
    //接收数据并且在画板上画出来,既然画板通过参数传递,就不需要这个成员变量了
    public int get_Mes(Graphics g) throws IOException {
        byte[] row_mes = new byte[16];
        int type = in.read();//读取类型
        if(type == 1){//画直线,四个坐标,16个字节
            for(int i=0;i<16;i++){
                row_mes[i] = (byte) in.read();//对于大于127的怎么办?
            }
        }
        int[] position = new int[4];
        for(int i=0;i<4;i++){
            position[i] = byte_to_int(row_mes[i*4],row_mes[i*4 +1],row_mes[i*4+2],row_mes[i*4+3]);
        }
        g.drawLine(position[0],position[1],position[2],position[3]);
        return type;
    }
    public void send_line(int x1,int y1,int x2,int y2) throws IOException{
        byte[] x1s = int_to_bite(x1);
        byte[] y1s = int_to_bite(y1);
        byte[] x2s = int_to_bite(x2);
        byte[] y2s = int_to_bite(y2);
        out.write(1);//数据种类不会太多
        out.write(x1s);
        out.write(y1s);
        out.write(x2s);
        out.write(y2s);
    }
    public byte[] int_to_bite(int num){
//        byte[] mes = new byte[4]; byte : -128 - 127;in.read() : 0 - 255,
//        一个大于127的数转成byte会丢失
        byte[] mes = new byte[4];
        mes[0] = (byte) (num >> 24 & 0xFF);
        mes[1] = (byte) (num >> 16 & 0xFF);
        mes[2] = (byte) (num >> 8 & 0xFF);
        mes[3] = (byte) (num >> 0 & 0xFF);
        return mes;
    }
    public int byte_to_int(byte mes1,byte mes2,byte mes3,byte mes4){//假设是从高位向低位
        return ((int) mes1 << 24) | ((int) mes2 << 16) | ((int) mes3 << 8) | ((int) mes4 << 0);
    }
}

同样是需要在建立连接的时候返回一个boolean值,用来判断连接受否成功建立。

界面类

因为服务器和客户端在界面上没有特殊要求,可以不区分。

import javax.swing.*;
import java.io.OutputStream;
public class DrawUI {
	java.awt.Graphics g;
	OutputStream out;//从画布获取坐标,通过输出流给另一端
	public DrawUI(OutputStream out){
		this.out=out;
	}
	public void  initUI() {
		JFrame jf = new JFrame();
		jf.setTitle("鼠标控制监听");
		jf.setSize(700,600);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
		DrawMouseListener dml = new DrawMouseListener(out);
		jf.addMouseListener(dml);
		g=jf.getGraphics();
		// 将画笔对象 传给监听器对象的属性 g
		dml.g2 = g;
	}
}

这里需要给画布一个输出流对象,因为信息的传输是画布->监听器->界面->客户端->流->服务器端->画布,需要一个传出信息的桥梁。

监听器类

监听器需要对数据类型转换,把听到的整数转成字符,通过流传到另一端。

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.OutputStream;
public class DrawMouseListener implements MouseListener{
	java.awt.Graphics g2;
	OutputStream out;
	DrawMouseListener(OutputStream out){
		this.out=out;
	}
	int x1,y1,x2,y2;//全局变量 用来获取鼠标按下 释放的坐标值
	public void mouseClicked(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();

		System.out.println("鼠标点击"+x+" =x y= "+y);
	}
	public void mousePressed(MouseEvent e) {
		x1 = e.getX();
		y1 = e.getY();
		System.out.println("鼠标按下"+x1+"=x1 y1= "+y1);
	}
	public void mouseReleased(MouseEvent e) {
		x2=e.getX();
		y2=e.getY();
		// 绘制直线,射线
		g2.drawLine(100,100, x2, y2);
		// 发直线
		byte[] linedata = new byte[16];
		linedata[0]=int_to_bite(x1)[0];
		linedata[1]=int_to_bite(x1)[1];
		linedata[2]=int_to_bite(x1)[2];
		linedata[3]=int_to_bite(x1)[3];
		linedata[4]=int_to_bite(y1)[0];
		linedata[5]=int_to_bite(y1)[1];
		linedata[6]=int_to_bite(y1)[2];
		linedata[7]=int_to_bite(y1)[3];
		linedata[8]=int_to_bite(x2)[0];
		linedata[9]=int_to_bite(x2)[1];
		linedata[10]=int_to_bite(x2)[2];
		linedata[11]=int_to_bite(x2)[3];
		linedata[12]=int_to_bite(y2)[0];
		linedata[13]=int_to_bite(y2)[1];
		linedata[14]=int_to_bite(y2)[2];
		linedata[15]=int_to_bite(y2)[3];
		try {
			out.write(1);
			out.flush();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		for (int i = 0; i < linedata.length; i++) {
			try {
				out.write(linedata[i]);
				out.flush();
			} catch (IOException ioException) {
				ioException.printStackTrace();
			}
		}
		System.out.println("鼠标释放"+x2+"=x2 y2= "+y2);
	}
	public void mouseEntered(MouseEvent e) {
		System.out.println("鼠标进入");
	}
	public void mouseExited(MouseEvent e) {
		System.out.println("鼠标离开");
	}
	public byte[] int_to_bite(int num){
//        byte[] mes = new byte[4]; byte : -128 - 127;in.read() : 0 - 255,
//        一个大于127的数转成byte会丢失
		byte[] mes = new byte[4];
		mes[0] = (byte) (num >> 24 & 0xFF);
		mes[1] = (byte) (num >> 16 & 0xFF);
		mes[2] = (byte) (num >> 8 & 0xFF);
		mes[3] = (byte) (num >> 0 & 0xFF);
		return mes;
	}
	public int byte_to_int(byte mes1,byte mes2,byte mes3,byte mes4){//假设是从高位向低位
		return ((int) mes1 << 24) | ((int) mes2 << 16) | ((int) mes3 << 8) | ((int) mes4 << 0);
	}
}

通讯的线程创建

服务端线程和客户端线程都是运行在主进程的,是可以实现线程并发的,通过下面的代码就可以看到两个线程都采用了死循环,但是仍然不会因此而卡死在一个线程上。

服务器端线程

import java.awt.*;
import java.io.IOException;
public class StartServer {
    public static void main(String[] args) throws IOException {
        Graphics g = null;//局部变量初始化
        DrawServer server = new DrawServer();
        server.listenServer(9090);
        if(server.connClinet()){//卡注
            DrawUI ui = new DrawUI(server.out);
            ui.initUI();
            g = ui.g;
        }
        while(true){
            server.get_Mes(g);
        }
    }
}

服务器是被动接受连接的,所以通过连接的方法是否完成来判断是否需要创建画布。注意到下面的一个死循环不断去从画布获取信息,如果有事件就通过监听器的管道传给另一端,实现及时响应。

客户端线程

import java.awt.*;
import java.io.IOException;
public class StartClient {
    public static void main(String[] args) throws IOException {
        Graphics g = null;//局部变量初始化
        DrawClient client = new DrawClient();
        //创建客户端
        if(client.loginServer("127.0.0.1",9090)){//不会卡住
            DrawUI ui = new DrawUI(client.out);
            ui.initUI();
            g = ui.g;//这个g是给画的时候的参数
        }
        while (true){
           if(client.get_Mes(g) != -1){
               client.get_Mes(g);
           }
        }
    }
}

客户端线程是主动发起连接的,是不会停止的,因此不能向服务器一样看有没有客户来连接,而是要看主动发起的连接是否成功。
如何判断我发送的信息是否有效?因为无效信息发送过来,我没有对应的协议/处理方法,就无法解读信息,因此我让发送方法返回表示我传输消息的类型,如果是无法识别的类型,就不用接收了。


总结

至此,一个简单的通讯模拟就完成了,但是这个通讯识别的信息种类太少,数据类型的转换还是一个待解决的问题。我会在接下来的学习中更新的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰的野望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值