JavaEE: 创造无限连接——网络编程中的套接字

文章目录

  • Socket套接字
    • TCP和UDP的区别
      • 有连接/无连接
      • 可靠传输/不可靠传输
      • 面向字节流/面向数据报
      • 全双工/半双工
    • UDP/TCP api的使用
      • UDP
        • DatagramSocket
        • DatagramPacket
        • InetSocketAddress
        • 练习
      • TCP
        • ServerSocket
        • Socket
        • 练习


Socket套接字

Socket是计算机网络中的一种通信机制,通过在应用程序之间建立网络连接,实现数据的传输和交流。它提供了一组接口和协议,使得网络上的不同设备或进程能够进行通信。

换句话说,就是操作系统给应用程序(传输层给应用层)提供的API起了个名字,就叫做Socket.

我们通过代码来直接操作网卡,不太好操作.
因为网卡有很多种型号,之间提供的api都会有差别.
为了方便程序员操作网卡,操作系统就把网卡这个概念给封装成Socket,应用程序员就不必关注硬件的差异和细节,只需要操作Socket对象就能间接的操作网卡.
就跟遥控器差不多~

需要注意的是,接下来要说的都是操作系统提供的Java版本的Socket api.不是系统原生的api,而是JDK封装好的!

Socket api提供了两组不同的api

  • UDP有一套
  • TCP也有一套

TCP和UDP的区别

TCP: 有连接,可靠传输,面向字节流,全双工.
UDP: 无连接,不可靠传输,面向数据包,全双工.

有连接/无连接

此处谈到的连接,是"抽象"的连接.
通信双方,如果保存了通信对端的信息,就相当于是"有连接",如果不保存对端的信息,就是"无连接".

举个例子,比方说结婚.

结婚证,一式两份,本子上写着新郎和新娘两个人的名字/照片等信息.

一份由新郎保存.
一份由新娘保存.

此时结婚的这两个人就相当于建立了"抽象的/逻辑上的"连接.

可靠传输/不可靠传输

此处谈到的"可靠",不是指100%能到达对方,而是"尽可能".
因为网络环境非常复杂,存在很多不确定的因素~

再厉害的技术,也顶不过挖掘机一铲子~

相对来说"不可靠",就是指完全不考虑数据是否能到达对方.

TCP内置了一些机制,能够保证可靠传输

  1. 感知到对方是不是收到了
  2. 重传机制,在对方没收到的时候进行重试

UDP则没有可靠性机制,完全不管发出去的数据是否能够到达对方.

面向字节流/面向数据报

TCP是面向字节流的,TCP的传输过程就和文件流/水流是一样的特点.
在这里插入图片描述
UDP是面向数据报的.
此时,传输数据的基本单位就不是字节了,而是"UDP数据报".
一次发送/接收,必须发送/接收完整的UDP数据报.

全双工/半双工

全双工: 一个通信链路,可以发送数据,也可以接收数据.(双向通信)

半双工: 一个通信链路,只能发送/接收(单向通信).

UDP/TCP api的使用

UDP

DatagramSocket

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

DatagramSocket构造方法:
在这里插入图片描述

DatagramSocket方法:
在这里插入图片描述

DatagramPacket

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

DatagramPacket构造方法:
在这里插入图片描述

DatagramPacket方法:
在这里插入图片描述
构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress来创建.

InetSocketAddress

InetSocketAddress (SocketAddress的子类)构造方法:
在这里插入图片描述

练习

写一个最简单的客户端服务器程序,“回显服务器”(echo server).客户端发啥样的请求,服务器就返回啥样的响应.

服务器代码

package javaEE.Internet;

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

public class A {
    private DatagramSocket socket = null;
	// SocketException 这个异常是IOException的子类
    public A(int port) throws SocketException {
    	// 对于服务器这一端来说,需要在Socket对象创建的时候,就指定一个端口号,作为构造方法的参数
    	// 后续服务器开始运行之后,操作系统就会把端口号和该进程关联起来.
    	// 创建Socket一定要指定端口号,服务器必须是指定了端口号,客户端主动发起请求的时候,才能找到服务器
        socket = new DatagramSocket(port);
        // 在调用这个构造方法的过程中,jvm就会调用系统的Socket api来完成"端口号-进程"之间的关联动作
        // 对于一个系统来说,同一时刻,一个端口号,只能被一个进程绑定
        // 但是一个进程可以绑定多个端口号(通过多个Socket对象来完成)
    }

    // 通过start来启动服务器的核心流程
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 通过死循环来不停的处理客户端的请求

