自学内容网 自学内容网

网络编程(JavaEE)

前言:

        熟悉了网络的基本概念之后,接下来就需要针对网络进行一系列的编程,其中可能涉及到新的一些编程操作,需要我们进一步探索!

网络编程套接字:

        套接字其实是socket的翻译。

        操作系统给应用程序(传输层给应用层)提供的API,起了个名字就叫socket。

        接下来探究的就是操作系统提供的API,socket的相关内容:

        socketAPI提供了两种:

        一种是针对UDP协议提供的API,另一种是针对TCP协议提供的API。

UDP\TCP:

        所谓的UDP和TCP针对传输层的两套协议,有什么不同的地方吗?

        TCP:有链接,可靠传输、面向字节流、全双工。

        UDP:无连接、不可靠传输、面向数据报、全双工。

1.有链接/无连接:通信双方如果保存了对端的信息就是有链接,如果有一端没有保存就是无连接。

2.可靠/不可靠传输:可靠是指接收方尽可能的接收到发送端的信息,不可靠是指发送方将信息发出后再不去干预。

TCP内置了一些机制确保可靠传输:

1、感知到对方是否接收到信息了。

2、重传机制,在对方没收到信息的时候进行重试。

3.面向字节流/数据报:

TCP面向字节流,此时传输数据就和文件流一个特点了。可以选择多传几次一次传n个字节。

UDP面向数据报,此时传输数据就是以数据报为单位,一次发送完整一个数据包,就必须完整接收一个数据包。

4.全双工/半双工:

全双工:一个通信链路可以发送数据,也可以接收数据(双向通信)

半双工:一个通信链路只可以发送/接收数据(单向通信)。

UDP的API:

        直接操作网卡不太好操作,引入了socket对象,通过操作socket对象简介操作网卡。

        系统的socket api在Java中又做了进一步的封装:DatagramSocket、DatagramPacket

对于UDP来说,由于发送和接收数据都是以数据报为单位,我们需要用到数据报类:DatagramPacket

echo Server:

        通过UDP协议提供的API写一份网络编程的代码,其中涉及到客户端和服务器,在这里服务器不做任何复杂的业务,只是将客户端发来的内容显示出来就好,这样的服务器也被称为"回显服务器"。

        

public class UdpEchoSever {
    private DatagramSocket socket;
    //该异常是IOException的子类
    public UdpEchoSever(int port) throws SocketException {
        //此处在运行服务器的时候需要制定一个端口号
        socket = new DatagramSocket(port);
    }
    //使用该方法启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器要持续不断地工作,这里使用while循环处理
        while(true) {
            //一个服务器要做的三件事情:
            //1、读取请求并解析
            //发送和接收数据数据报
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            //如果网卡上没有数据,receive就会阻塞等待
            socket.receive(requestPacket);
            //上述接收到的是二进制的byte数组,此时转成字符串才好处理
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求计算出响应
            String response = getProcess(request);
            //3.把响应写回到客户端
            //由于是无连接通信,DatagramSocket对象中不持有对方的IP和端口号,要想发送过去必须手动告诉客户端的IP和端口号
            DatagramPacket datagramPacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(datagramPacket);
            //此时记录日志方便观察程序执行效果
            System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getSocketAddress(),response);
        }
    }

    private static String getProcess(String request) {
        return request;
    }
    
}

服务器写完之后,就开始写UDP相关的客户端代码:

echo Client:

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String severIp;
    private int severPort;
    //指定服务器的ip和服务器的端口
    public UdpEchoClient(String ip,int port) throws SocketException {
        this.severIp = ip;
        this.severPort = port;
        //此处让系统自动分配出一个空着的端口来
        socket = new DatagramSocket();
    }
    //让客户端反复读从控制台读取内容,构造成UDP发送给服务器
    //最终再显示在客户端的屏幕上
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客户端启动!");
        while(true) {
            //1.从控制台读取用户输入的内容
            System.out.print("->");
            String request = scanner.next();
            //2.构造请求对象并发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(severIp),severPort);
            socket.send(requestPacket);
            //3.读取出服务器的响应,并解析响应内容
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
        udpEchoClient.start();
    }
}

TCP的API:

        注意:

        此时的服务器的相关socket子类不再是DatagramSocket,而是SeverSocket

        此时的客户端的相关socket子类不再是DatagramSocket,而是Socket

        此时不再有类似DatagramPacket去描述一个数据报。

原因在于:

        1、此时TCP发送和接收的是字节流

        2、此时TCP是有链接通信,双方都需要保存对方的信息(IP+端口号)

        echoServer:

public class TcpEchoServer {
    ServerSocket socket = null;
    public TcpEchoServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }
    public void start() throws IOException {
        //接收信号
        System.out.println("服务器启动!");
        while(true) {
            Socket clientSocket = socket.accept();
            //处理连接
            ProcessConnection(clientSocket);
        }
    }
    private void ProcessConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s,%d]: 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //Stock内部包含两个字节流,拿到这两个字节流就可完成接下的读写操作
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
        //读请求,解析请求,可能有多次请求
            while(true) {
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    //读取完毕客户端下线
                    System.out.printf("[%s,%d]: 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                String request = scanner.nextLine();
                System.out.println(request);
                //计算响应
                String response = process(request);
                //将计算后的响应写回socket
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                //刷新缓冲区
                printWriter.flush();
                System.out.printf("[%s:%d] req:%s resp:%s",clientSocket.getInetAddress(),clientSocket.getPort()
                ,request,response);
            }
        }catch(IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

echoClient:

public class TcpEnchoClient {
    private Socket socket = null;
    public TcpEnchoClient(String severIp,int serverPort) throws IOException {
        //与服务器建立连接
        socket = new Socket(severIp,serverPort);
    }
    public void start() {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
                while(true) {
                    //获取请求
                    String request = scanner.nextLine();
                    //传入请求
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    //清空缓冲区
                    printWriter.flush();
                    //获取回应,以字符串的方式读
                    Scanner scanner1 = new Scanner(inputStream);
                    String response = scanner1.nextLine();
                    System.out.println(response);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    }

    public static void main(String[] args) throws IOException {
        TcpEnchoClient tcpEnchoClient = new TcpEnchoClient("127.0.0.1",9090);
        tcpEnchoClient.start();
    }
}

大致的流程和UDP是类似的,其中需要时注意的就是,这是有连接通信,通过Socket类中提供的InputStream和OutputStream进行读写操作!不再使用SocketPacket类进行读写操作。

当然以上的服务器的设计只会针对一个客户端提供服务,如果一个客户端没有结束进程,那么其他的客户端就会阻塞等待!直到上一个客户端进程结束。

为了确保能为多个客户端提供服务,接下来引入多线程:

多线程echoSever:

        我们需要确保每一个客户端都能有服务器给予服务,可以使用多线程的方式解决上述问题:

        1.普通多线程:

public void start() throws IOException {
        //接收信号
        System.out.println("服务器启动!");
        while(true) {
            Socket clientSocket = socket.accept();
            Thread t = new Thread(()->{
                //处理连接
                try {
                    ProcessConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

         2.引入线程池的多线程:

    public void start() throws IOException {
        //接收信号
        System.out.println("服务器启动!");
        //引入线程工场
        ExecutorService service = Executors.newCachedThreadPool();
        while(true) {
            Socket clientSocket = socket.accept();
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        ProcessConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }


原文地址:https://blog.csdn.net/m0_75235246/article/details/144170995

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