网络编程(UDP\TCP回显服务器)

目录

套接字socket

TCP和UDP特点比较 

特点

比较

UDP回显服务器/客户端的编写

UDP的socket api

回显服务器

客户端

TCP回显服务器/客户端的编写

TCP的socket api

回显服务器

客户端

优化服务器

1.关闭服务器创建的socket对象

2.引入线程池,为多个客户端提供服务 


套接字socket

操作系统提供的网络编程的API称为“socket api”,在传输层中,TCP和UDP两种协议的特点和差异非常大,操作系统中就提供了两套api来分别表示:

流式套接字 -> TCP使用

数据报套接字 -> UDP使用

除此之外操作系统还有其他的关于网络编程的API,比如Unix域套接字,只是本地主机上的进程和进程之间的通信方式,不能跨主机通信,现在很少使用。

TCP和UDP特点比较 

特点

TCP:有连接,可靠传输,面向字节流,全双工。

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

比较

有连接vs无连接

有连接则是通信双方保存对方的信息,删除信息则断开连接,无连接则是通信双方不需要保存各自信息。在计算机中,各自保存双方的信息,就认为是建立了一个抽象的连接。

可靠传输vs不可靠传输

可靠 != 安全,可靠值要传输的数尽可能的全部传输给对方,在网络通信过程中,可能会存在多种意外情况,比如丢包,丢包的过程是随机的,无法预知。为了对抗丢包,引入了可靠传输特点,TCP具体这一特点,内部提供了一系列机制来实现可靠传输,UDP则是不可靠传输,传输数据时不会关心数据是否到达,接收方是否收到。 

面向字节流vs面向数据报:

TCP和文件操作都是面向字节流的,读写操作非常灵活。UDP面向数据报,传输的基本单位是一个个UDP数据报,每次读写只能读写一个完整的UDP数据报。

全双工vs版双工:

全双工:一条链路能够进行双向通信(TCP/UDP都是),在;一条链路上既可以接收也可以发送。

半双工:一条链路,只能进行单向通信,在Linux中,系统提供的一种软件资源:管道,就是半双工,接收和发送不能同时进行。

UDP回显服务器/客户端的编写

UDP的socket api

java对于系统提供的网络编程api(socket api)进行了进一步封装,进行UDP网络编程代码编写时,需要重点理解两个类:

(1)DatagramSocket:这个类是对操作系统socket概念的封装,系统中的socket可以理解为文件,socket文件可以视为网卡这个设备的抽象表示形式,针对socket文件的读写操作课相当于对网卡这个硬件设备进行读写。其实之前学习的普通文件,就是对硬盘这个硬件设备的抽象,直接操作硬盘不方便,借助文件进行操作就可以很方便的完成。类似于电视机的遥控器,通过遥控器来使用电视剧更加方便。

计算机中对具有”遥控属性“这样概念的叫做句柄(handle)。

(2)DatagramPacket:针对UDP数据报的抽象表示,一个DatagramPacket对象,就相当与一个UDP数据报,一次发送/一次接收就是传输了一个DatagramPacket对象。

回显服务器

Echo称为回显,正常服务器发送不同请求就会有不同响应,回显服务器就是请求发了什么,响应就是啥,这个过程不涉及计算和逻辑业务,是最简单的客户端服务器程序。

 编写服务器程序时,首先需要确定端口号,客户端是主动的一方,服务器是被动的一方,客户端需要找到服务器在哪。

IP地址(服务器所在主机的IP地址),port端口号(一个主机上,有多个程序都要进行网络通信,需要把那个程序用的哪个端口号记录下来,并要确保一个端口号不能被两个或多个进程关联)。

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

public class UdppEchoServer {
    private DatagramSocket socket = null;
    public UdppEchoServer(int port) throws IOException {
        socket = new DatagramSocket(port);
    }
}

可以看到抛出的一异常可以是IOException,也就是说明网络编程的本质是IO操作。

接下来要让服务器可以不停的处理请求,不停的返回响应:

