自学内容网 自学内容网

Java:网络编程

1. 基础的通信架构

基本的通信架构有2种形式C/S架构B/S架构,其中C/S架构即客户端/服务器架构,用户在使用时,需要额外安装客户端,当后台版本更新时,客户端也可能需要更新才能使用;B/S架构即浏览器/服务器架构,用户只需要在浏览器上输入对应的网址便可以使用。
网络通信的关键三要素ip地址、端口号、协议。其中ip地址也就是设备在网络中的地址,是唯一标识;端口号是应用程序在设备中的唯一标识;协议是连接和数据在网络中传输的规则。

1-1. IP地址

ip地址:全称为“互联网协议地址”,是分配给上网设备的唯一标识。ip地址有两种形式:IPv4、IPv6;其中IPv4采用4字节(32位)来记录这个地址:
在这里插入图片描述
IPv6共128位,分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号隔开;
在这里插入图片描述
电脑上在cmd命令上输入:ipconfig即可查询当前电脑的ipv4、ipv6地址。
在这里插入图片描述
在Java中获取到IP数据(InetAddress)

package Scoket_study;


import java.net.InetAddress;
import java.net.UnknownHostException;

public class ScoketStudy {


    public static void main(String[] args) throws UnknownHostException {


        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost.getHostName());
        // 获取到本机电脑名
        System.out.println(localHost.getHostAddress());
        // 获取到本机的ip地址

        InetAddress localHost2 = InetAddress.getByName("www.bilibili.com");
        System.out.println(localHost2.getHostAddress());

    }
}

在这里插入图片描述

1-2. 端口号

被规定为一个16位的二进制,范围为0~65535。
周知端口:0~1023,被预定义的知名应用占用(http占用80,ftp占用21);
注册端口:1024~49151,分配给用户进程或某些应用程序;
动态端口:49152~65535,不固定分配给某种进程,而是动态分配的。

自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序端口号相同(会出现端口号被占用的情况)。

1-3. 协议

TCP/IP网络模型:实际上国际标准。分为应用层、传输层、网络层、数据链路层和物理层。在应用层上,比如http、ftp、smtp;传输层比如UDP、TCP;网络层,比如ip;数据链路层和物理层,比如比特流
UDP:全称 User Datagram Protocol,也就是用户数据协议,不事先建立连接 ,数据按包发送,包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(最大为64kb)等;发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认。这种方式通信效率高,可以用在语音通话、视频直播等。
TCP:全称 Transmission Control Protocol,也就是传输控制协议,通过三次握手建立连接,传输数据进行确认,四次握手断开连接。
三次握手建立连接,客户端向服务器端发送请求,如果服务器端能接受到消息,会向客户端返回一个响应,以此表示服务器端能接收到客户端发送的请求,之后客户端再发送一个请求给服务器端,以此表示客服端能接收到服务器端返回的响应,从而建立起一个连接。
四次握手断开连接,客户端发送断开连接请求给服务器端,服务器收到请求之后,返回一个响应:稍等(因为此时服务器可能还有数据没有处理完);上一步执行完之后,会再返回一个响应:确认断开(用来确认客服端是否确认断开),客户端收到这个响应之后,发出正式确认断开连接。

2. UDP通信

在Java中提供了一个java.net.DatagreamSocket来实现UDP通信。
客户端发送数据参考代码如下:

package Scoket_study;


import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class Client1 {
    public static void main(String[] args) throws Exception {

        DatagramSocket socket = new DatagramSocket();
        // 创建客服端对象
        byte[] send_data = "见到你真高兴。。".getBytes();
        DatagramPacket packet = new DatagramPacket(send_data,
                send_data.length, InetAddress.getLocalHost(),6666);
        // 第一参数表示发送的数据字节码数组
        // 第二个参数表示发送的数据长度
        // 第三个参数表示发送到的服务器的ip地址
        // 第四个参数表示端口号
        // 创建数据包对象
        socket.send(packet);
        // 发送数据到服务器端
        System.out.println("客户端发送数据成功!");
        socket.close();
    }
}

服务器端发送数据参考代码如下:

package Scoket_study;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Server1 {

    public static void main(String[] args) throws Exception {

        DatagramSocket socket = new DatagramSocket(6666);
        // 创建服务器端,端口号为 6666
        byte[] recev = new byte[1024*64];// 64kb
        DatagramPacket packet = new DatagramPacket(recev, recev.length);
        socket.receive(packet);

        int len = packet.getLength();
        // 获取本次接收到的数据长度
        System.out.println("服务器端接收到的数据为:"+new String(recev,0,len));
        // 接收多少,输出多少
        socket.close();
    }
}

首先需要先执行服务器端的代码,然后再执行客户端的代码,运行结果如下:
请添加图片描述
如果想让上述两端可以反复接收消息,此时需要用到死循环,参考代码如下:

