【计算机网络】网络编程套接字之TCP套接字——含超详细代码注释

news/2024/5/18 12:36:16 标签: 网络, tcp/ip, udp

文章目录

  • TCP流套接字编程
    • ServerSocket API
      • ServerSocket 构造方法
      • ServerSocket 方法
    • Socket API
      • Socket 构造方法
      • Socket 方法
  • 案例及超详细注释
    • 案例一(回显服务)
    • 实例二(回显服务——多线程版本)
    • 实例三(线程池版本)

引言:

上篇文章我们一起学习了【计算机网络网络编程套接字之UDP数据报套接字,今天让我们来一起继续学习 网络编程套接字之TCP套接字编程😊😊😊

TCP流套接字编程

ServerSocket API

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

ServerSocket 构造方法

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

ServerSocket 方法

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

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket;不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法

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

Socket 方法

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

案例及超详细注释

案例一(回显服务)

服务器启动四步骤:

  1. 建立连接
  2. 读取客户端发来的请求
  3. 根据请求计算响应
  4. 把响应写回到客户端

看到这四个步骤,不知道有的小伙伴会不会有疑惑说,上篇文章UDP数据报套接字编程时,启动服务器只需要三个步骤啊,这怎么又多了个步骤捏❓❓❓这是因为UDP协议是无连接的,而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;

