JavaEE - 网络编程

news2024/11/17 11:41:27

一、网络编程基础

为什么需要网络编程?
用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。
在这里插入图片描述
与本地打开视频文件类似,只是视频文件这个资源的来源是网络。
相比本地资源来说,网络提供了更为丰富的网络资源:
在这里插入图片描述
所谓的网络资源,其实就是在网络中可以获取的各种数据资源。
而所有的网络资源,都是通过网络编程来进行数据传输的。

1.1、什么是网络编程

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。

在这里插入图片描述

当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。

特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。

但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:

  • 进程A:编程来获取网络资源
  • 进程B:编程来提供网络资源

1.2、网络编程中的基本概念

发送端和接收端
在一次网络数据传输时:

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

注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
在这里插入图片描述

请求和响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:

  • 第一次:请求数据的发送
  • 第二次:响应数据的发送。

好比在快餐店点一份炒饭:
先要发起请求:点一份炒饭,再有快餐店提供的对应响应:提供一份炒饭
在这里插入图片描述

客户端和服务端

  • 服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
  • 客户端:获取服务的一方进程,称为客户端。

对于服务来说,一般是提供:
客户端获取服务资源
在这里插入图片描述
客户端保存资源在服务端
在这里插入图片描述
好比在银行办事:

  • 银行提供存款服务:用户(客户端)保存资源(现金)在银行(服务端)
  • 银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管的现金)

常见的客户端服务端模型
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

  1. 客户端先发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)

在这里插入图片描述

二、Socket套接字

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

2.1、Socket套接字分类

Socket套接字主要针对传输层协议划分为如下三类:

  • 1、流套接字:使用传输层TCP协议
    TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
    以下为TCP的特点(细节后续再学习)
    有连接
    可靠传输
    面向字节流
    有接收缓冲区,也有发送缓冲区
    大小不限

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情
况下,是无边界的数据,可以多次发送,也可以分开多次接收。

  • 2、数据报套接字:使用传输层UDP协议
  • UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
    以下为UDP的特点(细节后续再学习):
    无连接
    不可靠传输
    面向数据报
    有接收缓冲区,无发送缓冲区
    大小受限:一次最多传输64k

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一
次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节

  • 3、原始套接字
    原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
    我们不学习原始套接字,简单了解即可。

在这里插入图片描述
接下来先来了解TCP和UDP两种协议

2.2、TCP 与 UDP 的 区别

在这里插入图片描述

有连接 和 无连接

可以怎么去理解:
有链接:像打电话

比如说:现在我们要打电话给某个朋友。
输入号码,按下手机拨号键。
手机开始发出 嘟嘟嘟 声音,开始等待对方接听,

而且,我们拨号之后,并不是马上就能接通的!
必须要等待 对方接听之后,我们才能与其交流。

之所以说:有链接 就像 打电话一样,是因为 打电话,必须要接通了之后,才能交流;没有接通,双方就无法交流。
有连接的意思:就是在两者确认建立联系后,就可以开始交互了。

无连接:发微信

不需要接通,直接就能发数据。
发微信,我们都知道:发送信息的时候,是不需要对方在线或者回复,按下回车,立马就能加个信息发送出去,不过 对方 看没看见这条消息,我们是不确定的 。
这种情况,就叫做无连接。

所以 TCP,就是要求双发先建立连接,连接好了,才能进行传数据。
而 UDP,直接传输数据,不需要双方建立连接。

可靠传输 和 不可靠传输

可靠传输:发送方 知道 接收方 有没有接收到数据

注意!不要理解错了。
可靠传输,不是说数据发送之后,对方100% 就能收到。
你代码写得再好,也刚不住挖掘机把你家网线挖断了。
网线都断了,你能把数据发出去才有鬼。

可靠传输,不是说传输数据百分百成功,关键还得看这里面是否能感知到 传输数据成功了。

关于可靠传输,还有一种错误理解。
可靠传输,就是“安全传输”。这种说法也是一个典型的错误。
可靠 和 安全 是 两码事!!!!

安全,指的是 数据在传输过程,不容易被黑客窃取,不容易被篡改。
可靠,指的是 数据发给对方,发送方能知道接收方有没有收到数据。
在这里插入图片描述

不可靠传输:发送方 不知道 接收方有没有接收到数据
在这里插入图片描述

总得来说:
可靠,就是我们对于自己发送的信息,心里有点数。
心里没底,就是不可靠。

面向字节流 与 面向数据报

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

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

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

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

