JavaEE之网络编程(TCP协议和UDP协议)

1.TCP和UDP的区别

1.1有连接和无连接

JavaEE之网络编程(TCP协议和UDP协议)_第1张图片

JavaEE之网络编程(TCP协议和UDP协议)_第2张图片 

1.2可靠传输和不可靠传输

可靠传输意为发送方可以感知到接受方有没有接收到数据。

不可靠传输意为发送方不知道接收方有没有接收到数据。

1.3面向字节流和面向数据报

面向字节流:数据是以字节为单位,进行传输的。

这个就非常类似于 文件操作中的文件内容相关的操作中的字节流。
网络传输也是一样!
假设,现有100个字节的数据。
我们可以一直发完。
也可以 一次发 10个字节,发送十次。
也可以 一次发 2 个字节,发送50次。

面向数据报:
以数据报为单位,进行传输。

一个数据报都会明确大小。
一次 发送/接收 必须是 一个 完整的数据报。
不能是半个,也不能是一个半,必须是整数个。

1.4全双工和半双工

全双工:一条链路双向通信

半双工:一条链路,单向通信

2.UDP数据报

1)DatagramSocket(数据报套接字)
2)DatagramPacket(数据报 的数据包)

这两个都是关于UDP数据报的类:

JavaEE之网络编程(TCP协议和UDP协议)_第3张图片

3.写一个简单的客户端服务器程序

因为刚开始比较简单,因此这个程序就是我们发送什么,他就返回什么。

第一步:JavaEE之网络编程(TCP协议和UDP协议)_第4张图片 

第二步:启动服务器

JavaEE之网络编程(TCP协议和UDP协议)_第5张图片 

第三步:

JavaEE之网络编程(TCP协议和UDP协议)_第6张图片 

回显服务器代码:

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

public class UdpEchoServer {

    //1.网络编程第一步先准备好socket实例

    private DatagramSocket socket = null;

    //port是服务器的端口号
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    //2.启动服务器
    public void start() throws IOException {
        System.out.println("启动服务器");
        //UDP是不需要建立连接的,直接接收客户端发来的数据即可
        while (true){
            //1.读取客户端发来的请求
            DatagramPacket datagramPacket = new DatagramPacket(new byte[1024],1024);
            //为了接收数据,需要事先准备好一个空的DatagramPacket对象,用receive来填充数据
            socket.receive(datagramPacket);
            //将datagramPacket解析成一个String
            String request = new String(datagramPacket.getData(),0,datagramPacket.getLength(),"UTF-8");
            //2.根据请求计算响应
             String response = process(request);
            //3.把响应写回客户端
             DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                     datagramPacket.getSocketAddress());
             socket.send(responsePacket);
              //打印具体的IP地址、端口号、请求和响应
            System.out.printf("[%s:%d]resquest: %s,request: %s\n",datagramPacket.getAddress().toString()
                    ,datagramPacket.getPort(),//客户端端口号
                    request,//请求
                    response);//响应
        }

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

 

 因为我们没有写客户端,因此没有请求,自然服务器也就没有响应

4.客户端实现

JavaEE之网络编程(TCP协议和UDP协议)_第7张图片

