【JavaWeb】网络编程概念 + Socket套接字 + UDP/TCP编程

news2024/11/25 12:00:21

目录

网络编程基础概念

发送端与接受端

请求与响应

客户端与服务器

常见的客户端服务器模型

Socket套接字

回显(echo)程序

UDP版的回显程序

服务器代码

客户端代码 

结果

TCP版的回显程序

服务器代码

客户端代码

结果


网络编程基础概念

网络编程,指网络上的主机,通过 不同的进程 ,以编程的方式实现 网络通信(网络数据传输) 。只需要满足进程不同,同一个主机的不同进程相互之间进行网络通信也是网络编程。
下面介绍一些网络编程的常用的基础概念。

发送端与接受端

发送端:发送数据的进程。发送端主机:该进程所在的主机(源主机)。

接收端:接受数据的进程。接受端主机:该进程所在的主机(目的主机)。

发送端和接收端只是相对的。


请求与响应

请求:发送端到接受端所发出的要求。

响应:接收端到发送端所回复的动作。

一般来说,一次完整的网络通信既要有请求也要有响应。


客户端与服务器

客户端:发送请求并获取服务的一端。

服务器:接受请求并提供服务的一段。

常见的客户端服务器模型

如下图 


Socket套接字

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

简单的理解为,在下图中的分层里,是传输层协议和应用层协议之间的API。

传输层中最核心的两个协议就是TCP和UDP。因此Socket也提供了两种风格的API。还有一种unix,现在基本不使用了。

在Java中,JVM把这个Socket又封装了一下,以供我们使用,名字还是Socket。


回显(echo)程序

回显服务器:客户端发过来什么,原封不动的在发回去。

UDP版的回显程序

UDP的特点:无连接,不可靠传输,面向数据报,全双工

连接:客户端和服务器之间进行通信之前先要相互联系上。

可靠传输:下篇文章详细讲UDP协议是会提到。

面向数据报:所传输的数据都是以“数据报”为单位的。

全双工:一个通信通道,同一时间内,既可以发送也可以接收数据。(因为通信通道里有8条网线)

半双工:一个通信通道,同一时间内,只能发送或接收数据。

在写程序之前,先画出一个图有更全面的认识。

服务器代码

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

// UDP版本的回响服务器
class UdpEchoServe {

    // 通过DatagramSocket实例化的对象serverSocket来操作网卡
    // 操作系统把这个对象抽象成文件来管理的
    private DatagramSocket serverSocket = null;

    // 传输信息的单位不是简单的字节,而是包装成了DatagramPacket这个类来使用
    // 这里第一个参数是一个返回型参数,客户端传过来的信息可以存到这里面
    // 第二个参数是最多可以放多少空间来存信息
    DatagramPacket serverPacket = new DatagramPacket(new byte[8192], 8000);

    // 构造方法
    // 建议服务器手动绑定端口号(1024~65535之间任意选择一个整数)
    // 如果系统自动分配的话,客户端就不知道服务器是哪个端口了,就无法通信了
    // 服务器的端口我们还是可以控制查询的,主动权在我们手中,客户端则是未知的
    // 端口号可以定位进程本质上就是serverSocket对象绑定了端口号
    public UdpEchoServe(int port) throws SocketException {
        this.serverSocket = new DatagramSocket(port);
    }

    // 通过这个方法让服务器启动
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 如果没有收到请求,就会阻塞等待
            // 请求填充到reservePacket中的数组里了
            serverSocket.receive(serverPacket);

            // 使用work方法代表处理请求
            // 先把请求变成字符串,后续更加方便操作它
            String workBeforeString = new String(serverPacket.getData(), 0, serverPacket.getLength());
            String workAfterString = work(workBeforeString);

            // 请求已经处理完毕,需要返回给客户端
            // 把处理结果打包成DatagramPacket类型来传输            这里不能直接用字符串的长度,而是要转成字节后的长度
            DatagramPacket retPacket = new DatagramPacket(workAfterString.getBytes(), workAfterString.getBytes().length,
                    // 同时还要从serverPacket中拿到客户端的地址(端口号和IP)
                    serverPacket.getSocketAddress());

            // 打包完成,返回数据
            serverSocket.send(retPacket);

            // 在服务器这里打印一下请求响应的中间结果
            System.out.printf("[请求方的IP:%s | 请求方的端口号: %d] = 请求:%s | 处理结果:%s\n",
                    serverPacket.getAddress().toString(), serverPacket.getPort(), workBeforeString, workAfterString);
        }
    }

    protected String work(String str) {
        // 由于是回显服务器,处理的结果就直接返回原字符串即可
        return str;
    }

}

public class UdpServer {

