计算机网络 — 网络编程

1. 网络编程

1.1 什么是网络编程

网络编程 : 指网络上的主机,通过不同的进程,以编程的方式实现网络通信.
简单来说: 网络编程就是通过代码的方式来控制不同进程间能够进行数据交互.

1.2 发送端和接收端

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端发送端和接收端两端,也简称为收发端

1.3 请求和响应

请求(Request) : 客户端给服务端发送的数据
响应(Response) : 服务端给客户端返回的数据

1.4 客户端和服务端

客户端 : 主动发送请求的一方
服务端 : 被动接受请求的一方

客户端和服务端的交互方式:

  1. 一问一答,客户端发送一个请求,服务端给一个响应
  2. 一问多答,客户端发送一个请求,服务端给多个响应
  3. 多问一答,客户端发送多个请求,服务端给一个响应
  4. 多问多答,客户端发送多个请求,服务器端多个响应

2. Socket 套接字

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程.

2.1 分类

流套接字 : 使用传输层 TCP 协议

TCP协议,(Transmission Control Protocol)
TCP的特点:

  1. 有连接
  2. 可靠传输
  3. 面向字节流
  4. 有接收缓冲区,也有发送缓冲区
  5. 大小不限
  6. 全双工

数据报接字 : 使用传输层 UDP 协议

UDP协议,(User Datagram Protocl)
UDP的特点:

  1. 无连接
  2. 不可靠传输
  3. 面向数据报
  4. 有接收缓冲区,无发送缓冲区
  5. 大小受限: 一次最多传输64k
  6. 全双工

3. UDP数据报套接字编程

3.1 DatagramSocket API

DatagramSocket 是UDP Socket, 用于发送和接收UDP数据报.

构造方法

方法签名 方法说明
DatagramSocket() 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port) 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

方法

方法签名 方法说明
void receive(DatagramPacket p) 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p) 从此套接字发送数据报包(不会阻塞等待,直接发送)
void close() 关闭此数据报套接字

3.2 DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报.

构造方法

方法签名 方法说明
DatagramPacket(byte[] buf, int length) 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号

方法

方法签名 方法说明
InetAddress getAddress() 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort() 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData() 获取数据报中的数据

注: 构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress的构造方法

方法签名 方法说明
InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址,包含IP地址和端口号

3.3 基本使用方法:

服务端:

  1. 创建一个 DatagramSocket 对象,创建的同时关联一个端口号
  2. 读取请求 并解析
  3. 根据请求计算响应
  4. 把响应写回到客户端
  5. 打印日志

客户端

  1. 创建一个 DatagramSocket 对象,创建的同时指定服务器的ip和端口号
  2. 读取输入的数据
  3. 构造请求 并 发送给服务器
  4. 从服务器读取响应
  5. 把数据显示给用户

使用示例: 一发一收

代码示例: UdpServer

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

public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 1. 读取请求 并 解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            // 2. 根据请求计算响应
            String response = process(request);
            // 3. 把响应写回到客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                                                                requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印日志
            String log = String.format("[%s:%d] req: %s; resp: %s",requestPacket.getAddress().toString(),
                                                                requestPacket.getPort(),request,response);
            System.out.println(log);
        }
    }

    private String process(String request) {
        return request;
    }

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

代码示例: UdpClient

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

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        this.socket = new DatagramSocket();
    }

    public void start() throws IOException {
        while (true) {
            // 1. 读取输入数据
            System.out.print("->");
            Scanner sc = new Scanner(System.in);
            String request = sc.next();
            if(request.equals("exit")){
                System.out.println("exit");
                break;
            }

            // 2. 构造请求 并 发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
                                                        InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            // 3. 读取服务器的响应 并 解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0, responsePacket.getLength());

            // 4.显式给用户
            String log = String.format("req: %s; resp: %s",request,response);
            System.out.println(log);
        }
    }

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

运行结果:
在这里插入图片描述

使用示例: 翻译程序

客户端 输入需要查找的英文的请求
客户端 返回对应的英文翻译的响应
代码示例: UdpServer

package Translation;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

public class UdpTranslateServer {
    private DatagramSocket socket = null;
    private Map<String,String> map = new HashMap<>();