public class TcpEchoServer {
    private ServerSocket serverSocket=null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动了!");
        while (true){
            //由于TCP是有连接的,不能一上来就读数据,而要先建立连接(像接电话一样)
            //accept就是在“接电话”,接电话的前提是,有人给你打了,如果当前没有客户端尝试建立连接,此处的accept就会阻塞
            //accept返回一个socket对象,称为clientSocket,后续和客户端之间的沟通,都是通过clientSocket来完成的
            //进一步讲,serverSocket就干了一件事,接电话~
            Socket clientSocket=serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) {
        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){
                    //1.读取请求
                    if (!scanner.hasNext()){
                        System.out.printf("[%s:%d]客户端断开连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    //此处用Scanner更方便,如果不用Scanner就用原生的InputStream的read也可以
                    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]req:%s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //记得关闭
                //由于clientSocket是每次连接都创建个新的,也就是数目很多,并且连接断开也就不再需要了,所以它会持续的进行积累
                //因此我们需要保证每次处理完的连接都要给释放了
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer=new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

客户端:

package network;

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;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        //这里传入的IP和端口号的含义表示的不是自己绑定,而是表示和这个IP端口建立连接!
        //调用这个构造方法,就会和服务器建立连接(打电话拨号了)
        socket = new Socket(serverIp,serverPort);
    }
    public void start(){
        System.out.println("和服务器连接成功了!");
        Scanner scanner=new Scanner(System.in);
        try(InputStream inputStream= socket.getInputStream()){
            try(OutputStream outputStream= socket.getOutputStream()){
                while (true){
                    //1.从控制台读取字符串
                    System.out.println("->");
                    String request=scanner.next();
                    //2.根据读取的字符串,构成请求,把请求发送给服务器
                    PrintWriter printWriter=new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();
                    //3.从服务器读取响应,并解析
                    Scanner respScanner=new Scanner(inputStream);
                    String response=respScanner.next();
                    //4.把结果显示到控制台上
                    System.out.printf("req: %s,resp: %s\n",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

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

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

虽然上面的TCP代码已经跑起来了,但是还存在一个很严重的问题❗❗那就是当前的服务器,在同一时刻只能处理一个连接,这就很不科学❗
那么为啥当前的服务器只能处理一个客户端嘞❓那是因为能够和客户端交互的前提是,要先调用accept,接收连接(也就是接通电话)图解如下⬇️⬇️⬇️

在这里插入图片描述
当前这个问题就好像,你和别人在打电话,而此时其他人若再给你打电话,就没法继续接通了。
要想解决上述问题,就得让processConnection 的执行,和前面的accept的执行互相不干扰;不能让processConnection里面的循环导致accept无法及时调用。
所以此时就需要我们之前的老朋友隆重登场了,那就是——多线程

那么为啥UDP版本的程序就没用多线程,也是好着的呢❓❓
因为UDP不需要处理连接,UDP只要一个循环,就可以处理所有客户端的请求;
但是此处,TCP既要处理连接,又要处理一个连接中的若干次请求,就需要两个循环,里层循环就会影响到外层循环的进度了~
因此在主线程循环调用accept 时,当有客户端连接上来的时候,就直接让主线程创建一个新线程,由新线程负责对客户端的若干个请求,提供服务。(在新线程里通过while循环来处理请求) ,这个时候多个线程是并发执行的关系(宏观上看起来同时执行)。这样的话就是各自执行各自的了,就不会相互干扰了。

注意:每个客户端连上来都需要分配一个线程

实例二(回显服务——多线程版本)

在这里插入图片描述
代码实现如下:
服务器端:

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;

public class TcpThreadEchoServer {
    private ServerSocket serverSocket=null;
    public TcpThreadEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动了!");
        while (true){
            //由于TCP是有连接的,不能一上来就读数据,而要先建立连接(像接电话一样)
            //accept就是在“接电话”,接电话的前提是,有人给你打了,如果当前没有客户端尝试建立连接,此处的accept就会阻塞
            //accept返回一个socket对象,称为clientSocket,后续和客户端之间的沟通,都是通过clientSocket来完成的
            //进一步讲,serverSocket就干了一件事,接电话~
            Socket clientSocket=serverSocket.accept();
            //改进方法:在这个地方,每次accept成功,都创建一个新的线程,由新线程负责执行这个processConnection方法
            Thread t=new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();
        }
    }

    private void processConnection(Socket clientSocket) {
        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){
                    //1.读取请求
                    if (!scanner.hasNext()){
                        System.out.printf("[%s:%d]客户端断开连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    //此处用Scanner更方便,如果不用Scanner就用原生的InputStream的read也可以
                    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]req:%s,resp:%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 tcpThreadEchoServer=new TcpThreadEchoServer(9090);
        tcpThreadEchoServer.start();
    }
}

客户端:

客户端代码和上述回显服务客户端代码一致

结果图如下:(启动了两个客户端)
在这里插入图片描述

实例三(线程池版本)

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 TcpThreadPoolEchoServer {
    private ServerSocket serverSocket=null;
    public TcpThreadPoolEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动了!");
        ExecutorService pool= Executors.newCachedThreadPool();
        while (true){
            //由于TCP是有连接的,不能一上来就读数据,而要先建立连接(像接电话一样)
            //accept就是在“接电话”,接电话的前提是,有人给你打了,如果当前没有客户端尝试建立连接,此处的accept就会阻塞
            //accept返回一个socket对象,称为clientSocket,后续和客户端之间的沟通,都是通过clientSocket来完成的
            //进一步讲,serverSocket就干了一件事,接电话~
            Socket clientSocket=serverSocket.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());
        //接下来处理请求和响应
        //这里的针对TCP socket的读写和文件读写一模一样
        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更方便,如果不用Scanner就用原生的InputStream的read也可以
                    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]req:%s,resp:%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 {
        TcpThreadPoolEchoServer tcpThreadPoolEchoServer=new TcpThreadPoolEchoServer(9090);
        tcpThreadPoolEchoServer.start();
    }
}

在这里插入图片描述


http://www.niftyadmin.cn/n/945214.html

相关文章

【计算机网络】网络原理之TCP/IP协议——含大量图解及详细解释

文章目录应用层常见的协议及区别传输层UDP协议UDP协议端格式UDP的特点缓冲区大小受限TCP协议TCP协议段格式TCP原理及协议的主要机制确认应答(ACK)机制—安全机制超时重传机制—安全机制TCP内部去重连接管理机制—安全机制如何建立连接(三次握…

【计算机网络】网络原理之TCP/IP协议(二)

文章目录网络层IP协议IP协议头格式地址管理特殊的IP地址如何解决IP地址不够用的问题动态分配IP地址NAT机制IPv6路由选择数据链路层MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对于TCP协议的影响ARP协议ARP协议的作用DNS协议(补充)引言:在上篇…

【Web】HTML中选择器的基本用法

文章目录选择器选择器的功能选择器的种类基础选择器标签选择器类选择器id选择器通配符选择器小结复合选择器后代选择器子选择器并集选择器伪类选择器小结选择器 选择器的功能 选中页面中指定的标签元素(要先选中元素,才能设置元素的属性。 )…

【计算机网络】一文带你了解HTTP协议——内含经典面试题

文章目录HTTP 协议HTTP是什么理解"应用层协议"理解HTTP协议的工作过程HTTP协议格式抓包工具的使用抓包工具的原理抓包结果协议格式总结HTTP 请求 (Request)认识 URLURL 基本格式关于 URL encode认识"方法"(method)GET 方法POST 方法其他方法:经…

【计算机网络】HTTPS的基础知识

文章目录HTTPSHTTPS的工作过程引入对称加密引入非对称加密引入证书总结引言:在上篇文章中一文带你了解HTTP协议,我们一起学习了HTTP协议的相关知识,下面让我们再一起来学习下HTTPS的有关知识吧😊😊😊 HTTPS…

浅谈Cookie 和 Session——含案例及详细注解

文章目录回顾Cookie理解会话机制 (Session)Cookie 和 Session的区别⭐⭐核心方法案例——网页登录编写一个简单的登录页面编写一个 Servlet 来处理这个登录请求编写服务器端返回主页启动服务器进行验证回顾Cookie 在前面的 HTTP 协议中,我们也理解过这个 Cookie&am…

代码案例—— web版表白墙及文件上传

文章目录实现一个 web 表白墙准备工作约定前后端交互接口实现服务器端代码调整前端页面代码数据存入数据库实现效果上传文件核心方法代码示例——通过网页提交一个图片到服务器上实现一个 web 表白墙 由于博主之前学前端的时候写的表白墙数据不能保存😅&#x1f605…

博客系统——前后端分离

文章目录准备工作1.创建maven项目2. 引入依赖(servlet,jackson,mysql)3.创建必要的目录4. 编写代码5/6. 打包部署程序——直接基于smart Tomcat如何下载tomcat如何下载smart Tomcat7. 在浏览器中验证编写数据库的操作代码1.创建数据库/表结构—数据库设计…