第一步:读取请求并对请求进行解析,构造一个数据报类的实例,对客户端发送的数据进行接收,放入实例中,对数据进行解析,最后为了方便打印,将数据报中的二进制数拿出来,转换为String类型的数据,String有一个构造方法,通过字节数组来构造。

 DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
 socket.receive(requestPacket);
 String  request = new String(requestPacket.getData(),0, requestPacket.getLength());

第二步:根据请求返回响应,由于是回显服务器,计算请求的方法直接返回就行,返回的响应使用String进行接收。

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

第三步:将响应返回给客户端,发送时需要知道接收对象的IP和端口号,可以通过接收的UDP数据报拿到发送客户端的信息,拿到之后放到响应的数据报中使用send()方法发送,最后在服务器上面打印关键信息。

//把响应写回到客户端
//构造UDP数据包
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,
                requestPacket.getSocketAddress());
//发送请求到客户端
 socket.send(responsePacket);
 System.out.printf("[%s %d] req = %s, resp =%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);

让程序启动后不断运行,加上while循环,一个完整的UDP回显服务器就此完成了。

客户端

客户端需要有自己的端口号和ip地址,客户端和服务器在一台主机上时就可以写本地环回地址(127.0.0.1),端口号是服务器在创建socket时指定的端口号。

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverPort;
    private String serverIP;
    public UdpEchoClient(String serverPort,String serverIP) throws SocketException {
        socket = new DatagramSocket();
        this.serverPort = serverPort;
        this.serverIP = serverIP;
    }
}

 在创建socket对象时并没有指定端口号,这是因为操作系统会自动分配一个端口号,这个自动的端口号每次重启都会不一样。

服务器需要固定端口号,而客户端需要让系统自动分配:

(1)服务器要有固定端口号,是因为客户端需要主动给服务器发请求,如果服务器端口号不是固定的,客户端就会不知道把请求发给谁了。

(2)客户端需要系统自动分配,指定固定的端口号是不行的,指定客户端的端口号,可能会和客户端所在电脑上的其他程序冲突,一旦端口冲突,就会导致程序启动不了。服务器在自己手里,就算端口冲突也是可以调整的,但客户端不在本机上时,端口冲突难以解决。

客户端逻辑在编写时和服务器有相同之处,客户端发送请求到服务器后,使用UDP数据报来接受响应。

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private int serverPort;
    private String serverIP;
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverPort = serverPort;
        this.serverIP = serverIP;
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while (true) {
           //输入请求
            System.out.println("请输入请求: ");
            String request = scanner.next();
            //构造请求
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            //发送请求
            socket.send(requestPacket);
            //构造接收的数据报
            DatagramPacket responsePack = new DatagramPacket(new byte[4096],4096);
            //接收服务器返回的结果
            socket.receive(responsePack);
            //转换成String类型
            String response = new String(responsePack.getData(),0, responsePack.getLength());
            System.out.println(response);
        }
    }

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

 服务器与客户端同时启动,客户端发送消息,服务器都会回显回来,并在控制台打印相应的ip地址,端口号,请求和响应:

TCP回显服务器/客户端的编写

TCP的socket api

主要涉及两个类:ServerSocket和Socket。

ServerSocket是专门提供给服务器使用的,ServerSocket在 实例化时调用的构造方法中自带端口号,实例化的对象包含一个accept方法,是一个类似接通功能的方法。

Socket是给客户端和服务器都提供服务,通过Socket的构造方法能够和指定的服务器建立连接。

TCP中通过使用InputStream和OutputStream进行文件读写操作,通过两个get方法来获取socket内部流对象。

Socket socket = new Socket();
socket.getInputStream();
socket.getOutputStream();

Tcp是字节流传输,传输基本单位是字节。

ServerSocket和Socket的功能不同:对于服务器来说,需要上来与客户端建立连接,建立连接要使用ServerSocket对象的accept方法,方法的返回值为socket类型;服务器一启动就会执行到建立连接的位置,如果此时没有客户端连接那么accept方法就会进入阻塞状态,直到有客户端连接。也就是说,ServerSocket是用与建立连接使用的,连接后将socket对象交给socket进行处理。