            // 1. 读取客户端的请求并解析
            // DatagramPacket自身需要存储数据,但是存储数据的空间具体多大,需要外部来定义
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            //	receive是从网卡上读取的数据,但是调用receive的时候,网卡上可不一定就有数据.
            // 如果网卡上收到数据了,receive立即返回获取到的数据,如果网卡上没有收到数据,receive就会阻塞等待,一直等待到真正收到数据为止.
            socket.receive(requestPacket);
            // 上述收到的数据是二进制byte[]的形式体现的,后续代码如果要进行打印之类的处理操作
            // 那就需要转换成字符串才好处理
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2. 根据请求计算响应,由于此处是回显服务器,响应就是请求.
            String response = process(request);

            // 3. 把响应写回到客户端
            // response.getBytes().length 这里容易写错,容易写成response.length().
            // response.getBytes().length获取字节数组,得到字节数组的长度,单位是"字节".
            // response.length()获取字符串中"字符"的个数,单位是"字符".
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            // UDP有一个特点,无连接.
            // 所谓的连接,就是通信双方,保存对方的信息(IP+端口)
            // DatagramPacket 这个对象中,不持有对方(客户端)的ip和端口.
            // 在进行send的时候,就需要在send的数据包里,把要发给谁这样的信息写进去,才能够正确的把数据返回.
            socket.send(responsePacket);

            // 4. 把日志打印一下
            System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress(), requestPacket.getPort(),
                    request, response);
        }
    }

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

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


客户端代码

package javaEE.Internet;

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

public class B {
    private DatagramSocket socket = null;
    private String serverIP;
    private int severPort;

    public B(String serverIP, int severPort) throws SocketException {
    	// 客户端这边,创建Socket,最好不要指定端口号
    	// 客户端是主动的一方,不需要让服务器来找他
    	// 客户端就不需要指定端口号了(不指定不代表没有,客户端这边的端口号是系统自动分配了一个端口)
    	// 这里还有一个重要的原因,如果客户端这里指定了端口号之后,由于客户端是在用户的电脑上运行的,天知道用户的电脑上都有哪些程序,都已经占用了哪些端口号了
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.severPort = severPort;
    }

    public void start() throws IOException {
        System.out.println("启动客户端!");
        Scanner scanner = new Scanner(System.in);

        while (true) {
            // 1. 从控制台读取到用户的输入
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 构造出一个UDP请求,发送给服务器
            // 此处是给服务器发送数据,发送数据的时候UDP数据报里就需要带有目标的IP和端口
            // 接收数据的时候,构造出的UDP数据报,就是一个空的数据报.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(this.serverIP),this.severPort);
            socket.send(requestPacket);
            // 3. 从服务器读取到响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            // 4. 把响应打印到控制台上
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        B b = new B("127.0.0.1",9090);
        // "127.0.0.1"这个是特殊的IP,环回IP
        // 这个IP就代表本机,如果客户端和服务器在同一个主机上,就使用这个IP
        b.start();
    }
}

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

在以上代码的基础上,实现一个"英译汉"的效果.

"英译汉"服务器代码:

package javaEE.Internet;

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

// 实现一个"英译汉"
public class C extends A{

    private Map<String,String> dict = new HashMap<>();
    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"未知单词");
    }

    public C(int port) throws SocketException {
        super(port);
        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("pig","小猪");
        dict.put("sheep","小羊");
        // 真实的服务器,需要很多的单词,可能是上万个.
    }

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

客户端代码不需要改动~
运行效果:
在这里插入图片描述

TCP

ServerSocket

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

ServerSocket构造方法:

在这里插入图片描述

同一个协议下,一个端口号只能被一个进程绑定.
比如,9090端口,在UDP下被一个进程绑定了;9090这个端口,还可以在TCP下被另一个进程绑定.

ServerSocket方法:
在这里插入图片描述

Socket

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

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

Socket构造方法:
在这里插入图片描述

Socket方法:
在这里插入图片描述
InputStream和OutputStream称为"字节流".
前面针对文件操作的方法,针对此处的tcp socket来说,也是完全适用的.

这里为啥没有类,来表示一个"TCP数据报"呢?
TCP是面向字节流的,TCP上传输数据的基本单位就是byte.
UDP是面向数据报,UDP这里需要定义专门的类,来表示UDP数据报,作为UDP传输的基本单位

练习

编写TCP回显服务器.

服务器代码:

