前言
2022.1.16
和同学一起做的项目,打算将来写在简历里面,或者就是写着玩玩,前言也会不定时更新,主要是写给自己看,防止自己忘记hhh
以后是打算边写代码边写博客,不然写完再写博客太费劲了,懒
2022.1.19
第二次更新,实现了简单的界面视图,并以直线的形式传递信息,也就是传两个点
一、服务器和客户端
1.创建服务器类和用户类
服务器
package communication2;
import java.io.IOException;
import java.net.ServerSocket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
System.out.println("等待连接。。。");
serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
System.out.println("连接成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
用户
package communication2;
import java.io.IOException;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",18888);//尝试连接
} catch (IOException e) {
e.printStackTrace();
}
}
}
先后运行Server和Client
运行Server时
运行了Client后
第一次连接尝试成功~~
接下来我们尝试让服务器发点东西给用户,看看能不能接收
2.第一次传输数据
更新代码
package communication2;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
System.out.println("等待连接。。。");
//获取Socket对象
Socket socket = serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
System.out.println("连接成功~");
OutputStream outputStream = socket.getOutputStream();//获取输出流
outputStream.write(new String("hello my friend").getBytes(StandardCharsets.UTF_8));//写入输出流
outputStream.close();//关闭输出流,表明输出结束
} catch (IOException e) {
e.printStackTrace();
}
}
}
package communication2;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",18888);//尝试连接
//获取从服务器传来的输入
InputStream inputStream = socket.getInputStream();
while (true){
int word = inputStream.read();//这个方法每次只能获取一个字符并为int类型
if (word == -1)
break;
//转换成char并输出字符
System.out.print((char)word);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
芜湖~成功了
3.将用户封装
package communication;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class Client {
private Socket socket;
private InputStream inputStream;
public Client(String ip, int port){
try {
this.socket = new Socket(ip,port);
this.inputStream = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
//封装读取信息方法
public void receiveMsg(){
int perWord = 0;
while (true){
try {
perWord = this.inputStream.read();
if (perWord == -1)
break;
System.out.print((char) perWord);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
更新Server代码,使其能够持续接收连接
package communication2;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Server {
static int index = 0;
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
System.out.println("等待连接。。。");
//持续保持等待连接状态
while (true) {
//获取Socket对象
Socket socket = serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
System.out.println("连接成功~");
index++;
OutputStream outputStream = socket.getOutputStream();//获取输出流
outputStream.write(new String("hello my friend you are the "+index+" client").getBytes(StandardCharsets.UTF_8));//写入输出流
outputStream.close();//关闭输出流,表明输出结束
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建一个Test类来写main方法,创建Client对象
package communication2;
public class Test {
public static void main(String[] args) {
Client client1 = new Client("127.0.0.1",18888);
client1.receiveMsg();
System.out.println();
Client client2 = new Client("127.0.0.1",18888);
client2.receiveMsg();
}
}
good,现在可以同时连接多个对象了,并且把用户封装好了
二、实现简单可视化界面通信
1.可视化界面和鼠标监听器
可视化界面用JFrame来写,并给该界面加上一个监听器,来监听鼠标按压和释放的点位,以此来画点
Server的封装
连接服务端的connect方法
private Socket connect(){
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
return socket;
}
界面生成方法
private JFrame initUi(Socket socket){
JFrame jFrame = new JFrame("通信测试界面-服务端");
jFrame.setSize(400,400);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setLocation(790,290);
jFrame.setVisible(true);
try {
//增加监听器,传入画布和输出流
jFrame.addMouseListener(new ServerUiListener(jFrame.getGraphics(),socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
return jFrame;
}
监听器类的实现
package communication;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class ServerUiListener implements MouseListener {
private Graphics graphics;
private OutputStream outputStream;
//确定两个坐标
private int x1,y1,x2,y2;
public ServerUiListener(Graphics graphics,OutputStream outputStream){
this.graphics = graphics;
this.outputStream = outputStream;
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
}
@Override
public void mouseReleased(MouseEvent e) {
//鼠标释放后即开始画线
x2 = e.getX();
y2 = e.getY();
graphics.drawLine(x1,y1,x2,y2);
//将两个坐标出入输出流
try {
outputStream.write(new Integer(x1).byteValue());
outputStream.write(new Integer(y1).byteValue());
outputStream.write(new Integer(x2).byteValue());
outputStream.write(new Integer(y2).byteValue());
// System.out.println(x1+" "+y1+" "+x2+" "+y2);
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
客户端的代码类似,也是加个界面加个监听器,不赘述了
2.运行展示![在这里插入图片描述](https://img-blog.csdnimg.cn/e9eef9fc5dd44bad8203d98b486857b3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y2X5oe_54Oo5pum,size_20,color_FFFFFF,t_70,g_se,x_16)
两边的窗口都可以相互看到画线的地方,在Server窗口画线可以在客户端看到,在客户端画线Server也能接收到
3.写代码时的疑惑
Server的监听器类中有数据传入到输出流,而在Server类中又有Inputstream的接收,同样的Client类里也有Inputstream接收,那到底是哪个Inputstream接收呢?运行后发现只有Client类才会接收,同一个对象不能既发送流又读入流
4.存在的问题
Inputstream在接收信息的时候只能接入0~255的int整数,超过255会溢出,或者小于0也会溢出,
具体表现就是,我在Server的窗口里画一条超范围的直线,在客户端接收到的点位会有明显的偏差
这个问题我在之后应该会解决
三、解决输入输出流最大只能传送255整数的问题
1.原理即解决方法
由于Inputstream的read()方法只能返回一个0~255的整数,也就是1个字节,8个bit的值,所以,要想准确的传递参数,需要先搞清楚int,byte和bit之间的关系
按理来说这是最基本的东西,但是我以前没咋用,所以记不住,嘿嘿嘿
在java里面,一个int的大小为4个byte即32个bit,所以要想将int完整的表示出来,需要将int拆分成4个部分放入输出流,然后在接收的时候再拼接出来
int用二进制来表示一共有32位,而Outputstream的write方法每次都只取最后8位,所以我们要将int拆分为32-25位,24-17位,16-9位,8-1位,用右移操作即可,接收时再左移回去
先来一个简单的例子
package Test;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(50000);
Socket socket = serverSocket.accept();
OutputStream outputStream = socket.getOutputStream();
//传10000已经大于255了
int num = 10000;
//进行移位操作
outputStream.write(num>>24);
outputStream.write(num>>16);
outputStream.write(num>>8);
outputStream.write(num>>0);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package Test;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",50000);
InputStream inputStream = socket.getInputStream();
//读取拆分的4部分
int b1 = inputStream.read();
int b2 = inputStream.read();
int b3 = inputStream.read();
int b4 = inputStream.read();
//拼接
int x = b1<<24|b2<<16|b3<<8|b4;
System.out.println(b1+" "+b2+" "+b3+" "+b4);
System.out.println(x);
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后分别运行,看输出
表示的是32到25位为0,24到17位为0,16到9位为39,8到1位为16
拼接过后得出了正确的数值
39和16代表啥意思呢,看看计算器
再来看看10000对应的二进制
这就代表了右移过后的结果
2.方法的封装
好了我们接下来将传int的方法封装起来,方便使用
package communication3;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StreamMethod {
public static void writeInt(OutputStream outputStream,int num){
try {
outputStream.write(num>>24);
outputStream.write(num>>16);
outputStream.write(num>>8);
outputStream.write(num>>0);
} catch (IOException e) {
e.printStackTrace();
}
}
public static int readInt(InputStream inputStream){
try {
int b1 = inputStream.read();
int b2 = inputStream.read();
int b3 = inputStream.read();
int b4 = inputStream.read();
//拼接
return b1<<24|b2<<16|b3<<8|b4;
} catch (IOException e) {
e.printStackTrace();
return 0;
}
}
}
3.更新代码即运行结果
Server中的监听方法
private void startListenMgs(JFrame jFrame,Socket socket){
int x1,y1,x2,y2;
try {
InputStream inputStream = socket.getInputStream();
Graphics graphics = jFrame.getGraphics();
while (true) {
x1 = StreamMethod.readInt(inputStream);
y1 = StreamMethod.readInt(inputStream);
x2 = StreamMethod.readInt(inputStream);
y2 = StreamMethod.readInt(inputStream);
graphics.drawLine(x1,y1,x2,y2);
System.out.println(x1+" "+y1+" "+x2+" "+y2);
}
} catch (IOException e) {
e.printStackTrace();
}
}
监听器中的写入方法
public void mouseReleased(MouseEvent e) {
//鼠标释放后即开始画线
x2 = e.getX();
y2 = e.getY();
graphics.drawLine(x1, y1, x2, y2);
//将两个坐标出入输出流
StreamMethod.writeInt(outputStream, x1);
StreamMethod.writeInt(outputStream, y1);
StreamMethod.writeInt(outputStream, x2);
StreamMethod.writeInt(outputStream, y2);
System.out.println(x1+" "+y1+" "+x2+" "+y2);
}
运行结果
现在数据算是传入成功了
四、完善功能
现在属于是只能画直线,我们可以多弄几种类型,加上画笔,圆和矩形,并且优化一下实时显示的功能
1.封装字符串传输的方法
要辨别选择的是哪一种类型,就需要通过字符串来区分,一个字符通常是2字节,但是如果是中文的话就是3字节,要是中英混搭的话就不好区分长度,所以直接调用.getBytes().length方法即可
public static void writeStr(OutputStream outputStream,String str){
try {
outputStream.write(str.getBytes().length);
outputStream.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
在接收的时候,字符串有个传入byte数组的构造方法可以直接转换成字符串
public static String readStr(InputStream inputStream){
try {
int length = inputStream.read();
byte[] strArray = new byte[length];
inputStream.read(strArray);
return new String(strArray);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
2.添加按钮并更新监听器
一些ui上的操作就不用赘述了,直接上代码展示
public static JFrame ServerUI(String title, Socket socket) {
String[] btnStr = {"画笔", "直线", "圆", "矩形"};
JFrame jFrame = new JFrame(title);
try {
jFrame.setSize(1000, 800);
jFrame.setLocation(200, 100);
jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE);
jFrame.setLayout(new FlowLayout());
jFrame.setVisible(true);
ServerUiListener serverUiListener = new ServerUiListener(jFrame.getGraphics(), socket.getOutputStream());
jFrame.addMouseListener(serverUiListener);
jFrame.addMouseMotionListener(serverUiListener);
for (int i = 0; i < btnStr.length; i++) {
JButton jButton = new JButton(btnStr[i]);
jFrame.add(jButton);
jButton.addActionListener(serverUiListener);
// System.out.println("加了一个按钮");
}
//setVisible必须放在最后面,否则按钮不会显示
jFrame.setVisible(true);
return jFrame;
} catch (IOException e) {
e.printStackTrace();
return jFrame;
}
}
监听器方面,画笔的话需要实时拖动,故调用mouseDragged方法。
如何动态显示作图痕迹
在拖动的过程中就应该去绘制线条,并在绘制下一个线条的时候擦除前一个线条,这样就感觉是动态绘制了,擦除方法我选择在原来的点位上绘制与背景相同颜色的线条,这样就相当于是擦除了
另外有个问题,在擦除的过程中可能会擦除之前已经画好的线条,所以我们还需要用数组保留画好的线条的点位,在每次擦除的时候重绘一下
我把点的数组放在了UI这个类里面设置成静态变量来使用
有关UI的方法都在这个UI类里面封装成了静态方法
public void mouseReleased(MouseEvent e) {
//鼠标释放后即开始画线
x2 = e.getX();
y2 = e.getY();
//在鼠标释放的时候加入点位
if(shape.equals("直线")) {
// System.out.println(x2+" "+y2);
UI.lineStartPoints.add(new Point(x1,y1));
UI.lineEndPoints.add(new Point(x2,y2));
graphics.drawLine(x1, y1, x2, y2);
}
if(shape.equals("圆")){
UI.circleStartPoints.add(new Point(x1,y1));
UI.circleEndPoints.add(new Point(x2,y2));
graphics.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
}
if(shape.equals("矩形")){
UI.recStartPoints.add(new Point(x1,y1));
UI.recEndPoints.add(new Point(x2,y2));
graphics.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
}
if(!shape.equals("画笔")) {
//加入了图形类就要先传类型,直接调用封装好的方法即可
StreamMethod.writeStr(outputStream, shape);
//将两个坐标出入输出流
StreamMethod.writeInt(outputStream, x1);
StreamMethod.writeInt(outputStream, y1);
StreamMethod.writeInt(outputStream, x2);
StreamMethod.writeInt(outputStream, y2);
System.out.println(x1 + " " + y1 + " " + x2 + " " + y2);
}
}
public void mouseDragged(MouseEvent e) {
//由于画笔是实时加入的,所以画笔的点位在鼠标拖动的时候就加上了
if (shape.equals("画笔")){
UI.Points.add(new Point(e.getX(),e.getY()));
graphics.fillOval(e.getX(),e.getY(),2,2);
StreamMethod.writeStr(outputStream,shape);
StreamMethod.writeInt(outputStream,e.getX());
StreamMethod.writeInt(outputStream,e.getY());
}
if(shape.equals("直线")){
//将之前的线覆盖
if(prex!=e.getX()||prey!=e.getY()){
graphics.setColor(new Color(238,238,238));
graphics.drawLine(x1,y1,prex,prey);
}
graphics.setColor(Color.black);
graphics.drawLine(x1,y1,e.getX(),e.getY());
}
if(shape.equals("圆")){
//将之前的线覆盖
if(prex!=e.getX()||prey!=e.getY()){
graphics.setColor(new Color(238,238,238));
graphics.drawOval(Math.min(x1,prex),Math.min(y1,prey),Math.abs(x1-prex),Math.abs(y1-prey));
}
graphics.setColor(Color.black);
graphics.drawOval(Math.min(x1,e.getX()),Math.min(y1,e.getY()),Math.abs(x1-e.getX()),Math.abs(y1-e.getY()));
}
if(shape.equals("矩形")){
//将之前的线覆盖
if(prex!=e.getX()||prey!=e.getY()){
graphics.setColor(new Color(238,238,238));
graphics.drawRect(Math.min(x1,prex),Math.min(y1,prey),Math.abs(x1-prex),Math.abs(y1-prey));
}
graphics.setColor(Color.black);
graphics.drawRect(Math.min(x1,e.getX()),Math.min(y1,e.getY()),Math.abs(x1-e.getX()),Math.abs(y1-e.getY()));
}
//这里是重绘之前的线条
for (int i = 0; i < UI.Points.size(); i++) {
graphics.fillOval(UI.Points.get(i).x,UI.Points.get(i).y,1,1);
}
for (int i = 0; i < UI.lineStartPoints.size(); i++) {
graphics.drawLine(UI.lineStartPoints.get(i).x,UI.lineStartPoints.get(i).y,UI.lineEndPoints.get(i).x,UI.lineEndPoints.get(i).y);
}
for (int i = 0; i < UI.circleEndPoints.size(); i++) {
graphics.drawOval(Math.min(UI.circleStartPoints.get(i).x,UI.circleEndPoints.get(i).x),Math.min(UI.circleStartPoints.get(i).y,UI.circleEndPoints.get(i).y),Math.abs(UI.circleStartPoints.get(i).x-UI.circleEndPoints.get(i).x),Math.abs(UI.circleStartPoints.get(i).y-UI.circleEndPoints.get(i).y));
}
for (int i = 0; i < UI.recEndPoints.size(); i++) {
graphics.drawRect(Math.min(UI.recStartPoints.get(i).x,UI.recEndPoints.get(i).x),Math.min(UI.recStartPoints.get(i).y,UI.recEndPoints.get(i).y),Math.abs(UI.recStartPoints.get(i).x-UI.recEndPoints.get(i).x),Math.abs(UI.recStartPoints.get(i).y-UI.recEndPoints.get(i).y));
}
prex = e.getX();
prey = e.getY();
}
Server类里的信息接收
private void startListenMgs(JFrame jFrame,Socket socket){
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
String shape;
try {
InputStream inputStream = socket.getInputStream();
Graphics graphics = jFrame.getGraphics();
while (true) {
shape = StreamMethod.readStr(inputStream);
if(!shape.equals("画笔")) {
x1 = StreamMethod.readInt(inputStream);
y1 = StreamMethod.readInt(inputStream);
x2 = StreamMethod.readInt(inputStream);
y2 = StreamMethod.readInt(inputStream);
}else {
//画笔只需要接收两个点位,这个在传输的时候可以先协商好
x1 = StreamMethod.readInt(inputStream);
y1 = StreamMethod.readInt(inputStream);
graphics.fillOval(x1,y1,2,2);
}
if (shape.equals("直线")) {
graphics.drawLine(x1, y1, x2, y2);
}
if(shape.equals("圆")){
graphics.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
}
if (shape.equals("矩形")){
graphics.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
}
System.out.println(x1+" "+y1+" "+x2+" "+y2);
}
} catch (IOException e) {
e.printStackTrace();
}
}
现在做成这样子了,后续再把消息交流加上去