    public UdpTranslateServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
        map.put("translate","翻译");
        map.put("china","中国");
        map.put("hello","你好");
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 1. 读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            // 2. 根据请求计算响应
            String response = process(request);
            // 3. 把响应写回给客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                                                                requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印日志
            String log = String.format("[%s:%d] req: %s; resp: %s",requestPacket.getAddress().toString(),
                                        requestPacket.getPort(),request,response);
            System.out.println(log);
        }
    }

    private String process(String request) {
        return map.getOrDefault(request,"查无此单词");
    }

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

代码示例: UDPClient

package Translation;

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

public class UdpTranslateClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    public UdpTranslateClient (String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        while (true) {
            System.out.print("->");
            Scanner sc = new Scanner(System.in);
            // 1. 根据用户的输入 构造请求
            String request = sc.next();
            if(request.equals("exit")){
                System.out.println("exit!");
                return;
            }
            // 2. 发送请求给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                                                    InetAddress.getByName(serverIp),serverPort);
            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);
            // 4. 解析响应并显式
            String log = String.format("req: %s; resp: %s",request,response);
            System.out.println(log);
        }
    }

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

运行结果:
在这里插入图片描述

4. TCP流套接字编程

4.1 ServerSocket API

构造方法

方法签名 方法说明
ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口

方法

方法签名 方法说明
Socket accept() 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close() 关闭此套接字

4.2 Socket API

构造方法

方法签名 方法说明
Socket(String host, int port) 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

方法

方法签名 方法说明
InetAddress getInetAddress() 返回套接字所连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

4.3 基本使用方法

服务器

  1. 创建ServerSocket 关联上一个端口号
  2. 调用 ServerSocketaccept 方法
    • 目的是 建立连接
    • 会返回一个 Socket 实例,称为 clientSocket
  3. 使用 clientSocketgetInputStreamgetOutputStream 得到字节流对象,进行读写和写入
    • 读取请求 并 解析
    • 根据请求计算响应
    • 把响应写回客户端
    • 打印日志
  4. 当客户端断开连接之后,服务器就应该要及时的关闭 clientSocket. (防止出现文件泄露的情况)

客户端

  1. 创建一个 Socket 对象.创建的同时指定服务器的 ip端口
  2. 客户端就可以通过 Socket 对象的 getInputStreamgetOutputStream 来和服务器进行通信
    • 从键盘上,读取用户输入的内容
    • 把这个读取的内容构造成请求,发送给服务端
    • 从服务器读取响应并解析
    • 把结构显示到界面上

使用示例1: 一发一收

这里的是普通版本 不能处理多个客户端

代码示例: TCPClient

package TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;
    private String serverIp;
    private int serverPort;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        // 让 socket 创建的同时,就和服务器尝试建立连接
        this.socket = new Socket(serverIp,serverPort);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            while (true) {
                // 1. 从键盘上,读取用户输入的内容
                System.out.print("->");
                String request = scanner.next();
                if (request.equals("exit")){
                    break;
                }
                // 2. 把这个读取的内容构造成请求,发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                // 3. 从服务器读取响应并解析
                Scanner respScanner = new Scanner(inputStream);
                String response = respScanner.next();
                // 4. 把结果显示到界面上
                String log = String.format("req: %s; resp: %s",request,response);
                System.out.println(log);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

代码示例: TCPServer

package TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    private ServerSocket listenSocket = null;

    public TcpEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 1. 建立连接
            Socket clientSocket = listenSocket.accept(); // 没有客户端来就会 阻塞等待
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        String log = String.format("[%s:%d] 客户端上线!",
                clientSocket.getInetAddress().toString(),clientSocket.getPort());
        System.out.println(log);
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            while (true) {
                // 1. 读取请求并解析
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    log = String.format("[%s:%d] 客户端下线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    System.out.println(log);
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(response);
                writer.flush();

                log = String.format("[%s:%d] req: %s; resp: %s",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
                System.out.println(log);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }

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

使用示例2: 多个客户端发 一个服务器收

多线程版本,能处理多个客户端,但是需要频繁的创建销毁线程
代码示例 TCPServer (客户端一致)

package TCPThread;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpThreadEchoServer {
    private ServerSocket listenSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = listenSocket.accept();
            // 创建一个线程来给这个客户提供服务
            Thread t = new Thread(){
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
            t.start();
        }
    }
    public void processConnection(Socket clientSocket) throws IOException {
        // 1. 打印日志
        String log = String.format("[%s,%d] 客户端上线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        System.out.println(log);
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()){
            while (true) {
                // 1. 读取 请求 并 解析
                Scanner sc = new Scanner(inputStream);
                if(!sc.hasNext()){
                    log = String.format("[%s,%d] 客户端下线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    System.out.println(log);
                    break;
                }
                String request = sc.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回客户端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
                // 4. 打印日志
                log = String.format("[%s:%d] req: %s; resp: %s",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
                System.out.println(log);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();
        }
    }

    public String process(String request){
        return request;
    }

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

使用示例3: 多个客户端发 一个服务器收(优化版)

由于多线程版的创建销毁线程的开销太大,这里使用线程池的方法.

package TCPThread;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpThreadPoolEchoServer {
    private ServerSocket listenSocket = null;

    public TcpThreadPoolEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = listenSocket.accept();
            // 使用线程池 来 处理当前的 processConnextion
            executorService.submit(()-> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }
    public void processConnection(Socket clientSocket) throws IOException {
        // 1. 打印日志
        String log = String.format("[%s,%d] 客户端上线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        System.out.println(log);
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()){
            while (true) {
                // 1. 读取 请求 并 解析
                Scanner sc = new Scanner(inputStream);
                if(!sc.hasNext()){
                    log = String.format("[%s,%d] 客户端下线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    System.out.println(log);
                    break;
                }
                String request = sc.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回客户端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
                // 4. 打印日志
                log = String.format("[%s:%d] req: %s; resp: %s",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
                System.out.println(log);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();
        }
    }

    public String process(String request){
        return request;
    }


}

运行结果
在这里插入图片描述

使用示例4: 翻译程序

代码示例: TCPServer (这里的客户端还是跟前面一样)

package Translation;

import TCPThread.TcpThreadPoolEchoServer;

import java.io.IOException;
import java.util.HashMap;

// echo 和 translate 两个服务器之间,大多都是类似的
public class TcpTranslateServer extends TcpThreadPoolEchoServer {
    private HashMap<String,String> map = new HashMap<>();

    public TcpTranslateServer(int port) throws IOException {
        super(port);
        map.put("china","中国");
        map.put("hello","你好");
        map.put("translate","翻译");
    }

    @Override
    public String process(String request) {
        return map.getOrDefault(request,"查无此单词!");
    }

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

运行结果:
在这里插入图片描述

5. 网络计算器(自定义协议)

我们自定义协议
请求 : 字符串类型 操作数1;操作数2;运算符
响应 : 字符串类型 计算响应

服务端代码

package Calculator;

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

public class CalcServer {
    private DatagramSocket socket = null;

    public CalcServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true){
            // 1. 读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            // 2. 跟据请求计算响应
            String response = process(request);
            // 3. 把响应写回给客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                                                            requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印日志
            String log = String.format("[%s:%d] req: %s; resp: %s",requestPacket.getAddress().toString(),
                                    requestPacket.getPort(),request,response);
            System.out.println(log);
        }
    }

    private String process(String request) {
        // 分离
        String[] str = request.split(";");
        if(str.length != 3) return "[请求的格式出错!]";
        int num1 = Integer.parseInt(str[0]);
        int num2 = Integer.parseInt(str[1]);
        if(str[2].equals("+")){
            return num1+num2+"";
        }else if(str[2].equals("-")){
            return num1-num2+"";
        }else if(str[2].equals("*")){
            return (num1 * num2)+"";
        }else if(str[2].equals("/")){
            return (num1 / num2)+"";
        }
        return "[请求格式出错!操作符不支持!]";
    }

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

客户端代码

package Calculator;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class CalcClient {
    private String serverIp;
    private int serverPort;
    private DatagramSocket socket = null;

    public CalcClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        while (true) {
            // 1. 用户输入的请求
            System.out.print("请输入操作数1: ");
            int num1 = sc.nextInt();
            System.out.print("请输入操作数2: ");
            int num2 = sc.nextInt();
            System.out.print("请输入运算符: ");
            String operator = sc.next();
            String request = num1+";"+num2+";"+operator;
            // 2. 构造请求,并发给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            // 3. 从服务器中读取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());

            // 4. 将结果显示到屏幕上
            String log = String.format("req: %s; resp: %s",request,response);
            System.out.println(log);
        }
    }

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

运行结果:

在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>