package Scoket_study.udp;


import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class Client1 {

    public static void main(String[] args) throws Exception {

        DatagramSocket socket = new DatagramSocket();
        // 创建客服端对象
        Scanner scanner = new Scanner(System.in);
        String msg;
        DatagramPacket packet;
        while(true){
            System.out.print("请输入:");
            msg = scanner.nextLine();
            if("quit".equals(msg)){
                System.out.println("客户端已关闭,欢迎下次使用!");
                socket.close();
                break;
            }

            byte[] send_data = msg.getBytes();
            packet = new DatagramPacket(send_data,
                    send_data.length, InetAddress.getLocalHost(),6666);
            // 第一参数表示发送的数据字节码数组
            // 第二个参数表示发送的数据长度
            // 第三个参数表示发送到的服务器的ip地址
            // 第四个参数表示端口号
            // 创建数据包对象
            socket.send(packet);
            // 发送数据到服务器端
            System.out.println("客户端发送数据成功!");
        }
    }
}

package Scoket_study.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Server1 {

    public static void main(String[] args) throws Exception {

        DatagramSocket socket = new DatagramSocket(6666);
        // 创建服务器端,端口号为 6666
        byte[] recev = new byte[1024*64];// 64kb
        DatagramPacket packet = new DatagramPacket(recev, recev.length);
        while(true){
            socket.receive(packet);
            int len = packet.getLength();
            // 获取本次接收到的数据长度
            System.out.println("服务器端接收到的数据为:"+new String(recev,0,len));
            // 接收多少,输出多少
        }
    }
}

请添加图片描述

3. TCP通信

在Java中提供了一个java.net.Socket类来实现TCP通信。
客户端的开发参考代码如下:

package Scoket_study.tcp;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("127.0.0.1",8888);
        // 参数为服务器端的ip地址和端口号
        OutputStream ops = socket.getOutputStream();
        DataOutputStream dops = new DataOutputStream(ops);
        // 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
        dops.writeUTF("你好!");
        dops.close();
        
        socket.close();
    }   
}

服务器端的开发参考代码如下:

package Scoket_study.tcp;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) throws IOException {

        ServerSocket serverSocket = new ServerSocket(8888);
        // 服务端
        Socket socket = serverSocket.accept();
        InputStream ips = socket.getInputStream();
        DataInputStream dips = new DataInputStream(ips);
        // 客户端使用的是数据输出流,因此这里需要使用数据输入流
        String s = dips.readUTF();
        System.out.println("客户端发送的消息为:"+s);
        System.out.println(socket.getRemoteSocketAddress());
        // 获取客户端的一些信息
        dips.close();
        socket.close();
    }
}


运行结果:
在这里插入图片描述
如果想要实现客户端能多发,服务器端能多收,此时需要使用到while死循环,参考代码如下:

package Scoket_study.tcp;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8888);
        // 服务端
        System.out.println("xxx服务端启动成功。。。。。。。。。。。");
        Socket socket = serverSocket.accept();
        InputStream ips = socket.getInputStream();
        DataInputStream dips = new DataInputStream(ips);
        // 客户端使用的是数据输出流,因此这里需要使用数据输入流
        while(true){
            try {
                String s = dips.readUTF();
                System.out.println("客户端发送的消息为:"+s);
            } catch (IOException e) {
                System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了。。。。");
                dips.close();
                socket.close();
                break;
            }
        }
        // 获取客户端的一些信息
    }
}

package Scoket_study.tcp;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {


        Socket socket = new Socket("127.0.0.1",8888);
        // 参数为服务器端的ip地址和端口号
        OutputStream ops = socket.getOutputStream();
        DataOutputStream dops = new DataOutputStream(ops);
        // 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
        Scanner scanner = new Scanner(System.in);
        System.out.println("xxxx客户端。。。。。。。");
        while(true){
            System.out.print("输入:");
            String msg = scanner.nextLine();
            if("quit".equals(msg)){
                dops.close();
                socket.close();
                System.out.println("退出成功!");
                break;
            }
            dops.writeUTF(msg);
            dops.flush();
            // 需要刷新流
        }


    }
}

请添加图片描述

4. TCP通信支持多个客户端通信

上述代码仅仅只能接收到一个客户端的消息,无法接收到多个客户端消息。为此,需要用到多线程技术。上述客户端的代码不需要做修改,只需要修改服务器端的代码即可,参考代码如下:

package Scoket_study.tcp;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8888);
        // 服务端
        System.out.println("xxx服务端启动成功。。。。。。。。。。。");

        while(true){
            Socket socket = serverSocket.accept();
            System.out.println("客户端:"+socket.getRemoteSocketAddress()+"上线了。。。。");
            SocketThread thread = new SocketThread(socket);
            thread.start();
        }

    }
}



