您的位置:首页 > 文旅 > 旅游 > 简单又快的科学小制作_手机搭建网站工具_福州seo排名公司_包就业的培训机构

简单又快的科学小制作_手机搭建网站工具_福州seo排名公司_包就业的培训机构

2025/4/23 0:09:31 来源:https://blog.csdn.net/2301_78583687/article/details/147341664  浏览:    关键词:简单又快的科学小制作_手机搭建网站工具_福州seo排名公司_包就业的培训机构
简单又快的科学小制作_手机搭建网站工具_福州seo排名公司_包就业的培训机构

目录

一. TCP API 

二. TCP回显服务器-客户端

1. 服务器

2. 客户端

3. 服务端-客户端工作流程

4. 服务器优化


TCP数据流套接字编程是一种基于有连接协议的网络通信方式


一. TCP API 

在TCP编程中,主要使用两个核心类ServerSocket Socket


ServerSocket

ServerSocket类只有服务器会使用,用于接受连接

ServerSocket构造方法:

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

 ServerSocket方法:

方法签名方法说明
Socket accept()

监听端口,如果有客户端连接后,则会返回一个服务端 Socket 对象

如果没有客户端连接,则会进入阻塞等待

void close()关闭此套接字
  •  accept()方法用于接收客户端连接请求并建立正式通信通道
  •  accept()方法是接受连接并返回Socket,真正和客户端进行交互的是Socket

 Socket

 Socket类负责具体的数据传输 

  • 客户端一开始就使用Socket进行通信(请求由客户端发起)
  • 服务器在接受客户端建立请求后,返回服务端Socket
  • 在双方建立连接之后,都会使用Socket进行通信 

Socket 构造方法:

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

 Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回的地址(IP和端口)
InputStream getInputStream()返回输入流
OutputStream getOutputStream()返回输出流
  • TCP面向字节流,基本传输单位是字节

二. TCP回显服务器-客户端

回显服务器

回显服务器:不进行任何的业务逻辑,只是将收到的数据显示出来


1. 服务器

  接收连接请求 

  • TCP是有连接的可靠通信
  • 真正建立连接的过程在内核中被实现,应用层只是调用相应API同意建立连接
  • 类比打电话,客户端拨号,服务器这边在响铃,通过调用accept接听

 代码实现:

            Socket clientSocket = serverSocket.accept();
  • accept()方法具有阻塞功能
  • accept()方法一次只能返回一个Socket对象,接收一次请求
  • 如果没有客户端发起连接请求,则会进入阻塞等待
  • 如果有一个客户端发起连接请求,则执行一次,如果有多个客户端发起连接请求,则执行多次

处理请求

 private void processConnection(Socket clientSocket) {}
  •  使用方法专门处理一次连接,在一次连接中可能会涉及多次请求响应交互

如何处理请求和返回响应? 

由于TCP面向字节流,我们可以字节流传输的类 InputStream和OutputStream

   try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){} catch (IOException e) {throw new RuntimeException(e);}
  •  使用try-with-resources管理InputStream和OutputStream,确保流自动关闭。 
  • InputStream从网卡中读取数据
  • OutputStream从网卡中写入数据

1)接收请求并解析

        Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){System.out.printf("[客户端ip:%s,端口号:%d],客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}String request = scanner.next();
  • 客户端和服务器双方都有自己的缓冲区
  • 客户端发送数据,会先将数据放入服务器缓冲区中
  • 如果服务器缓冲区中没有数据,hasNext()则会陷入阻塞等待中
  • 如果客户端退出,则会触发四次挥手断开连接,服务器会感知到,就会在hasNext()返回false。

2)根据请求计算响应

 回显服务器:不会处理数据,输入什么就会返回什么

            String response = process(request);

使用process方法来实现回显功能

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

 如果想要实现特定的功能,直接在process中实现即可 

3)返回响应

 Scanner的写操作无法自己完成,只能进行读取操作,写操作需要依靠其他的类(PrintWriter) 

                PrintWriter printWriter = new PrintWriter(outputStream);
//                将数据写入数据的缓冲区中printWriter.println(respond);

冲刷缓冲区

 由于缓冲区的特殊机制,缓冲区只有满的时候,才会被发送出去

                printWriter.flush();
  • 我们这里要保证实时性,客户端每发送一次请求,服务器都要第一时间响应
  • IO操作比较低效,如果每进行一次IO,就要冲刷一次,效率很低,为了让这种低效的操作少一点,等缓冲区满了,才会冲刷