 代码展示:

package network;

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

//站在客户端的角度:
//源IP:本机IP
//源端口:系统分配的端口
//目的IP:服务器IP
//目的端口:服务器的端口
//协议类型:UDP
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        //此处的 serverPort 是服务器的端口
        // 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    // 启动客户端
    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        while(true){
            //1、先从控制台读取用户输入的字符串
            System.out.println("->");
            String request = sc.next();
            //2、把这个用户输入的内容,构成一个 UDP 请求,并发送给服务器
            //构造的请求里面包含两个信息:1、数据的内容(request 字符串);2、数据要发给谁,服务器的 IP + 端口
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);
            //3、从服务器读取响应数据,并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"utf-8");
            //4、把响应效果显示到控制台上
            System.out.printf("[%s:%d] request: %s,response: %s\n",
                    serverIP,// 服务器IP
                    serverPort,// 服务器端口
                    request,//请求
                    response);// 响应
        }
    }
    public static void main(String[] args) throws IOException {
        //由于服务器 和 客户端 在同一个机器上,所以使用的 IP,仍然是 127.0.0.1,如果是在不同的机器上,这里IP就需要更改了。
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

这就是我们的服务器和客户端来进行请求和响应的代码。平常都是一个服务器,多个客户端。这一个服务器来处理多个客户端的请求。

5.写一个英译汉的请求处理服务

要先将之前的process的private改为public。

简单字典服务器程序:

import network.UdpEchoServer;

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

// 创建一个类 UdpDictionaryServer 来表示  字典服务器
// 因为代码的逻辑几乎是一样,且所有的办法都是public的
// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。
public class UdpDictionaryServer extends UdpEchoServer {
    HashMap map = new HashMap<>();//利用HashMap 来构建词库
    public UdpDictionaryServer(int port) throws SocketException {
        super(port);
        map.put("cat","小猫");
        map.put("dog","小狗");
        map.put("pig","佩奇");
    }

    @Override
    public String process(String request) {
        // 如果查询的单词在“词库”中存在,就返回其 键值对/对应的中文,
        //反之,如果查询的单词在 “词库”中 不存在,返回 没有对应的词。
        return map.getOrDefault(request,"没有对应的词义");
    }

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

6.TCP编程

6.1TCP回显服务

构造一个ServerSocket对象:

package network;

import java.io.IOException;
import java.net.ServerSocket;

public class TcpEchoServer {
    // listen 的 中文意思是 监听
    // 但是,在Java socket 中是体现不出来 “监听”的含义
    // 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
    // 而 ServerSocket 确实起到了一个监听的效果
    // 所以,取个 listenSocket 的名字
    private ServerSocket listenSocket = null;

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

JavaEE之网络编程(TCP协议和UDP协议)_第8张图片

 TCP服务器代码展示:

public class TcpEchServer {


    private ServerSocket listenSocket = null;

    public TcpEchServer(int port) throws IOException {

        listenSocket = new ServerSocket(port);


    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true){
            //由于TCP是有连接的,因此不能刚开始就读取数据,需要先建立连接
            //accept就是在接电话,前提是有人给你打电话,即有客户端给你发送请求
           Socket clientSocket =  listenSocket.accept();
           processConnection(clientSocket);//处理连接成功的客户端请求
        }
    }
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s,%d] 与客户端建立连接\n",clientSocket.getInetAddress().toString(),//获取客户端IP地址
                clientSocket.getPort());//获取客户端端口号
        //接下来就是处理需求和返回响应
        //这里的针对服务端的读写,和文件操作的读取一样
        try(InputStream inputStream = clientSocket.getInputStream()){
             try(OutputStream outputStream = clientSocket.getOutputStream()){
                 Scanner scanner = new Scanner(inputStream);
                 //循环处理每个请求,分别返回响应
                 while (true){
                     //1.读取请求
                     //如果没有下一个结果,直接结束循环
                     if(!scanner.hasNext()){
                         System.out.printf("[%s:%d 客户端断开连接 \n",clientSocket.getInetAddress().toString(),
                                 clientSocket.getPort());
                         break;
                     }
                     //此处用Scanner更方便
                     String request = scanner.next();

                     //2.根据请求,计算响应
                     String response = process(request);
                      //3.将相应返回给客户端
                     //为了方便起见,可以使用PrintWriter把OutputStream包裹一下
                     PrintWriter printWriter = new PrintWriter(outputStream);
                     printWriter.println(response);
                     printWriter.flush();//刷新缓存区
                     //如果没有这个flush,客户端可能不能第一时间看到响应的结果

                     System.out.printf("[%s:%d] request:%s,reponse:%s\n",clientSocket.getInetAddress(),
                             clientSocket.getPort(),
                             request,
                             response);
                 }
             }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }
    private String process(String request){
        return request;
    }
}

6.2TCP客户端编程

JavaEE之网络编程(TCP协议和UDP协议)_第9张图片

TCP客户端总代码展示:

package network;

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

public class TcpEchoClient {
    // 用普通的 Socket 即可,不用 ServerSocket 了
    private Socket socket = null;