package javaEE.Internet;

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 D {
    private ServerSocket serverSocket = null;

    public D(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");
        while (true) {
        	// 像ServerSocket,DatagramSocket,它们的生命周期,都是跟随整个进程的
        	// 这里的clientSocket是"连接级别"的数据
        	// 随着客户端断开连接,这个socket也就不再使用了.(即使是同一个客户端,断开之后,重新连接,也是一个新socket,和旧socket不是同一个了)
        	// 因此,这样的socket就应该主动关闭掉,避免出现文件资源泄漏的问题.
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

    // 针对一个连接,提供处理逻辑
    private void processConnection(Socket clientSocket) throws IOException {
        // 先打印一下客户端的信息
        System.out.printf("[%s,%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // 获取到socket中持有的流对象.
        // TCP是全双工通信的通信,一个socket对象,既可以读,也可以写~
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用Scanner包装一下inputStream,就可以更方便的读取这里的请求数据了
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                // 1. 读取请求并解析
                if (!scanner.hasNext()) {
                    // 如果Scanner无法读取出数据,说明客户端关闭了连接,导致服务器这边读取到"末尾"
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算相应
                String response = process(request);

                // 3. 把响应写回给客户端
                // 此处可以按照字节数组来写,也可以有另一种写法
                // outputStream.write(response.getBytes());
                // printWriter.println(response);可以让我们在写入的时候加上 \n 
                // 由于上面if语句里写了!scanner.hasNext(),这就意味着请求应该是以"空白符"(空格,回车,制表符,垂直制表符,翻页符...)结尾的.
                // 因此此处就约定,使用\n来作为请求和响应的结尾标志.
                // 后续客户端,也会使用scanner.next来读取响应.
                printWriter.println(response);
                // 由于printWriter这样的类,以及很多IO流中的类,都是"自带缓冲区"的
                // 引入缓冲区之后,进行写入操作,不会立即触发IO,而是先放到内存缓冲区中,等缓冲区里攒了一些后,再统一进行发送
                // 由于此处的数据比较少,因此这样的数据就会一直停留在缓冲区中,出不去了~
                // 为了让较少的数据也能发送出去,这里就可以引入flush操作,来主动"刷新缓冲区".
                printWriter.flush();

                // 4. 打印日志
                System.out.printf("[%s,%d] req=%s; resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.printf("[%s,%d]客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
            clientSocket.close();
        }
    }

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

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

客户端代码:

package javaEE.Internet;

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 E {
    private Socket socket = null;

    public E(String serverIp, int severPort) throws IOException {
        socket = new Socket(serverIp, severPort);
    }

    public void start() {
        System.out.println("客户端启动!");

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            Scanner scannerIn = new Scanner(System.in);
            PrintWriter printWriter = new PrintWriter(outputStream);

            while ((true)) {
                // 1. 从控制台读取数据
                System.out.print("->");
                String request = scannerIn.next();
                // 2. 把请求发送给服务器
                printWriter.println(request);
                printWriter.flush();
                // 3. 从服务器读取相应
                if (!scanner.hasNext()) {
                    break;
                }
                String response = scanner.next();
                // 4. 打印相应结果
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

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

本文到这里就结束啦~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第五十八天 | 拓扑排序精讲-软件构建

目录 软件构建 思路 拓扑排序的背景 拓扑排序的思路 模拟过程 判断有环 写代码 方法一&#xff1a; 拓扑排序 软件构建 题目链接&#xff1a;卡码网&#xff1a;117. 软件构建 文章讲解&#xff1a;代码随想录 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文…

机器人的动力学——牛顿欧拉,拉格朗日,凯恩

机器人的动力学推导方法有很多&#xff0c;常用得有牛顿&#xff0c;拉格朗日&#xff0c;凯恩等方法&#xff0c;接下来&#xff0c;简单说说他们之间的使用。注&#xff1a;这里不考虑怎么来的&#xff0c;只说怎么应用。 参考1&#xff1a;4-14动力学分析方法-牛顿—欧拉方…

聚焦API安全未来,F5打造无缝集成的解决方案

研究发现&#xff0c;目前超过90%的基于Web的网络攻击都以API端点为目标。随着对API使用需求的增加&#xff0c;这些攻击还会持续增长。现代企业需要一种动态防御策略&#xff0c;在风险升级成代价高昂、令人警惕且往往无法预防的API安全漏洞之前&#xff0c;发现并降低风险。 …

数据库提权【笔记总结】

文章目录 UDF提权以有webshell只有数据库权限条件复现msf工具sql语句提权 MOF提权前言条件复现msf工具php脚本提权 sqlserver提权前言条件xp_cmdshell提权复现 沙盒提权介绍复现 Oracle提权靶场搭建执行任意命令复现 通过注入存储过程提权&#xff08;低权限提升至DBA&#xff…

C++从入门到起飞之——多态 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. 多态的概念 2. 多态的定义及实现 2.1 多态的构成条件 2.1.1 实现多态还有两个必须重要条件&…

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

通用接口开放平台设计与实现——(31)API服务线程安全问题确认与修复

背景 在本系列的前面一篇博客评论中&#xff0c;有小伙伴指出&#xff0c;API服务存在线程安全问题&#xff1a; https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405 今天来确认下&#xff0c;线程是否安全&#xff1f;如不安全&#xff0c;如何…

高配小主机加装SSD固态硬盘,我选择性能与设计兼备的希捷酷鱼 530

高配小主机加装SSD固态硬盘&#xff0c;我选择性能与设计兼备的希捷酷鱼 530 哈喽小伙伴们好&#xff0c;我是Stark-C~ 我最近入手了零刻的一款新发布的 GTi12 Ultra高性能迷你主机&#xff0c;其出色的配置与强大的功能让我有了将它用作主力机的打算。不过因为它的高配版本搭…

【记录一下VMware上开虚拟端口映射到公网】

材料 win11 和装在vmware上的ubuntu 步骤一在Ubuntu上配置静态地址&#xff0c;配置如下 vim /etc/netplan/01-network-manager-all.yaml(此文件看系统上对应的是哪个文件&#xff0c;建议先备份)network:version: 2renderer: NetworkManagerethernets:ens33:dhcp4: falseadd…

四十一、完成内容添加功能(使用go测试方法)

目录 一、添加model 二、完成相关dao 三、使用测试类进行测试 1、把光标防止要测试的方法上&#xff0c;右击并选择 2、自动会生成一个以dao文件加_test命名的文件 3、在其中完善方法并完成测试 四、完成content_create_handle 一、添加model 按数据库字段以及字段格式完…

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…

基于SpringBoot+Vue的剧本杀管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

AIGC7: 高通骁龙AIPC开发者沙龙过程记录A

图中是一座高耸的宫殿。 就像AI的出现&#xff0c;慢慢初现端倪&#xff0c;头角峥嵘。 背景 一直以来都比较关注AI的发展&#xff0c;有幸再一次参加异常AI的盛会。 从我的角度看。 高通是一家生产芯片的公司&#xff0c;国内的小米&#xff0c;荣耀&#xff0c;Oppo , Vi…

华为为什么要做三折叠屏手机?

前些天我做了一条视频&#xff0c;关于讲华W的新的三折叠屏手机。我说我有点失望&#xff0c;结果引起了华W的同事的一些关注。于是&#xff0c;华W几位高管都跑过来&#xff0c;跟我解释为什么会出现这样的一个状态。 我才知道&#xff0c;这款手机他们其实是亏着钱在卖的。因…

【测试】——Selenium API (万字详解)

&#x1f4d6; 前言&#xff1a;本文详细介绍了如何利用Selenium进行Web自动化测试&#xff0c;包括定位元素&#xff08;如cssSelector和xpath&#xff09;、常用操作函数&#xff08;如点击、输入等&#xff09;、窗口管理、键盘鼠标事件和浏览器导航&#xff0c;以及处理弹窗…

图说GPT网络结构(参数量与计算量估计)

现在AI领域的主流模型几乎都是Transformer网络架构衍生而来。大热的LLM中的生成类模型很多都是来自于Transformer的变体&#xff0c;即decoder only架构。而GPT就是该类中的经典模型。尽管现在变体甚多&#xff0c;但大多没有根本性地改变其套路。 为了阐述方便&#xff0c;首…

音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

数据安全治理

数据安全治理 1.数据安全治理2.终端数据安全加密类权限控制类终端DLP类桌面虚拟化安全桌面 3.网络数据安全4.存储数据安全5.应用数据安全6.其他话题数据脱敏水印与溯源 7.UEBA8.CASB 1.数据安全治理 数据安全治理最为重要的是进行数据安全策略和流程制订。在企业或行业内经常发…

[大语言模型-论文精读] 以《黑神话:悟空》为研究案例探讨VLMs能否玩动作角色扮演游戏?

1. 论文简介 论文《Can VLMs Play Action Role-Playing Games? Take Black Myth Wukong as a Study Case》是阿里巴巴集团的Peng Chen、Pi Bu、Jun Song和Yuan Gao&#xff0c;在2024.09.19提交到arXiv上的研究论文。 论文: https://arxiv.org/abs/2409.12889代码和数据: h…

通过logstash同步elasticsearch数据

1 概述 logstash是一个对数据进行抽取、转换、输出的工具&#xff0c;能对接多种数据源和目标数据。本文介绍通过它来同步elasticsearch的数据。 2 环境 实验仅仅需要一台logstash机器和两台elasticsearch机器&#xff08;elasticsearch v7.1.0&#xff09;。本文用docker来模…