04 网络编程

传输层协议(貌似只有2个) UDP, TCP,  因为 UDP 不安全, 所以 TCP 肯定会广泛使用.

UDP

传输层协议, UDP 可能丢包, 不安全

这个传输一次最多是 60 K 的数据.

发送端

 

package com.leon.Network;

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

public class MyUdpSend {

    public static void main(String[] args) throws Exception {
        System.out.println("开始发送数据......");
        //1. 使用 datagramSocket 指定端口, 创建发送端
        DatagramSocket client = new DatagramSocket(8888);
        //2. 准备数据, 一定要转成字节数组
        String data = "asdfasdf";
        byte[] datas = data.getBytes();
        //3. 封装 DatagramPacket 包裹,需要指定目的地
        DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, 
                new InetSocketAddress("localhost",9999));
        //4. 发送包裹send(DatagramPacket p)
        client.send(packet);
        //5. 释放资源
        client.close();
        System.out.println("结束发送数据......");
    }
}

 接收端

package com.leon.Network;

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

public class MyUdpReceive {

    public static void main(String[] args) throws Exception {
        System.out.println("开始接收数据......");
        //1. 使用 datagramSocket 指定端口, 创建接收端
        byte[] container = new byte[1024*60];
        DatagramSocket server = new DatagramSocket(9999);
        //2. 准备容器, 封装 DatagramPaket包裹
        DatagramPacket packet = new DatagramPacket(container, 0, container.length);
        //3. 阻塞式收包裹
        server.receive(packet);    // 阻塞拿数据
        //4. 分析数据 byte[] getData(), getLength()
        byte[] datas = packet.getData();
        int len = packet.getLength();
        System.out.println(new String(datas, 0, len));
        //5. 释放资源
        server.close();
        System.out.println("结束接收数据......");
    }
}

所以, 如果是上传文件, 利用 UDP 也是没有问题的,基本步骤不变, 只是需要把文件转换成字节数组存储并转发.

TCP

传输层协议: 面向连接, 安全可靠.

服务器端基本步骤:

package com.leon.Network;

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

public class ChatServer {
    public static void main(String[] args) throws Exception {
        System.out.println("------Start Server-----");
        // 1. 指定端口, 使用ServerSocket 创建服务器
        ServerSocket server = new ServerSocket(8888);
        // 2. 阻塞式的接收
        Socket rec = server.accept();
        System.out.println("一个客户建立了连接");
        // 3. 接收消息
        DataInputStream dis = new DataInputStream(rec.getInputStream());
        String msg = dis.readUTF();
        // 4.返回消息
        DataOutputStream dos = new DataOutputStream(rec.getOutputStream());
        dos.writeUTF(msg);
        dos.flush();
        // 5.释放资源
        dos.close();
        dis.close();
        rec.close();
    }
}

客户端基本步骤:

package com.leon.Network;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.Buffer;

public class ChatClient {
    public static void main(String[] args) throws Exception, IOException {
        System.out.println("---- start client ----");
        //1. 建立连接, Socket创建, 服务器地址和端口
        Socket client = new Socket("localhost", 8888);
        //2. 客户端发送消息
        BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
        DataOutputStream dos = new DataOutputStream(client.getOutputStream());
        DataInputStream dis = new DataInputStream(client.getInputStream());
        boolean isRunning = true;
        while (isRunning) {
            String msg = console.readLine();
            dos.writeUTF(msg);
            dos.flush();
            //3. 获取消息
            msg = dis.readUTF();
            System.out.println(msg);
        }
        // 4.释放资源
        dos.close();
        dis.close();
        client.close();
    }
}

实例聊天室

 将上面代码重构一下, 写的更合理一些, 比如通过增加一些类, 封装一些功能

私聊, 群聊 都不难, 主要是建立连接的问题. 

另外, 一定要注意 socket 连接时, 断开连接的问题, 因为实际上 server 和 client 都分配了资源, 如果要断开连接, 必须获得双方的认可.

下边例子中没有空滤这一点, 如果真正要开发 socket 编程的话. 一定要特别注意断开连接的代码.

package com.leon.Network;

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

public class ChatServer2 {

    public static void main(String[] args) throws Exception {
        System.out.println("------Start Server-----");
        // 1. 指定端口, 使用ServerSocket 创建服务器
        ServerSocket server = new ServerSocket(8888);
        while (true) {
            // 2. 阻塞式接收
            Socket client = server.accept();
            System.out.println("一个客户建立了连接");
            new Thread(new Channel(client)).start();
        }
    }
}

class Channel implements Runnable {
    // 3. 接收消息
    // 4. 发送消息
    // 5. 释放消息
    private DataInputStream dis;
    private DataOutputStream dos;
    private Socket client;
    private boolean isRunning = false;
    