    //此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
    public TcpEchoClient(String serverIP,int serverPort) throws IOException {
        // 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
        // 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
        socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
    }
    public void start(){
        System.out.println("和进服务器连接成功!");
        Scanner sc = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try (OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    //1、从控制台读取字符串
                    System.out.println("->");
                    String request = sc.next();
                    //2、根据读取的自妇产,构造请求,把请求发送给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
                    printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
                    //3、从服务器读取响应,并解析
                    Scanner scanner = new Scanner(inputStream);
                    String response = scanner.next();
                    //4、把结果显示到控制台上。
                    System.out.printf("request:%s,response:%s\n ",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

但是这里还有一个问题:JavaEE之网络编程(TCP协议和UDP协议)_第10张图片

 

JavaEE之网络编程(TCP协议和UDP协议)_第11张图片 

JavaEE之网络编程(TCP协议和UDP协议)_第12张图片 

 6.3TCP服务器之多线程版本

package network;

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

public class TcpThreadEchoServer {
    // listen 的 中文意思是 监听
    // 但是,在Java socket 中是体现不出来 “监听”的含义
    // 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
    // 而 ServerSocket 确实起到了一个监听的效果
    // 所以,取个 listenSocket 的名字
    private ServerSocket listenSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }
    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");

        while(true){
            //由于 TCP 是有连接的、因此,不能一上来就读取数据,需要先建立连接
            // accept 就是在“接电话”,接电话的前提是:有人给你打电话【有客户端发送请求】
            Socket clientSocket = listenSocket.accept();
            Thread t = new Thread(()->{
                try {
                    processThreadConnection(clientSocket);// 处理连接成功的客户端请求
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }

    private void processThreadConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),// 获取客户端IP地址
                clientSocket.getPort());//获取客户端端口
        //接下来,就可以来处理请求 和 响应
        // 这里的针对 TCP socket 的读写  和 文件操作 的读取一模一样!
        try(InputStream inputStream = clientSocket.getInputStream()){
            try(OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner sc = new Scanner(inputStream);
                // 循环处理每个请求,分别返回响应
                while(true){
                    //1、读取请求
                    // 如果没有下一个结果,直接结束循环。
                    if(!sc.hasNext()){
                        System.out.printf("[%s:%d] 客户端断开连接!\n",
                                clientSocket.getInetAddress().toString(),
                                clientSocket.getPort());
                        break;
                    }
                    //此处使用 Scanner 更方便
                    // 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
                    // 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
                    //  read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
                    String request = sc.next();

                    //2、根据请求,计算响应
                    String response = process(request);

                    //3、将响应返回给客户端
                    //为了方便起见,可以使用 PrintWriter 把 OutputStream 包裹一下
                    PrintWriter printWriter =new PrintWriter(outputStream);
                    printWriter.println(response);
                    printWriter.flush();// 刷新缓冲区。
                    // 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果

                    System.out.printf("[%s:%d] request:%s,response:%s\n",
                            clientSocket.getInetAddress(),// IP
                            clientSocket.getPort(),// 端口
                            request,//请求
                            response);// 响应
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }
    // 因为是回显服务,不涉及业务。只需要直接返货就可以了
    private String process(String request) {
        return request;
    }

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

7.Tcp服务器线程池版本

package network;

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 TcpThreadPool {
    private ServerSocket listenSocket = null;
    public TcpThreadPool(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = listenSocket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream()){
            try (OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner sc = new Scanner(inputStream);
                while (true){
                    if(!sc.hasNext()){
                        System.out.printf("[%s:%d] 客户端断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    String request = sc.next();
                    String response = process(request);
                    PrintWriter printWriter= new PrintWriter(outputStream);
                    printWriter.println(response);
                    printWriter.flush();

                    System.out.printf("[%s:%d] request:%s response:%s\n",
                            clientSocket.getInetAddress(),
                            clientSocket.getPort(),
                            request,
                            response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

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

8.Tcp线程池服务器版本之英汉翻译

package network;

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

public class TcpEchoClient {
    // 用普通的 Socket 即可,不用 ServerSocket 了
    private Socket socket = null;

    //此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
    public TcpEchoClient(String serverIP,int serverPort) throws IOException {
        // 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
        // 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
        socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
    }
    public void start(){
        System.out.println("和服务器连接成功!");
        Scanner sc = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try (OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    //1、从控制台读取字符串
                    System.out.println("->");
                    String request = sc.next();
                    //2、根据读取的字符串,构造请求,把请求发送给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
                    printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
                    //3、从服务器读取响应,并解析
                    Scanner scanner = new Scanner(inputStream);
                    String response = scanner.next();
                    //4、把结果显示到控制台上。
                    System.out.printf("request:%s,response:%s\n ",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

你可能感兴趣的:(JavaEE,java)