在代码中,这两者的区别是非常明显的!

全双工

全双工 对应的是 半双工

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

举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和他认识,知道彼此身份,并且有相互联系的方式。
      他是xxx,联系方式xxxxxx。所以别再打我,作用不大,因为我都会说。

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

举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和上级是单向通信的,他联系到我,我联系不到他。所以别再打我,作用不大

TCP 和 UDP 都是全双工。
半双工理解即可。

三、UDP数据报套接字编程

在这里插入图片描述

3.1、DatagramSocket API

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

DatagramSocket 构造方法

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

DatagramSocket 方法

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

3.2、DatagramPacket API

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

DatagramPacket 构造方法

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

DatagramPacket 方法

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

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

3.3、InetSocketAddress API

InetSocketAddress ( SocketAddress 的子类 )构造方法:

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

3.4、示例一:写一个最简单的客户端服务器程序【回显服务】

什么是回显服务?
回显服务 - Echo Server
简单来说,我说什么,你回什么。

就像回声,重复着我们说过话。

回显服务,就是这样的。
我们发送什么样子的数据,它就给我们返回一个同样的数据。
也就是说:根据我们请求的内容数据,来返回一个具有相同数据的响应。

这样的程序属于最简单的网络编程中的程序。
因为不涉及到任何的业务逻辑,就只是通过 socket API 进行单纯的数据转发。
我通过这个程序,来向大家演示 API 的使用。

准备工作
在这里插入图片描述

服务器部分
在这里插入图片描述

package network;

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

/**
 * Created with Intellij IDEA.
 * Description:
 * User: ryy
 * Date: 2023-05-02
 * Time: 16:28
 */
public class UdpEchoServer {
    //进行网络编程,首先要准备 socket 实例
    private DatagramSocket socket = null;

    //port 是服务器的端口号
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("启动服务器!");
        //UDP是不需要建立连接的,直接接收客户端发来的数据即可。
        while (true){
            //1、读取客户端发来的请求
            //为了接受数据,我们需要先准备好一个空的DatagramPacket对象.
            //然后,由receive 来填充数据。
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);

            //把 DatagramPacket 对象构造出一个字符串
            String request = new String(requestPacket.getData(),0, requestPacket.getLength(),"UTF-8");
            //2、根据请求计算响应(由于咱们这是一个回显服务,这一步就可以省略了)
            String response = process(request);

            //3、把响应写回客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);

            // 打印具体 IP、端口、请求、响应
            System.out.printf("[%s:%d] request: %s,response: %s\n",
                    requestPacket.getAddress().toString(),// 客户端IP
                    requestPacket.getPort(),// 客户端端口号
                    request,//请求
                    response);// 响应
        }
    }

    //由于是 回显服务,响应和请求是一样的
    private String process(String request) {
        return request;
    }

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

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