    public Channel(Socket client) {
        this.client = client;
        try {
            this.dis = new DataInputStream(client.getInputStream());
            this.dos = new DataOutputStream(client.getOutputStream());
            isRunning = true;    // 分配信道时, 激活, 可以开始通信
        } catch (Exception e) {
            System.out.println("-- Server Channel constructor error --");
            isRunning = false;
            release();
        }
        
    }
    // 接收消息
    public String receive() {
        String msg = "";
        try {
            msg = dis.readUTF();
            System.out.println("server received: " + msg);
        } catch (IOException e) {
            System.out.println("-- Server Channel receive error --");
            isRunning = false;
            release();
        }
        return msg;
    }
    // 发送消息
    public void send(String msg) {
        try {
            dos.writeUTF(msg);
            dos.flush();
        } catch (IOException e) {
            System.out.println("-- Server Channel send error --");
            isRunning = false;
            release();
        }
        
    }
    // 释放资源
    public void release() {
        this.isRunning = false;
        CloseUtility.close(dis, dos, client);
    }
    @Override
    public void run() {
        while (isRunning) {
            String msg = receive();
            if (!msg.equals("")) {
                System.out.println("server send message:" + msg);
                send(msg);
            }
        }
    }
}
package com.leon.Network;

import java.io.Closeable;
import java.io.IOException;

public class CloseUtility {
    
    public static void close(Closeable... targets) {
        for (Closeable target: targets) {
            try {
                if (null != target) {
                    target.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
package com.leon.Network;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.Buffer;

public class ChatClient2 {

    public static void main(String[] args) throws Exception, IOException {
        System.out.println("---- start client ----");
        //1. 建立连接, Socket创建, 服务器地址和端口
        Socket client = new Socket("localhost", 8888);
        //2. 客户端发送消息
        new Thread(new ClientSend(client)).start();
        // 3. 客户端接收消息
        new Thread(new ClientReceive(client)).start();    
    }
}
package com.leon.Network;

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

//封装客户端接收 data 的功能
public class ClientReceive implements Runnable{
    private Socket client;
    private DataInputStream dis;
    private boolean isRunning = false;
    
    @Override
    public void run() {
        while (isRunning) {
            String msg = receive();
            if (!msg.equals("")) {
                System.out.println(msg);
            }
        }
    }
    public ClientReceive(Socket client) {
        this.client = client;
        try {
            dis = new DataInputStream(client.getInputStream());
            isRunning = true;
        } catch (IOException e) {
            System.out.println("== clientReceive constructor error ==");
            release();
        }
    }
    
    private String receive() {
        String msg = "";
        try {
            msg = dis.readUTF();
        } catch (IOException e) {
            System.out.println("== client receive error ==");
            release();
        }
        return msg;
    }
    
    // 4. 释放资源
        private void release() {
            isRunning = false;
            CloseUtility.close(dis, client);
        }

}
package com.leon.Network;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

// 封装客户端发送 data 的功能
public class ClientSend implements Runnable {
    //2. 客户端发送消息
    BufferedReader console;
    DataOutputStream dos;
    private Socket client;
    private boolean isRunning = false;
    public ClientSend(Socket client) {
        this.client = client;
        console = new BufferedReader(new InputStreamReader(System.in));
        isRunning = true;
        try {
            dos = new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            System.out.println("ClientSend constructor error");
            isRunning = false;
            this.release();
        }
    }
    
    @Override
    public void run() {
        while (isRunning) {
            String msg = getStrFromConsole();
            System.out.println(msg + " client input itself");
            if (!msg.equals("")) {
                send(msg);
            }
        }
        
    }
    // 发送消息
    private void send(String msg) {
        try {
            dos.writeUTF(msg);
            dos.flush();
        } catch (IOException e) {
            System.out.println("client send error");
            release();
        }
        
    }
    
    // 从控制台获取输入
    private String getStrFromConsole() {
        try {
            return console.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
    
    // 4. 释放资源
    private void release() {
        isRunning = false;
        CloseUtility.close(dos, client);
    }
}

关于手写 web server

1. http 通信, 实际上底层是 TCP 通信, 所以也是要写 socket, 然后根据通信的内容 msg, 抽取 http 协议的相关内容.

2. 手写一个 xml 来配置路由器, 通过程序调取 xml, 来找到对象的 servlet. (程序获取到了请求, 需要寻找响应函数)

3. 手写 request, response 来封装 http 请求 和 应答

4. 手写具体的 servlet, 这里需要传进参数 request, response. 然后实现业务逻辑(doGet, doPost)

5. 做一个多线程的分发器, 接收服务器发送消息的请求

原文地址:https://www.cnblogs.com/moveofgod/p/12470387.html