    public static void main(String[] args) throws IOException {
        // 指定端口号
        UdpEchoServe udpEchoServe = new UdpEchoServe(9999);
        udpEchoServe.start();

    }

}

客户端代码 

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

// UDP版本的回响客户端
class UdpEchoClient {

    // 和客户端的一样,也是要通过这个类实例出来的对象来操作网卡
    private DatagramSocket clientSocket = null;
    // 同时还需要设定服务器的IP和端口,这样才能把消息发给客户端
    // 服务器则不用设定,因为服务器作为被动的接受数据的一方,这数据中就包含了客户端的IP和端口
    private String serverIp = null;
    private int serverPort = -1;

    // 构造方法
    // 在构造方法中设定客户端端口、服务器IP和服务器端口
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        // 建议随机分配客户端的端口
        // 因为随机分配的端口是空闲的端口,如果自己指定,则不一定是
        this.clientSocket = new DatagramSocket();
        this.serverIp = serverIp;
        this.serverPort = serverPort;
    }

    // 通过start方法启动客户端
    public void start() throws IOException {
        System.out.println("客户端启动!");
        while (true) {
            System.out.println("请输入:");
            Scanner scanner = new Scanner(System.in);

            // 输入数据
            String data = scanner.next();
            if (data.equals("exit")) {
                clientSocket.close();
                break;
            }

            // 收到数据后打包数据  同样是以DatagramPacket为单位
            // 这里把字符串转成字节,还有字节的实际个数也要传过去
            // InetAddress这个类中的getByName方法可以把"127.0.0.1"这个字符串转成32位二进制数
            DatagramPacket clientPacket = new DatagramPacket(data.getBytes(), data.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);

            // 发送数据
            clientSocket.send(clientPacket);

            // ... ... 等待服务器处理,然后接收处理后的数据

            // 接收数据
            DatagramPacket backedData = new DatagramPacket(new byte[8192], 8000);
            // 把收到的数据填到数组当中
            clientSocket.receive(backedData);

            // 解析数据 然后使用数据
            work(backedData);
        }

    }

    // 处理返回数据的方法
    private void work(DatagramPacket backedData) {
        String useData = new String(backedData.getData(), 0, backedData.getLength());
        // 解析好之后使用数据
        System.out.println(useData);
    }
}

public class UdpClient {

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

}

结果

如果像多创造一些客户端,在IDEA中可以勾选允许创建多个实例。

  

  

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// TCP版本的回显服务器
class TcpEchoServer {

    // 同UDP,这里的类变成了ServerSocket来管理TCP版的服务器
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        // 手动绑定服务器端口号
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        // 启动服务器
        System.out.println("服务器启动!");

        // 要先和客户端建立连接
        // 通过Socket这个类与客户端建立连接
        // 如果客户端没有连接,accept就会进行阻塞

        // Ⅰ 这里只能处理一个客户端
        // Socket connectionSocket = serverSocket.accept();
        // 具体的连接和收发数据给connectionWork这个方法
        // connectionWork(connectionSocket);

        // Ⅱ 通过多线程可以处理多个客户端——多搞几个连接
//        while (true) {
//            Thread threadConnection = new Thread(() -> {
//                try {
//                    Socket connectionSocket = serverSocket.accept();
//                    connectionWork(connectionSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            threadConnection.start();
//        }