客户端部分
在这里插入图片描述

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;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: ryy
 * Date: 2023-05-02
 * Time: 16:28
 */
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    public UdpEchoClient(String ip, int port) throws SocketException {
        //此处的 serverPort 是服务器的端口
        // 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。
        socket = new DatagramSocket();
        serverIP = ip;
        serverPort = port;
    }

    //启动客户端
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1、先从控制台上读取用户输入的字符串
            System.out.println("用户输入字符:");
            String request = scanner.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 {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

在这里插入图片描述

客户端 和 服务器交互效果
在这里插入图片描述
这才是我们服务器正常的运行状态,同时处理多个客户端发送的请求。

再来写一个简单程序:就是上面代码的基础上,带上点业务逻辑

写一个翻译程序(英译汉)
请求是一些简单的英文单词。
响应是 英文单词 对应的 中文翻译。
客户端不变,把服务器代码进行调整。
主要是调整 process 方法。
其他步骤都是一样的。
关键的逻辑就是“根据想求来处理响应”
在这里插入图片描述

package network;

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

/**
 * Created with Intellij IDEA.
 * Description:
 * User: ryy
 * Date: 2023-05-02
 * Time: 22:02
 */
 // 创建一个类 UdpDictionaryServer 来表示  字典服务器
// 因为代码的逻辑几乎是一样,且所有的办法都是public的
// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。
public class UdpDictionaryServer extends UdpEchoServer{
    private Map<String,String> map = new HashMap<>();
    public UdpDictionaryServer(int port) throws SocketException {
        super(port);
        map.put("dog","小狗");
        map.put("cat","小猫");
        map.put("duck","小鸭子");

    }

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

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

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

总结

一个服务器,最关键的逻辑就是“根据想求来处理响应”!
什么样的请求,得到什么样的响应。
这是我们一个服务器要完成的一个最最关键的事情。
通过这个东西,才能让我们的程序真正帮我们解决一些实际问题。
这一点,大家要体会我们 服务器-客户端 的交互过程。

之所以,网络编程 是一个 服务器-客户端的结构,是因为 有些工作,我们希望让服务器完成一些工作,既然要完成这样的工作,就得有输入(请求),也有输出(响应)。
从输入到输出,从请求到响应的这个过程,这就是服务器要完成的基本工作。

四、TCP流套接字编程

TCP 和 UDP 的差别很大!
在 TCP API 中,也是涉及到两个核心的类
在这里插入图片描述

4.1、ServerSocket API

4.1.1、ServerSocket 构造方法

ServerSocket 是创建TCP服务端Socket的API。

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

4.1.2、ServerSocket 方法

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

4.3、Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

4.3.1、Socket 构造方法

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

4.3.2、Socket 方法

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

4.4、回显服务器 - TCP版

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;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: ryy
 * Date: 2023-05-02
 * Time: 22:31
 */
public class TcpEchoServer {
    // listen 的 中文意思是 监听
    // 但是,在Java socket 中是体现不出来 “监听”的含义
    // 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
    // 而 ServerSocket 确实起到了一个监听的效果
    // 所以,取个 listenSocket 的名字
    private ServerSocket listenSocket = null;

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

    //启动服务器
    public void start() throws IOException {
        System.out.println("启动服务器!");
        while (true){
            //注意,UDP 是无连接,所以一上来就发送数据报
            //而TCP是有连接的,要先建立连接,连接好之后才能发送数据
            //accept 就是用来建立连接的,如果没有客户端建立连接,那服务器就会阻塞
            //accept 返回了一个Socket 对象,称为 clientSocket ,后续和客户端的沟通都是通过 clientSocket 来完成的
            //换句话说,ServerSocket 就干了一件事, 建立连接
            Socket clientSocket = listenSocket.accept();

            //处理连接之后的客户端的请求
            processConnection(clientSocket);
        }
    }

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

                    //2、根据请求 计算响应
                    String response = process(request);
                    //3、把响应返回给客户端
                    //为了方便 用 PrintWriter 把 outputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.print(response);

                    //刷新缓冲区
                    printWriter.flush();
                    System.out.printf("[%s : %d]  request -》 %s  response -》 %s\n",clientSocket.getInetAddress().toString()
                            ,clientSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //此处用来关闭文件
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

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

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

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;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: ryy
 * Date: 2023-05-03
 * Time: 21:36
 */
public class TcpEchoClient {
    // 用普通的 Socket 即可,不用 ServerSocket 了
    private Socket socket = null;

    //此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
    public TcpEchoClient(String serverIP,int serverPort) throws IOException {
        // 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
        // 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
        // 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
        socket = new Socket(serverIP,serverPort);
    }

    private void start(){
        System.out.println("和服务器建立连接成功!");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try (OutputStream outputStream = socket.getOutputStream()){
                while (true){
                    //客户端还是4个步骤
                    //1、从控制台上输入字符串
                    System.out.println("请输入请求:");
                    String request = scanner.next();
                    //2、把字符串构造成请求,然后发送请求给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();
                    //3、服务器响应请求,并解析
                    Scanner scaResponse = new Scanner(inputStream);
                    String response = scaResponse.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();
    }
}

此是TCP版本的回显服务写完了,但是还会出现一些问题,UDP版本的可以多个客户端与服务器建立连接,但是TCP就不行,下面我们看一下出现的问题

多线程版本的回显服务器程序

客户端代码和上面一样,只需要改一下服务器里面的代码

当前的服务器,同一时刻只能处理一个客户端连接。
作为一个服务器应该给很多客户端提供服务,而这里只能处理一个客户端,这显然是不科学的。

在这里插入图片描述

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){
            //注意,UDP 是无连接,所以一上来就发送数据报
            //而TCP是有连接的,要先建立连接,连接好之后才能发送数据
            //accept 就是用来建立连接的,如果没有客户端建立连接,那服务器就会阻塞
            //accept 返回了一个Socket 对象,称为 clientSocket ,后续和客户端的沟通都是通过 clientSocket 来完成的
            //换句话说,ServerSocket 就干了一件事, 建立连接
            Socket clientSocket = listenSocket.accept();

            //处理连接之后的客户端的请求
            //改进方法 - 就是在客户端建立连接的时候,创建一个线程执行这里的 processConnection 方法
            Thread t = new Thread(() -> {
                processConnection(clientSocket);

            });
            t.start();
        }
    }

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

                    //2、根据请求 计算响应
                    String response = process(request);
                    //3、把响应返回给客户端
                    //为了方便 用 PrintWriter 把 outputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);

                    //刷新缓冲区
                    printWriter.flush();
                    System.out.printf("[%s : %d]  request -》 %s  response -》 %s\n",clientSocket.getInetAddress().toString()
                            ,clientSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //此处用来关闭文件
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

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

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

效果图
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/486222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KALI入门到高级【第五章】

预计更新第一章 入门 1.1 什么是Kali Linux&#xff1f; 1.2 安装Kali Linux 1.3 Kali Linux桌面环境介绍 1.4 基本命令和工具 第二章 信息收集 1.1 网络扫描 1.2 端口扫描 1.3 漏洞扫描 1.4 社交工程学 第三章 攻击和渗透测试 1.1 密码破解 1.2 暴力破解 1.3 漏洞利用 1.4 特…

模糊PID(模糊规则表)

模糊PID的模糊化相关内容,请参看下面的博客文章: PLC模糊控制模糊PID(梯形图实现+算法分析)_RXXW_Dor的博客-CSDN博客博途PLC的模糊PID控制详细内容请查看下面的博客文章:Matlab仿真+博途PLC模糊PID控制完整SCL源代码参考(带模糊和普通PID切换功能)_博途怎么实现模糊pid_…

后端程序员的前端必备【Vue】 - 05 class与style绑定、表单输入绑定、Vue生命周期

class与style绑定、表单输入绑定、Vue生命周期 1 class与style绑定1.1 绑定class1.2 绑定style1.3 练习 2 表单输入绑定3 Vue生命周期3.1 vue生命周期3.2 组件的生命周期钩子 1 class与style绑定 操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attri…

【操作系统】操作系统内核

图灵机 当系统中有一个CPU的时候 &#xff0c;MR就是它的状态 当系统中有n个CPU的时候&#xff0c;MR1,MR2…MRn 是它的状态 IRQ和NMI是低电平有效信号 NMI不可屏蔽中断 6502的CPU一共40个引脚 eflags寄存器里的 IF 表示 interrupt enable 1表示可以响应中断&#xff0c;0表示…

破解马赛克有多「容易」?

刷短视频时&#xff0c;估计大家都看过下面这类视频&#xff0c;各家营销号争相曝光「一分钟解码苹果笔刷背后内容」的秘密。换汤不换药&#xff0c;自媒体们戏称其为「破解马赛克」&#xff0c;殊不知让多少不明真相的用户建立起了错误的认知&#xff0c;也让苹果笔刷第 10086…

【网络编程】demo版UDP网络服务器实现

文章目录 一、引入二、服务端实现2.1 创建套接字socket2.2 绑定bind2.3 启动服务器2.4 IP的绑定2.5 读取数据recvfrom 三、用户端实现3.1 绑定问题3.2 发送数据sendto 四、源码 一、引入 在上一章【网络编程】socket套接字中我们讲述了TCP/UDP协议&#xff0c;这一篇就是简单实…

保护移动设备免受恶意软件侵害优秀方法

几天前&#xff0c;移动恶意软件攻击增加了500%显然&#xff0c;我们大多数人都不知道不能很好地保护我们的手机下面小编揭秘有效保护移动设备免受恶意软件侵害的最佳方法。 1、使用移动反恶意软件 恶意软件很容易感染智能手机和平板电脑&#xff0c;因此在设备上安装可靠的…

douyin 之xgorgon0404参数

如果我们想要看抖音的数据&#xff0c;在抓包的时候&#xff0c;会发现有一个xgorgon参数,在请求接口的时候&#xff0c;只需要在请求头携带xgorgon参数&#xff0c;就能获得响应后的数据。 目前抖音的xgorgon0404算法已经还原了&#xff0c; 下面是一个请求的demo。代码如下:…

计算机网络可靠传输的三种基本实现机制

可靠传输的means 在数据链路层会给上层网络层提供可靠传输或者不可靠传输&#xff0c;不可靠传输是指检测到传输差错的时候只是丢弃该分组而不进行任何处理&#xff0c;而可靠传输会给发送者一个信号进行重发该分组。 以下给出的三种基本实现机制是提出一些问题&#xff0c;慢…

截图的背景色如何去除?这里介绍一个小工具

屏幕截图&#xff0c;是方便常用的功能。例如从网页或者视频中截图。但是有时候想去除截图中的背景颜色&#xff0c;怎么办&#xff1f;下面这个案例介绍如何去掉截图中的蓝色背景色。 这个小工具就能帮你方便实现。Web端的便捷小工具链接: http://www.artvily.com/renderCase…

深度学习框架发展趋势

深度学习方法的发展是推动深度学习框架进步的最大动力&#xff0c;因此深度学习框架的功能和设计应顺应 算法和模型的发展趋势&#xff1a; 第一&#xff0c;易用性。深度学习领域仍处于快速发展期&#xff0c;参与者和学习者不断增加&#xff0c;新模型大量提出。因 此&#…

深入篇【C++】类与对象:拷贝构造函数详解

深入篇【C】类与对象&#xff1a;拷贝构造函数详解 ①.拷贝构造函数Ⅰ.概念Ⅱ.特征1.重载形式之一2.参数唯一3.形参必须传引用4.编译器的拷贝函数5.典型调用场景 ②.总结&#xff1a; ①.拷贝构造函数 Ⅰ.概念 在创建对象时&#xff0c;能否创建一个与已存在对象一模一样的新…

[架构之路-186]-《软考-系统分析师》-5-数据库系统 - 关系型数据库、操作型数据库、数据集市、数据仓库的区别?

目录 总体架构&#xff1a; 一、数据库 二、关系型数据库 三、SQL与数据库 四、 分布式数据库 五、数据仓库 1. 概述 2. 架构 3、数据仓库的主要特征 3、1 面向主题性 3、2 集成性 3、3 非易失性 3、4 时变性 总体架构&#xff1a; 一、数据库 数据库是“按照数据…

【链表OJ题 1】反转链表

目录 题目来源&#xff1a; 代码实现 1、方法一 1.1分析 2、方法二 2.1 分析 题目来源&#xff1a; 力扣 题目描述&#xff1a; 代码实现 1、方法一 struct ListNode* reverseList(struct ListNode* head) {struct ListNode* prev NULL, * cur head;while (cur){st…

基础IO+文件

基础IO 回顾文件回顾文件操作库函数调用接口写文件-w读文件-r 系统调用打开文件-open写文件-write 文件操作本质文件描述符fd文件描述符的分配规则重定向 FILE缓冲区模拟实现缓冲区总结 理解文件系统磁盘物理结构存储结构逻辑结构 文件系统软硬链接 动静态库动态库和静态库生成…

Kafka上的优化经验

1. 平滑扩容 Motivation kafka扩容⼀台新机器的流程 假如集群有 3 个 broker &#xff0c;⼀共有 4 个 TP &#xff0c;每个 3 副本&#xff0c;均匀分布。现在要扩容⼀台机器&#xff0c; 新 broker 加⼊集群后需要通过⼯具进⾏ TP 的迁移。⼀共迁移 3 个 TP 的副…

JavaScript高级程序设计(第2版)——读书笔记

文章目录 第1章 JavaScript简介第2章 在HTML中使用JavaScript第3章 基本概念第4章 变量、作用域、内存问题第5章 引用类型第6章 面向对象的程序设计第7章 匿名函数第8章 BOM第9章 客户端检测第10章 DOM第11章 DOM2和DOM3第12章 事件第13章 表单脚本第14章 错误处理与调试第15章…

java 的参数传递

一、疑惑引入 首先&#xff0c;我们从一个例子来引出这个问题&#xff1a; public static void main(String[] args) throws IOException {List<String> mockList Lists.newArrayList("a", "b");System.out.println("1: " mockList);L…

【GAMES101】04 Viewing Transformation

1.View/Camera Transformation&#xff08;视图变换&#xff09; 1、将准备拍摄的对象移动到场景中指定位置。&#xff08;模型变换&#xff0c;Model Transform&#xff09; - 模型导入场景中从模型坐标系转换成世界坐标系 2、将相机移动到准备拍摄的位置&#xff0c;将它对准…

【网络】-- TCP协议

其中TCP就属于传输层&#xff0c;并且端口号也是在传输层起作用。 目录 TCP协议报头 可靠性 32位序号 16位窗口大小 六个标记位 三次握手四次挥手 RST PSH URG 16位紧急指针 FIN socksetopt 可靠性机制 确认应答(ACK)机制 超时重传机制 连接管理机制 三大机…