class SocketThread extends Thread{
    private Socket socket;
    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream ips = socket.getInputStream();
            DataInputStream dips = new DataInputStream(ips);
            while(true){
                try {
                    String msg = dips.readUTF();
                    System.out.println("接收到客户端:"+socket.getRemoteSocketAddress()+"的消息:"+msg);
                } catch (IOException e) {
                    System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
                    dips.close();
                    socket.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

请添加图片描述

5. 使用TCP通信实现群聊功能

也就是一个客户端发送消息给服务器端,然后通过服务器端发送给其他客户端。在服务器端为了存储当前已经上线了客户端,这里需要用到集合来进行存储这些客户端。

package Scoket_study.tcp2;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;


public class Client {
    public static void main(String[] args) throws IOException {


        Socket socket = new Socket("127.0.0.1",8888);
        // 参数为服务器端的ip地址和端口号
        OutputStream ops = socket.getOutputStream();
        DataOutputStream dops = new DataOutputStream(ops);
        // 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
        Scanner scanner = new Scanner(System.in);
        System.out.println("xxxx客户端。。。。。。。");

        new ClientThread(socket).start();

        while(true){
            String msg = scanner.nextLine();
            if("quit".equals(msg)){
                dops.close();
                socket.close();
                System.out.println("退出成功!");
                break;
            }
            dops.writeUTF(msg);
            dops.flush();
            // 需要刷新流
        }
    }
}

// 客户端接收消息
class ClientThread extends Thread{
    private Socket socket;

    public ClientThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream ips = socket.getInputStream();
            DataInputStream dips = new DataInputStream(ips);
            while(true){
                try {
                    String msg = dips.readUTF();
                    System.out.println("接收到"+socket.getRemoteSocketAddress()+"客户端的消息:"+msg);
                } catch (IOException e) {
                    //System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
                    dips.close();
                    socket.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package Scoket_study.tcp2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {

    public static List<Socket> onLineSockets = new ArrayList<>();
    // 使用静态变量
    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8888);
        // 服务端
        System.out.println("xxx服务端启动成功。。。。。。。。。。。");

        while(true){
            Socket socket = serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("客户端:"+socket.getRemoteSocketAddress()+"上线了。。。。");
            SocketThread thread = new SocketThread(socket);
            thread.start();
        }

    }
}



class SocketThread extends Thread{
    private Socket socket;
    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream ips = socket.getInputStream();
            DataInputStream dips = new DataInputStream(ips);
            while(true){
                try {
                    String msg = dips.readUTF();
                    System.out.println("接收到客户端:"+socket.getRemoteSocketAddress()+"的消息:"+msg);
                    sendMsgToAll(msg);
                } catch (IOException e) {
                    System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
                    Server.onLineSockets.remove(socket);
                    dips.close();
                    socket.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 发送消息给其他客户端
    private void sendMsgToAll(String msg) throws IOException {

        for (Socket onLineSocket : Server.onLineSockets) {
            if(onLineSocket != socket){
                OutputStream ops = onLineSocket.getOutputStream();
                DataOutputStream dops = new DataOutputStream(ops);
                dops.writeUTF(msg);
                dops.flush();
            }
        }
    }
}

运行结果:
请添加图片描述
如果想要在Idea上运行一个文件多次,需要把这个设置点上才行哈!
请添加图片描述

6. TCP 实现B/S架构

package Scoket_study.tcp_bs;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8080);

        while(true){
            Socket socket = serverSocket.accept();
            new BsThread(socket).start();
        }
    }
}



class BsThread extends Thread{
    private Socket socket;
    public BsThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            OutputStream ops = socket.getOutputStream();
            PrintStream ps = new PrintStream(ops);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();
            ps.println("<div color='red' style='font-size:30px;font-family:楷体;'>爱我中华</div>");
            ps.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

此时在浏览器上输入:127.0.0.1:8080,即可查看运行结果:

在这里插入图片描述
但是上述代码还存在一个问题,那就是每访问一下这个地址,就需要新创建一个线程类对象,一旦多了,肯定会影响到性能的,因此考虑使用线程池

package Scoket_study.tcp_bs;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Server {
    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8080);


        ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS
        ,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        while(true){
            Socket socket = serverSocket.accept();

            pool.execute(new BsRunnable(socket));

        }
    }
}

class BsRunnable implements Runnable{
    private Socket socket;
    public BsRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            OutputStream ops = socket.getOutputStream();
            PrintStream ps = new PrintStream(ops);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();
            ps.println("<div color='red' style='font-size:30px;font-family:楷体;'>爱我中华</div>");
            ps.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

原文地址:https://blog.csdn.net/qq_45404396/article/details/144135310

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!