        // Ⅲ 使用线程池
        while (true) {
            ExecutorService threadPoolExecutor = Executors.newCachedThreadPool();
            threadPoolExecutor.submit(()-> {
                try {
                    Socket connectionSocket = serverSocket.accept();
                    connectionWork(connectionSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    // 这里处理一个客户端连接和收发数据
    private void connectionWork(Socket connectionSocket) throws IOException {
        // 能到这里说明客户端已经建立连接了·
        System.out.printf("[IP: %s | 端口号: %d] 客户端上线!\n",
                connectionSocket.getInetAddress().toString(), connectionSocket.getPort());

        try (InputStream inputStream = connectionSocket.getInputStream();
             OutputStream outputStream = connectionSocket.getOutputStream()) {
            // 可能会有很多次的请求,通过循环来一直保持连接(长连接)
            while (true) {
                // 把读进来的请求用scanner装起来,更方便操作
                Scanner scanner = new Scanner(inputStream);

                // 如果请求没了,说明读完了就退出
                if (!scanner.hasNext()) {
                    System.out.printf("[IP: %s | 端口号: %d] 客户端下线!\n",
                            connectionSocket.getInetAddress().toString(), connectionSocket.getPort());
                    break;
                }

                // 把请求从scanner中读出来, 不包括空格 会车符等空白字符串
                String workBeforeString = scanner.next();
                // 放到 work方法中加工
                String workAfterString = work(workBeforeString);

                // 通过OutPutStream返回结果结果
                // 由于OutPutStream中没有可以直接把字符串写进去的方法
                // 这里通过PrintWriter来代替写
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 把处理后的字符串+回车符写进去
                printWriter.println(workAfterString);
                // 在把数据冲刷一下, 确认写进去了
                printWriter.flush();

                System.out.printf("[请求方的IP:%s | 请求方的端口号: %d] = 请求:%s | 处理结果:%s\n",
                        connectionSocket.getInetAddress().toString(), connectionSocket.getPort(), workBeforeString, workAfterString);
            }
        }
        finally {
            // 每一个客户端请求完成后就要关闭请求连接的这个资源, 否则会越来越多
            // 在finally中确保一定会关闭的
            connectionSocket.close();
        }
    }


    private String work(String workBeforeString) {
        return workBeforeString;
    }
}

public class TcpServer {
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(5050);
        tcpEchoServer.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;

class TcpEchoClient {
    // 通过Socket这个类来连接服务器
    private Socket connectionSocket = null;

    public TcpEchoClient (String serverIP, int serverPort) throws IOException {
        // 这里实例化的时候就与服务器建立连接了
        // 同时Socket的构造方法可以识别"127.0.0.1"这样字符串类型的点分十进制
        connectionSocket = new Socket(serverIP, serverPort);
    }

    public void start() throws IOException {
        System.out.println("客户端启动! ");
        try(InputStream inputStream = connectionSocket.getInputStream();
            OutputStream outputStream = connectionSocket.getOutputStream()) {
            while (true) {
                // 先输入数据
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入:");
                String data = scanner.next();
                if (data.equals("exit")) {
                    break;
                }
                // 打包数据发给服务器
                // 通过PrintWriter类来包装数据
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 这里还把 \n 写进去了,为了分割数据
                printWriter.println(data);
                // 确保数据发送出去了
                printWriter.flush();

                // ... ...等到客户端处理完成后接收结果
                // 读取客户端的响应
                Scanner result = new Scanner(inputStream);
                String resultString = result.next();

                // 最后打印结果
                System.out.println(resultString);
            }
        }
    }
}

public class TcpClient {

    public static void main(String[] args) throws IOException {

        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 5050);
        tcpEchoClient.start();

    }

}

结果


有什么错误评论区指出。希望可以帮到你。

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

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

相关文章

2.5|物联网应用系统设计|复习提纲|提问背诵

基础概念总结掌握Linux常用的基本命令功能、语法结构,用法等。具体命令参考实验指导书、相关PPT等资料内容。什么是操作系统(OS)?操作系统是用以控制和管理计算机系统资源,方便用户使用的程序和数据结构的集合。在所有…

零基础学习Python的一点建议

Python语言的火爆程度,真的是超过了任何一门计算机语言,当然火爆程度里面含有赶上了人工智能这个领域的风口,但是大部分的原因是Python易学,语法对小白非常友好,总结一句话,Python语言能做很多事情&#xf…

亿级高并发电商项目-- 实战篇 --万达商城项目 六(编写角色管理、用户权限(Spring Security认证授权)、管理员管理等模块)

专栏:高并发---前后端分布式 👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者 📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信…

使用nvm管理node

nvm介紹 node的版本管理器,可以方便地安装&切换不同版本的node 我们在工作中,可以会有老版本的node的项目需要维护,也可能有新版本的node的项目需要开发,如果只有一个node版本的话将会很麻烦,nvm可以解决我们的难点…

node 拉取github开源漏洞

我们可以通过github的open api 来拉取一些信息。这里主要是拉取 github 开源漏洞中的漏洞信息 Github Explorer github Explorer 是一个在线工具,登录之后,我们可以在左侧输入GraphQL 查询语句,之后就可以查询相关的信息。例如:…

B树和B+树,红黑树作为索引的区别

索引是一种数据结构,帮助我们在mysql表中更高效获取数据的数据结构 常用作为索引的数据结构:二叉树,红黑树,Hash表,B树,B树 下面的数据表中有两个字段,第一个字段是col1,第二个字段…

如何在Qt中设置背景图片,且不覆盖其它控件

正常情况,我们直接通过在样式表里设置背景图片会出现背景图片覆盖其它控件的情况,比如下面操作: 首先右击空白处,点击改变样式表。 然后选择background-image 然后点击铅笔图标 之后我们要先添加前缀,也就是我们…

使用 Three.js 后处理的粗略铅笔画效果

本文使用Three.js的后处理创建粗略的铅笔画效果。我们将完成创建自定义后处理渲染通道、在 WebGL中实现边缘检测、将法线缓冲区重新渲染到渲染目标以及使用生成和导入的纹理调整最终结果的步骤。翻译自Codrops,有改动。 Three.js 中的后处理 Three.js中的后处理是一…

1.9 实践项目——爬取学生信息

1. 项目简介设计一个 Web 服务器 server.py,它读取 students.txt 文件中的学生数据,以表格的形式呈现在网页上,其中 students.txt 的格式如下:No,Name,Gender,Age1001,张三,男,201002,李四,女,191003,王五,男,21设计一个客户端的爬…

【Junit5】就这篇,带你从入门到进阶

目录 前言 1.前置工作 2、注解 2、断言(Assertions类) 2.1、断言 匹配/不匹配 2.2、断言结果 为真/为假 2.3、断言结果 为空/不为空 3、用例的执行顺序 3.1、用例执行顺序是怎样的? 3.2、通过order注解来排序 4、参数化 4.1、单…

Firefox 110, Chrome 110, Chromium 110 官网离线下载 (macOS, Linux, Windows)

Mozilla Firefox, Google Chrome, Chromium, Apple Safari 请访问原文链接:https://sysin.org/blog/chrome-firefox-download/,查看最新版。原创作品,转载请保留出处。 作者主页:www.sysin.org 天下只剩三种(主流&am…

feign技巧 - form方式传值

feign技巧 - form方式传值。 0. 文章目录1. 前言2. 调用样例3. 原理解析3.1 feign端序列化参数3.2 SpringMVC服务端解析参数3.3 补充 - 继承关系不会被传递的原因3.4 补充 - 不能使用GET。4. 总结1. 前言 直接正题。 如何使用feign进行fom表单方式的请求调用,以及其…

leaflet 上传KMZ文件,并在map上显示(062)

第062个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中本地上传包kmz文件,解析并在地图上显示图形。在制作本示例的过程中,还有点缺憾,就是kmz中的图片文件没有在jszip中处理好,只能先解压缩后,将图片文件放到public/文件加下,暂时留一个遗憾点,以后再做…

又发现一个ChatGPT体验站,辅助写代码真方便

♥️ 作者:Hann Yang ♥️ 主页:CSDN主页 ♥️ 2022博客之星Top58,原力榜Top10/作者周榜Top13 ♥️ “抢走你工作的不会是 AI ,而是先掌握 AI 能力的人” ChatGPT 美国OpenAI研发的聊天机器人程序,于2022年11月30日发…

【刷题笔记】--两数之和Ⅳ,从二叉树中找出两数之和

法一:深度搜索中序遍历双指针 思路:通过中序遍历二叉树得到一个递增的数列,再在这个递增的二叉树中找到这两数。 主要学到双指针这个方法。 对于一般数列,我们要找到两数满足其之和等于目标数,我们一般会进行暴力&a…

C++请求SpringBoot的接口问题记录

问题描述最近忙一个小东西,遇到一个很有意思的问题,记录一下。 需求非常简单,就是java侧提供一个接口给C侧调用。 接口按照业务规范提供出来了,在postman中请求一下,出入参都正常。 关于这个接口请求方式为postJson方式…

C++:提高篇: 栈-寄存器和函数状态:栈指针帧指针详解

栈指针和帧指针前言1、EBP和ESP详解2、push ,leave ,call汇编指令分析3、下面用一个图总结前言 🚗🚗🚗:在刚接触 ESP和EBP概念时,我一直认为:ESP指向栈顶指针,EBP指向栈…

为什么说百度下个月推出文心一言会被ChatGPT完全碾压

作者,姚远: Oracle ACE(Oracle和MySQL数据库方向)华为云MVP 《MySQL 8.0运维与优化》的作者中国唯一一位Oracle高可用大师拥有包括 Oracle 10g和12c OCM在内的20数据库相关认证。曾任IBM公司数据库部门经理现在一家第三方公司任首…

操作系统——2.操作系统的特征

这篇文章,我们来讲一讲操作系统的特征 目录 1.概述 2.并发 2.1并发概念 2.1.1操作系统的并发性 3.共享 3.1共享的概念 3.2共享的方式 4.并发和共享的关系 5.虚拟 5.1虚拟的概念 5.2虚拟小结 6.异步 6.1异步概念 7.小结 1.概述 上一篇文章,我们…

实时数据仓库

1 为什么选择kafka? ① 实时写入,实时读取 ② 消息队列适合,其他数据库受不了 2 ods层 1)存储原始数据 埋点的行为数据 (topic :ods_base_log) 业务数据 (topic :ods_base_db) 2)业务数据的有序性&#x…