Java网络编程及Connection refused异常处理

Java网络编程及Connection refused异常

IP地址

​ 实现网络通信,首先必须确定通信双方的对象。因此知道两个元素:IP地址和端口号。通过IP地址和端口号就可以定位到一台确定的主机上。

​ IP的作用是唯一定位一台网络上的计算机。IP地址的分类方式有两种:

  • 通过IP地址分类(IPV4/IPV6):

    • IPV4:由4个字节组成,如127.0.0.1 ,每个字节的长度为0-255;
    • IPV6:IPv6的128位地址是按照每16位划分为一段,因此总共有8段,每段被转换为一个4位十六进制数,并用冒号隔开。形如fe80::1165:d1f:7522:87f1%5。一个或多个连续的16比特为0字符时,为了缩短地址长度,用 :: 表示,但一个IPv6地址中只允许一个:: 。
  • 通过公网地址和私网地址分类:

    • 公网是给互联网使用的,私网是给局域网使用的。

    在Java中,有一个InetAddress类,专门用于操作IP相关的属性。需要注意的是,该类没有构造方法,其方法都为静态方法,可直接调用。常用方法如下:

getAddress() :返回此 InetAddress 对象的原始 IP 地址。

getByAddress(String host, byte[] addr) :根据提供的主机名和 IP 地址创建 InetAddress。

getByName(String host) :在给定主机名的情况下确定主机的 IP 地址。 
//实例:获取本机IP
InetAddress.getByName("localhost");
InetAddress.getByName("127.0.0.1")getLocalHost() :返回本地主机。

端口Port

​ 端口表示一个程序的进程,不同的进程有不同的端口号,端口号的范围被定位为0~65535。端口分类如下:

  • 共有端口0~1023 (不建议使用)

    • HTTP:80
    • HTTPS:443
    • FTP:21
    • Telent:23
  • 程序注册端口:1024~49151

    • Tomcat:8080
    • MySQL:3306
    • Oracle:1521
  • 动态、私有端口:49152~65535

    常用相关DOS命令:

netstat - ano #查看所有端口

netstat - ano|findstr "5900"//查看指定(5900)的端口

tasklist|findstr "8696" //查看指定端口的进程

​ 假设两台机器上分别存在两个进程:QQ(假设端口号为8570),MSN(假设端口号为9527),机器1上的QQ要给机器2的QQ发消息,那么机器1需要先通过IP地址定位主机2,然后再通过端口号8570找到QQ进程,这样才能进行消息的收发。假如消息发错了,比如发给了端口号为9527的MSN,那么此消息会作废。

​ Java中也有与端口相关的类InetSocketAddress,该类存在构造方法:

InetSocketAddress(InetAddress addr, int port):根据 IP 地址和端口号创建套接字地址

InetSocketAddress(int port) :创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。

InetSocketAddress(String hostname, int port) :根据主机名和端口号创建套接字地址。

​ 常用方法如下:

getAddress() :获取 InetAddress。

getHostName():获取 hostname。

getPort():获取端口号。

编程实现通信协议

TCP

​ 客户端步骤:

  1. 连接socket服务器;
  2. 发送消息。
public class TCPClientDemo01 {
    public static void main(String[] args) {
        Socket socket = null;
        OutputStream outputStream = null;

        try {
            InetAddress localHost = InetAddress.getByName("127.0.0.1");
            int port = 9999;
            socket = new Socket(localHost, port);
            outputStream = socket.getOutputStream();
            outputStream.write("老八秘制小汉堡".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务器:

  1. 建立服务端口;
  2. 等待(accept)用户的连接;
  3. 接收用户的消息。
public class TCPServerDemo01 {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream inputStream = null;
        ByteArrayOutputStream bos = null;

        try {
            serverSocket = new ServerSocket(9999);
            while (true) {
                socket = serverSocket.accept();
                inputStream = socket.getInputStream();
                //利用内存操作流的缓冲器特性,防止传输中文时出现乱码
                bos = new ByteArrayOutputStream();
                int len = 0;
                byte[] bytes = new byte[1024];
                while ((len = inputStream.read(bytes)) != -1) {
                    bos.write(bytes);
                }
                System.out.println(bos.toString());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //socket也需要进行关闭,并且遵循一个原则:先开后关
            if (bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

注意:**编译时一定要先运行服务端,再运行客户端,否则会报java.net.ConnectException: Connection refused: connect异常。**这是初学时一个比较容易犯的问题。

文件上传

​ 大致步骤同上,但是需要注意传输什么类型的数据就需要使用什么类型的IO流。

​ 客户端:

public class TCPClientDemo02 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9527);
        OutputStream outputStream = socket.getOutputStream();
        //因为处理的是文件,因此需要字节流来读取数据
        FileInputStream fis = new FileInputStream("student.txt");
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes))!= -1){
            outputStream.write(bytes);
        }

        /*写完数据后必须要告诉服务端,否则会陷入死循环:
        客户端发送完数据后一直再等待服务端发送接收完毕信号,
        而服务端则一直再等在发送端发送下一个文件
        */
        socket.shutdownOutput();

        //确定服务端接收完毕才断开连接
        InputStream inputStream = socket.getInputStream();
        //接收到的是byte类型的数据,因此用内存操作流来接收
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len2 = 0;
        while ((len2 = inputStream.read(buffer))!= -1){
            byteArrayOutputStream.write(buffer,0,len2);
        }
        System.out.println(byteArrayOutputStream.toString());

        fis.close();
        outputStream.close();
        socket.close();
        inputStream.close();
        byteArrayOutputStream.close();
    }
}

服务端:

public class TCPServerDemo02 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9527);
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        //输出文件需要字节流来输出
        FileOutputStream fos = new FileOutputStream("Student2.txt");
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buffer))!= -1){
            fos.write(buffer,0,len);
        }

        //通知客户端接受完毕
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("接收完毕".getBytes());

        fos.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
        outputStream.close();
    }
}