服务器总代码:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket = null;TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while(true){//从缓冲区内取出并同意链接//将取出的数据使用clientSocket另外保存起来,//每有一个客户端,就会出现一个clientSocket对象,所有使用完,必须关闭Socket clientSocket = serverSocket.accept();//进行数据分析/*            Thread t = new Thread(()->{processConnection(clientSocket);});t.start();*/
//            这样写开销大,会有很多次的创建和销毁,改进使用线程池ExecutorService service = Executors.newFixedThreadPool(3);service.submit(()->{processConnection(clientSocket);});}}//使用这个方法专门处理一次连接,在一次连接中可能会涉及多次请求交互private void processConnection(Socket clientSocket) {System.out.printf("[客户端ip:%s,端口号:%d],客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());//循环处理请求并返回响应(请求可能不止一次)//从网卡中读数据和写数据try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while (true){
//                byte[] buffer = new byte[1024];
//                int n = inputStream.read(buffer);
//                //将字节数组转换为字符串
//                if(n==-1){
//                    System.out.printf("[客户端ip:%s,端口号:%d],客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());
//                    break;
//                }
//                String request = new String(buffer,0,n);Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){System.out.printf("[客户端ip:%s,端口号:%d],客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}
//                1.接受请求并解析//客户端必须有一个空格或者换行符String request = scanner.next();
//                2.根据请求计算响应String respond = process(request);
//                3.返回响应//返回的是字节数组类型
//                outputStream.write(request.getBytes(),0,request.getBytes().length);//返回字符串类型(各种类型)PrintWriter printWriter = new PrintWriter(outputStream);
//                将数据写入数据的缓冲区中printWriter.println(respond);//冲刷缓冲区printWriter.flush();//打印日志System.out.printf("[客户端ip:%s,端口号:%d],req:%s,resp:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,respond);}} catch (IOException e) {throw new RuntimeException(e);}finally {try {//必须进行close,clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

注意: 

  • 在服务器中,ServerSocket对象不需要被消耗,整个程序中只有一个ServerSocket对象,它的生命周期要伴随整个程序,不能提前关闭,只有程序退出了,才会被释放
  • 方法中的Socket必须要释放,每出现一个客户端,就会随之出现一个Socket对象,如果不释放,Socket对象会越来越多,将文件描述符表占满(内存泄露问题)

2. 客户端

 构造方法

    TcpEchoClient(String serverIp,int serverPort) throws IOException {socket = new Socket(serverIp,serverPort);}
  • 这里不需要将serverIP和serverPort在类中保存
  • 因为tcp有链接,socket会保存好这两个值

客户端如何发送请求和接收响应?

客户端同样使用字节流进行传输

        try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){} catch (IOException e) {throw new RuntimeException(e);}
  •  使用try-with-resources管理InputStream和OutputStream,确保流自动关闭。 
  • InputStream从网卡中读取数据
  • OutputStream从网卡中写入数据

1)从控制台读取请求

            //客户端输入的数据Scanner scannerConsole = new Scanner(System.in);while(true){System.out.print("->");//客户端没有输入if(!scannerConsole.hasNext()){break;}
//              从控制台读取请求String request = scannerConsole.next();}
  • 使用Scanner进行输入,如果没有输入数据,hasNext()会进入阻塞等待

2)将请求发送给服务器

 Scanner只会读取数据,发送使用类PrintWriter

          PrintWriter writer = new PrintWriter(outputStream);writer.println(request);               
  •  向服务器发送数据

冲刷缓冲区 

                //冲刷缓冲区writer.flush();

将发送的数据,先放入缓冲区中,等待缓冲区满了,才会发送缓冲区中的内容 


3)接收服务器返回的响应

            Scanner scannerNetwork = new Scanner(inputStream);String respond = scannerNetwork.next();
  • 服务器发送的数据,先到达客户端的缓冲区,客户端要从缓冲区读出数据
  • 这里使用Scanner进行读出数据,也可以使用read()方法读取 

4)将响应数据显示在控制台

                System.out.println(respond);

将接收到的字符串响应,直接打印出来即可 


客户端总代码:


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;TcpEchoClient(String serverIp,int serverPort) throws IOException {
//        这里不需要将serverIP和serverPort在类中保存
//        因为tcp有链接,socket会保存好这两个值socket = new Socket(serverIp,serverPort);}public void start(){System.out.println("客户端启动");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){//客户端输入的数据Scanner scannerConsole = new Scanner(System.in);//通过网络读取Scanner scannerNetwork = new Scanner(inputStream);//像服务器发送请求PrintWriter writer = new PrintWriter(outputStream);while(true){System.out.print("->");//客户端没有输入if(!scannerConsole.hasNext()){break;}
//                1. 从控制台读取请求String request = scannerConsole.next();
//                2.将请求发送给服务端writer.println(request);//冲刷缓冲区writer.flush();
//                3.接受服务端返回的响应//从数据缓冲区中读取出内容String respond = scannerNetwork.next();
//                4.将响应显示在控制台System.out.println(respond);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();}
}

3. 服务端-客户端工作流程

 无论是TCP还是UDP都是服务端先启动 

创建连接过程

  1. 服务器启动,由于没有客户端建立连接,accept()进入阻塞,等待客户端创建连接
  2. 客户端启动,客户端申请和服务器建立连接
  3. 服务器从accept()阻塞中返回,调用processConnection()方法进行交互

 双方交互过程

  1. 服务器进入processConnection()方法,执行到hasNext(),由于客户端没有发送数据,服务器读取不到数据,进入阻塞状态
  2. 客户端在hasNext()这里进入阻塞,等待用户在控制台中输入数据
  3. 用户输入数据,客户端从hasNext()中退出阻塞,将数据发送给服务器,next()阻塞等待服务器返回数据
  4. 服务器从hasNext()阻塞中返回,读取请求并处理,构造响应,发送给客户端
  5. 客户端读取响应并打印

4. 服务器优化

            Thread t = new Thread(()->{processConnection(clientSocket);});t.start();
  • 每来一个客户端,服务器就需要创建出一个新的线程
  • 每次客户端结束,服务器就需要销毁这个线程

如果客户端比较多,那么服务器就需要频繁的创建和销毁 ,开销大


 (1)可以通过引入线程池来避免频繁的创建和销毁

            ExecutorService service = Executors.newFixedThreadPool(3);service.submit(()->{processConnection(clientSocket);});

 如果有的客户端处理的过程很短(网站),也有可能客户端处理的时间会很长

处理时间很短的客户端,分配一个专门的线程,有点浪费,所有引入了IO多路复用技术


(2)IO多路复用技术

IO多路复用技术是操作系统提供的机制。 

让一个线程去同时去负责处理多个Socket对象

本质在于这些Socket对象不是同一时刻都需要处理 

虽然有多个Socket对象,但是同一时间活跃的Socket对象只是少数(大部分的Socket对象都是在等数据),我们可以在等的过程中,去处理活跃的Socket对象 


点赞的宝子今晚自动触发「躺赢锦鲤」buff! 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com