回显服务器

 每次创建一个服务器对象都要创建一个SerrverSocket来连接客户端,创建服务器时要包含 端口号,否则客户端没有端口号就无法连接。

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

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

 在服务器中创建start方法用来启动服务器,调用start方法后服务器开始不断的执行客户端的请求,不停的处理客户端的请求就需要不停的与客户端建立连接,通过调用process()方法去处理客户端的请求,将和客户端建立连接的信息使用socket接收,传递给process方法:

public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            Socket socket = serverSocket.accept();
            possess(socket);
        }
    }

 实现process方法,要注意TCP是面向字节流传输,此时进行读写操作时需要使用InputStream和OutputStream,这两个类在使用完后必须回收防止资源泄露,避免数据丢失,使用 try-catch方法里的try with source用法,把对象放到try()中,使用 完毕会自动回收资源。获得数据后可以使用Read类读取请求,但是Read类读取请求后得到的是byte数组,需要进一步转换成字符串 ,这里使用Scanner去读取可以直接转换成String类型:

    private void possess(Socket clientSocket)  {
        System.out.println("客户端上线");
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            while (true) {
                String request = scanner.next();
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

使用Scanner类读取数据时,读到"空白符"(空白符是一类字符的统称,包括但不限于换行,回车,空格,制表符,翻页符等)才会读取完毕 ,客户端在发送数据时,务必在每个请求的末尾加上空白符。

由于TCP是按照字节来传输的,在实际传输中,应该使若干个字节构成一个“应用层的数据报”,此时就可以通过使用空白符作为“分割符”,来区分不同的数据报。

 将读取的请求交给处理请求的函数,使用的是process函数,这里对请求的处理实际上是直接返回请求(回显服务器):

String request = scanner.next();
String response = possessFun(request);
outputStream.write(response.getBytes());

在读取请求之前,应该判断文件是否有输入 ,可以在进入while循环之后使用if语句判断请求是否有输入,使用scanner的next方法来判断,请求到达后,并且带有明确的分隔符就会返回true,如果TCP断开连接,就会返回false,使用scanner读取到文件末尾或者TCP断开连接就会返回false,否则就会阻塞等待客户端继续发送请求:

Tcp断开连接->阻塞解除返回false。

Tcp没有断开连接->对方们没有发数据过来,阻塞等待。

客户端发送请求 -> 接触阻塞并返回true。

服务器完整代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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) {
            Socket socket = serverSocket.accept();
            possess(socket);
        }
    }
    private void possess(Socket clientSocket)  {
        System.out.println("客户端上线");
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            while (true) {
                if(!scanner.hasNext()) {
                    System.out.println("客户端下线");
                    break;
                }
                String request = scanner.next();
                String response = possessFun(request);
                outputStream.write(response.getBytes());
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
    private String possessFun(String request) {
        return request+" \n";
    }
}

客户端

 客户端在构造方法中应该有服务器的ip地址和端口号,在构造过程中就和服务器建立连接。

    private Socket socket = null;
    public TcpClientSocket(String address,int  port) throws IOException {
        socket = new Socket(address,port);
    }

建立start方法去启动客户端输入请求,同样使用try-catch语句实例化字节流对象,使用Scanner在控制台上输入请求,将接收的语句发送给服务器之前使用‘/n’作为分割符 ,发送给服务器使用OutputStream,传输的单位是字节,将请求转换为byte进行发送。

    public void start() {
        System.out.println("'客户端启动");
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(System.in);
            Scanner Rescanner = new Scanner(inputStream);
            while (true) {
                String request = scanner.next();
                request += "/n";
                outputStream.write(request.getBytes());
            }
        }

然后如同服务器一样,判断来自服务器的响应是否到达,使用scanner接收响应,在将接收的响应打印到控制台,客户端就完成编写:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpClientSocket {
    private Socket socket = null;
    public TcpClientSocket(String address,int  port) throws IOException {
        socket = new Socket(address,port);
    }
    public void start() {
        System.out.println("'客户端启动");
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(System.in);
            Scanner Rescanner = new Scanner(inputStream);
            while (true) {
                String request = scanner.next();
                request += "/n";
                outputStream.write(request.getBytes());
                if(!Rescanner.hasNext()) {
                    break;
                }
                String response = Rescanner.next();
                System.out.println(response);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 步骤:

 1.客户端从控制台得到请求。2.将请求发送到服务器3.服务器接收请求4.服务器处理请求5服务器.将响应发送给客户端6.客户端接收响应7.将响应输出在控制台。

优化服务器

1.关闭服务器创建的socket对象

serverrSocket不需要特别关闭,因为生命周期是伴随整个服务器进程。客户端的socket也是如此,但是服务器用于接收客户端信息的socket就必须关闭,服务器会对应多个客户端,如果使用完毕后不关闭当前资源文件得不到释放,就会引起文件资源泄露。在服务器代码中加上finally语句释放socket对象。

2.引入线程池,为多个客户端提供服务 

主线程处理accept,每次接收一个accept就创建一个线程来服务,这里使用可扩容的线程池:

    public void start() throws IOException {
        System.out.println("服务器启动");
        //自动扩容线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
            Socket socket = serverSocket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        possess(socket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

                                                文章到这里就结束了,感谢观看。 

 

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

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

相关文章

基于python的某音乐网站热门歌曲的采集与分析,包括聚类和Lda主题分析

一项目背景 在当前竞争激烈的市场环境下,分析酷狗音乐上的热门歌曲及其用户行为趋势,对平台运营、歌曲推荐和音乐创作具有重要意义。尤其是通过对酷狗音乐平台热门歌曲的数据采集与分析,可以深入理解用户偏好、歌曲流行的规律以及市场需求的…

论文阅读之方法: Single-cell transcriptomics of 20 mouse organs creates a Tabula Muris

The Tabula Muris Consortium., Overall coordination., Logistical coordination. et al. Single-cell transcriptomics of 20 mouse organs creates a Tabula Muris. Nature 562, 367–372 (2018). 论文地址:https://doi.org/10.1038/s41586-018-0590-4 代码地址…

国产多点低压差分(M-LVDS)线路驱动器和接收器,可替代DS91D176/SN65MLVD201

MS2111 是多点低压差分 (M-LVDS) 线路驱动器和接收器。经过优化,可运行在高达 200Mbps 的信号速率下。所有部件均符合 MLVDS 标准 TIA / EIA-899 。该驱动器的输出支持负载低至 30Ω 的多点总线。 MS2111 的接收器属于 Type-2 , 可在 -1V 至 …

【推荐算法】推荐系统的评估

这篇文章是笔者阅读《深度学习推荐系统》第五章推荐系统的评估的学习笔记,在原文的基础上增加了自己的理解以及内容的补充,在未来的日子里会不断完善这篇文章的相关工作。 文章目录 离线评估划分数据集方法客观评价指标P-R曲线ROC/AUCmAPNDCG A/B 测试分…

专题1:AI大模型在汽车行业的应用现状与展望

前言: 本文是《大模型在汽车行业的典型应用实践》100w字长文预告的一篇,本文没有泛泛的去做行业应用现状的介绍,而是通过引入行业调研报告的方式,进行详解,用事事和数据来说话,具有很好的参考意义。 一、…

开源ISP介绍(1)——开源ISP的Vivado框架搭建

开源github链接:bxinquan/zynq_cam_isp_demo: 基于verilog实现了ISP图像处理IP 国内Gitee链接:zynq_cam_isp: 开源ISP项目 基于以上开源链接移植项目到正点原子领航者Zynq7020开发板,并对该项目的Vivddo工程进行架构详解,后续会…

【Java基础面试题009】Java的I/O流是什么?

相关知识补充:黑马-字符集、IO流(一).pdf Autism_Btkrsr/Blog_md_to_pdf - 码云 - 开源中国 (gitee.com) 黑马-IO流(二).pdf Autism_Btkrsr/Blog_md_to_pdf - 码云 - 开源中国 (gitee.com) 回答重点 Java的I/O&…

Python Tkinter 模块

Python 支持多种图形界面的第三方库,包括 TkQtwxWidgets… Python 提供的 Tkinter 模块,就是 Tk GUI 工具包的接口。 Tkinter Tk 是图形库,支持多种操作系统,使用 Tcl 语言开发; Tk 会调用操作系统提供的本地 GUI …

详细介绍下oracle建库过程中核心脚本dbcore.bsq

在我们搭建oracle 11g数据库过程中,当我们设置好安装参数后,最后一步进行数据库安装时,oracle安装进程实际上调用的是$ORACLE_HOME/rdbms/admin/sql.bsq脚本进行建库。今天我们将详细介绍下其中的核心脚本dbcore.bsq。 一、建库脚本 我们先看…

解决`-bash: ./configure:/bin/sh^M:解释器错误: 没有那个文件或目录`的问题

解决`-bash: ./configure:/bin/sh^M:解释器错误: 没有那个文件或目录`的问题 一、错误原因分析二、解决方法方法一:使用`dos2unix`工具方法二:使用`sed`命令方法三:使用`tr`命令方法四:在文本编辑器中转换方法五:在Windows系统中使用适当的工具三、预防措施四、总结在使…

linux安全-firewalld防火墙-基础讲解

目录 一、 防火墙技术分类 二、 firewalld 三、 firewalld支持的类型的NAT 四、 富语言 五、 firewalld配置方式 六、 firewall-cmd命令 七、 小实验 这篇文章将对 firewalld 防火墙的基础知识进行介绍 firewalld简介:firewalld的作用是为包过滤机制提供匹配…

图的概念即存储结构(C++实现图【1】)

目录 1. 图的基本概念 2. 图的存储结构 2.1 邻接矩阵 2.1.1私有成员变量 2.1.2类模板的声明 2.1.3构造函数 2.1.4获取顶点下标 2.1.5添加边的信息 2.1.6打印图 2.1.7测试用例 2.2邻接表 2.2.1私有成员变量 2.2.2Edge类 2.2.3类模板的声明 2.2.4构造函数 2.2.5获取顶点下标 2.2.…

使用Tauri创建桌面应用

当前是在 Windows 环境下 1.准备 系统依赖项 Microsoft C 构建工具WebView2 (Windows10 v1803 以上版本不用下载,已经默认安装了) 下载安装 Rust下载安装 Rust 需要重启终端或者系统 重新打开cmd,键入rustc --version,出现 rust 版本号&…

实验13 使用预训练resnet18实现CIFAR-10分类

1.数据预处理 首先利用函数transforms.Compose定义了一个预处理函数transform,里面定义了两种操作,一个是将图像转换为Tensor,一个是对图像进行标准化。然后利用函数torchvision.datasets.CIFAR10下载数据集,这个函数有四个常见的…

Wwise SoundBanks内存优化

1.更换音频格式为Vorbis 2.停用多余的音频,如Random Container的随机脚步声数量降为2个 3.背景音乐勾选“Stream”。这样就让音频从硬盘流送到Wwise,而不是保存在内存当中,也就节省了内存 4.设置最大发声数Max Voice Instances 5.设置音频…

【测试工具JMeter篇】JMeter性能测试入门级教程(六):JMeter中实现参数化的几种方式

一、参数化的定义 什么是参数化?从字面上去理解的话,就是事先准备好数据(广义上来说,可以是具体的数据值,也可以是数据生成规则),而非在脚本中固化,脚本执行时从准备好的数据中取值。…

2024年11月份实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先,来看下效果图 在线体验地址:https://geojson.hxkj.vip,并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

【力扣】—— 二叉树的前序遍历、字典序最小回文串

Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 🌱🌱个人主页:奋斗的明志 🌱🌱所属专栏:数据结构 📚本系列文章为个人学…

电脑显示没信号显示屏不亮怎么办?电脑没信号解决方法

电脑没信号显示屏不亮这种故障的原因可能有多种,例如显示器的供电、连接、设置等问题,或者电脑的显卡、内存、硬盘、主板等硬件问题。所以我们想要解决这个问题,也是需要多方面排除找到具体原因然后进行修复。下面将为大家介绍一些常见的电脑…

【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问

【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问 文章目录 【docker】Windows11创建Ubuntu-desktop并使用VNC完成远程访问前言创建Ubuntu容器下载镜像运行容器连接容器 搭建容器XFCE桌面环境安装ubuntu桌面 总结 前言 docker ubuntu容器在深度学习领域的使用过程…