UDP

​ UDP传输不需要连接服务器,只需要知道发送和接收端口就可以进行数据传输。UDP其实没有发送端接收端之分,服务器 端可以是客户端,客户端也可以是服务端。

实现数据传输

//客户端
public class UDPClientDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建DatagramSocket对象
        DatagramSocket socket = new DatagramSocket(9527);

        //2.将要发送的数据封包
        byte[] bytes = "Hello".getBytes();

        DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length,InetAddress.getByName("127.0.0.1"), 8578);

        //3.发送数据包
        socket.send(packet);

        socket.close();
    }
}
//服务端
public class UDPServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建DatagramSocket对象
        DatagramSocket socket = new DatagramSocket(8578);

        //2.将接收到的数据存入接收包
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);

        //接收数据包
        socket.receive(packet);

        System.out.println(new String(packet.getData()));

        socket.close();
    }
}

多线程聊天实现:

//发送方
public class SendMSG implements Runnable {
    DatagramSocket socket = null;
    BufferedReader bR = null;
    DatagramPacket packet = null;
    //自己端口号
    private int fromPort;
    //接收方的端口号
    private int recievePort;
    //接收方的IP地址
    private String recieveIP;
	//构造函数
    public SendMSG(int fromPort, int recievePort, String recieveIP) {
        this.fromPort = fromPort;
        this.recievePort = recievePort;
        this.recieveIP = recieveIP;
        try {
            socket = new DatagramSocket(fromPort);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            //读取一句话,因为高效字符流有readline方法,故使用
            //因为要从键盘读取,因此使用字符流
            bR = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                //读取输入的话
                String s = bR.readLine();
                //要封包必须是字节类型,因此转换称字节数组形式
                byte[] bytes = s.getBytes();
                packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress(this.recieveIP, this.recievePort));
                //发送数据
                socket.send(packet);
                //收到bye则结束聊天
                if (s.equals("bye")) {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bR != null) {
                try {
                    bR.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket.close();
        }
    }
}
//接收方
public class RecieveMSG implements Runnable {
    //本地端口号
    private int fromPort;
    //消息的发送者
    private String msgFrom;
    DatagramSocket socket = null;

    public RecieveMSG(int fromPort,String msgFrom) {
        this.fromPort = fromPort;
        this.msgFrom = msgFrom;
        try {
            socket = new DatagramSocket(fromPort);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            while (true) {
                //该字节数组1个人理解是为了满足DatagramPacket构造函数而创建
                byte[] bytes = new byte[1024];
                DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
				//接收包
                socket.receive(packet);
                //将包中的数据存入新的字节数组2
                byte[] reciver = packet.getData();
                //转为字符串形式输出
                String s = new String(reciver, 0, reciver.length);
                System.out.println(msgFrom + ":" + s);
                //收到bye则退出聊天
                if (s.trim().equals("bye")) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }
}

​ 以学生和老师为例,主函数如下:

//学生窗口
public class TalkStudent {
    public static void main(String[] args) {
        new Thread(new SendMSG(9527,8888,"localhost")).start();
        new Thread(new RecieveMSG(9999,"老师")).start();
    }
}

//老师窗口
public class TalkTeacher {
    public static void main(String[] args) {
        new Thread(new SendMSG(8570,9999,"localhost")).start();
        new Thread(new RecieveMSG(8888,"学生")).start();
    }
}

​ 需要注意的是填写的端口:对于发送方,不能直接使用接收方的端口号作为信息的接收者,否则会出现端口占用异常,因此需要一个间接端口(如8888)来发送。

​ 学生发送给老师消息的流程为:学生(端口号9527)将数据发送给端口8888,老师(端口号8570)从端口8888接收消息。

你可能感兴趣的:(TCP/IP协议族)