IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 【计算机网络】网络编程套接字之TCP套接字——含超详细代码注释 -> 正文阅读

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


引言:

上篇文章我们一起学习了【计算机网络】网络编程套接字之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();
    }
}

在这里插入图片描述

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:56:47  更:2022-05-05 12:00:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:55:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码