关于网络编程

目录

1、InetAdress类

2、Socket套接字

3、UDP数据报套接字编程 

(1)DatagramSocket 类

 (2)DatagramPacket类

 (3)处理无连接问题

UdpEchoServer.java

UdpEchoClient.java

4、TCP流套接字编程

(1)工作流程

Server.java

Client.java 

(2)改进

Server.java

Client.java

 运行结果

(3)相关解析

socket.close()

scanner.next()的工作原理

hasNext()工作原理

Ideal同时运行多个进程设置

 服务器同时处理数个客户端请求


指网络上的主机,通过不同的进程以编程的方式实现网络通信(网络数据传输)

1、InetAdress类

Java语言提供了InetAdress类,该类用于处理IP地址主机名称(hostname)的获取

这个类并没有提供构造函数,必须利用该类的静态方法来创建对象

static InetAdressgetByName(String host)
static InetAdress[ ]getAllByName(String host)
static InetAdressgetByAdress(byte[ ] addr)
static InetAdressgetByAdress(String host , byte[ ] addr)
static InetAdressgetLocalHost()返回本地主机的地址
static InetAdressgetLoopbackAdresst()返回回送地址

另外还有些非静态方法用于获取InetAdress对象的信息:

返回值方法名返回数据
byte[ ]getAdress()原始IP地址
String getHostAdress()IP地址字符串
String getHostName()主机名
String getCanonicalHostName()获取此IP地址的完全限定域名

文章链接分享:主机与域名

2、Socket套接字

是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元

基于Socket套接字的网络程序开发就是网络编程

针对传输层协议可以将网络编程分为两类:

(1)流套接字:TCP(传输控制协议)网络通信

        有连接、可靠、面向字节流、有接收/发送缓冲区的、大小不限、全双工

        (适用于需要可靠数据传输的场景,如HTTP、FTP等协议的通信)

(2)数据报套接字:UDP(用户数据报协议)网络通信

        无连接、不可靠、面向数据报、全双工

        (适用于不需要可靠传输,但对实时性有要求的场景,如视频会议、在线游戏等)

有无连接:

通信双方不需要建立连接,可以直接发送数据报

而这里使用Socket进行通信时,双方必须先建立一个连接才能进行数据的发送和接收

面向字节流:即传输数据基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次发送

面向数据报:即传输数据是一块一块的,发送一块数据加入100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节

3、UDP数据报套接字编程 

这里要用到2个类:DatagramSocket类、DatagramPacket类

(1)DatagramSocket 类

        此类表示用于发送和接收数据报数据包的套接字

相关方法:

void receive(DatagramPacket p)从此套接字接收数据报(阻塞直到真正接收到数据报)
void send(DatagramPacket p)从此套接字发送数据报
void close()
 (2)DatagramPacket类

上述DatagramSocket 类中我们发现往这个Socket通道中发送接收都是以一个UDP数据报为单位进行的

但是这个数据报DatagramPacket内部是不能自行分配内存空间,需要手动创建空间,需要通过构造方法往里面放一个字节数组

 (3)处理无连接问题

我们知道UDP是无连接的,意味着服务器与客户端双方并不存储对方的地址与端口号,但是若双方都不知道对方在哪儿,这就传输通信不了了呀!

        银行(服务端)是不需要知道取钱的人(客户端)的地址的,但客户端要知道银行的地址才能找上门请求服务的,所以客户端这边是需要记下指定服务端的地址的。

        但若你还请求了办理证件这种服务,是需要后期邮寄上门的,那么你在请求服务时就会把地址告诉银行。

按照这样的逻辑我们来看下在UDP这里是如何处理无连接问题的

既然UDP自身无法记住,那就由我们另外记录下来

客户端把服务端的地址、端口设置为成员变量,在构造方法中进行赋值,这样就记录了服务端的地址与端口,那么在下面一些列请求中就可以使用了(相当于记下了) 

public UdpEchoClient(String ip,int port) throws SocketException {
        socket =new DatagramSocket();
        serverIp=ip;
        serverPort=port;
}

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

而服务端只需要给自己指定端口号即可(给自己定个固定地址,让客户端找上门服务)

 private DatagramSocket socket=null;
    public UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }

我们可以发现服务端的端口号是指定的固定的,但是客户端的Socket方法却是让系统自动随机分配一个空闲端口。

原因很简单,试想一下,若同时有数个相同地址相同端口的客户端向同一个服务端发起请求,相同个端口无法区分,那么服务端处理谁呢?所以由系统自动随机分配一个空闲端口可以避免端口冲突!

UdpEchoServer.java

下面我们来写一个简单的回显服务器:(收到什么就返回什么)

public class UdpEchoServer {
   
    private DatagramSocket socket=null;

    public UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }
    
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true){
            //1、创建数据报
            DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
            //2、接收并解析数据报
            socket.receive(requestPacket);
            String request=new String(requestPacket.getData(),0,requestPacket.getLength());
            //3、计算响应并返回给客户端
            String response=process(request);
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //4、打印日志记录此次数据交互的详情
            System.out.printf("[%s:%d] req=%s resp=%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }

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

    public static void main(String[] args) throws IOException {
        UdpEchoServer server=new UdpEchoServer(9090);
        server.start();
    }
}
UdpEchoClient.java
public class UdpEchoClient {
    private DatagramSocket socket=null;
    private String serverIp="";
    private int serverPort=0;

    public UdpEchoClient(String ip,int port) throws SocketException {
        socket =new DatagramSocket();
        serverIp=ip;
        serverPort=port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner=new Scanner(System.in);
        while (true){
            //1、从控制台读取数据作为请求
            System.out.println("请输入要发送的数据");
            String request=scanner.next();
            //2、发送请求数据报
            DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            //3、接收响应数据报
            DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response=new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

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

上述只是一个简单的回显服务,若你还想要一个处理复杂点的服务的服务端可以直接继承回显服务端再重写相关方法。比如下面是一个翻译服务:

public class UdpDictServer extends UdpEchoServer{

    private Map<String,String> dict=new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);
        //此处可以往这个表里插入几千几万个这样的英文单词
        dict.put("dog","小狗");
        dict.put("cat","小猫");
        dict.put("pig","小猪");
    }

    //重写process方法,在重写的方法中完成翻译的过程(翻译的本质就是”查表“)
    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"该词在词典中不存在!");
    }

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

4、TCP流套接字编程

用到两个关键的类:

  • SeverSocket类:用于创建服务器绑定端口
  • Socket类:该类实现客户端套接字,套接字是两台机器之间通讯的端点
(1)工作流程

UDP中是各自有一个DatagramSocket类,客户端手动存储服务端的地址与端口,通过数据报发送请求到服务端,服务端就可以接收数据报中的请求、客户端的地址端口,这样就又可以通过数据报把响应返回给客户端,客户端就获得响应,完成网络通信。

这个TCP可就牛杯了。

服务端通过ServerSocket创建服务器绑定端口,等待接收客户端在内核已建立好的连接对象。

客户端Socket s=new Socket(String serverIP,serverPort)就可以与服务端建立连接,服务端这边就能直接获得连接对象Socket对象,就可以直接对Socket对象进行一系列操作(读取请求、写入响应)。

也就是说这下服务端和客户端都是直接对同一个Socket对象进行操作!双方都用这一个Socket对象获取字节输入输出流,这样我都可以直接对同一个socket进行读写了!

    Socket是操作系统中的一个概念,本质上是一种特殊的文件,Socket就属于是把“网卡”这个设备给抽象成文件了。往Socket中写数据,就相当于通过网卡发送数据;从socket文件读数据就相当于通过网卡接收数据。

按照流程来一一梳理下连接完之后双方交互的过程

  1. 客户端 out.write(request)写入请求;
  2. 服务端 in.read()读取请求,服务端 out.write()写入响应;
  3. 客户端 in.read()读取响应

根据这样的流程先简单模拟下:

Server.java
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=new ServerSocket(9088);
        System.out.println("服务端上线");

        while (true){
           
            Socket socket = serverSocket.accept();
            System.out.println("客户端连接成功");
            try(InputStream in = socket.getInputStream();OutputStream out=socket.getOutputStream()) {
                byte buf[]=new byte[1024];
                int n = in.read(buf);
                String request=new String(buf,0,n);

                String response="hello";
                out.write(response.getBytes());
            }finally {
                socket.close();
                System.out.println("客户端下线");
            }
        }
    }
}
Client.java 
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket("127.0.0.1",9088);

        try(InputStream in = socket.getInputStream(); OutputStream out=socket.getOutputStream()){
            String request="hello";
            out.write(request.getBytes());

            byte buf[]=new byte[1024];
            int n = in.read(buf);
            String response=new String(buf,0,n);
            System.out.println (new String(buf,0,n));
        }
    }
}

客户端连续连接两次运行结果如下:

(2)改进
Server.java
public class Server {
    ServerSocket server=null;

    public Server(int port) throws IOException {
        server=new ServerSocket(port);
        System.out.println("服务端上线");
    }

    public void start() throws IOException {
        while (true){
            Socket socket=server.accept();
            System.out.printf("[%s:%d] 客户端上线! \n",socket.getInetAddress(),socket.getPort());
            Thread t=new Thread(()->{
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

    private void processConnection(Socket socket) throws IOException {
        try(InputStream in=socket.getInputStream();OutputStream out=socket.getOutputStream()){
            Scanner sc=new Scanner(in);
            PrintWriter pw=new PrintWriter(out);
            while (true){
                if (!sc.hasNext()){
                    //若没有内容则进入阻塞等待输入内容;若有内容则继续循环处理请求
                    //若客户端断开连接(下线),sc就知道不会再有输入内容,就进入if()跳出循环,请求处理完毕,此线程结束
                    System.out.printf("[%s:%d] 客户端下线! \n",socket.getInetAddress(),socket.getPort());
                    break;
                }
                String request=sc.next();

                String response=process(request);
                pw.println(response);//服务端写入时末尾加了“\n”,作为客户端读取结束标志
                pw.flush();
                System.out.printf("[%s,%d] req=%s resp=%s \n",socket.getInetAddress(),socket.getPort(),request,response);
            }
        }finally {
            socket.close();
        }
    }

    private String process(String request){
        return request;
    }
    public static void main(String[] args) throws IOException {
        Server server=new Server(9789);
        server.start();
    }
}
Client.java
public class Client {
    Socket socket=null;
    public Client(String serverIp,int serverPort) throws IOException {
        socket=new Socket(serverIp,serverPort);
    }

    public void start() throws IOException {
        Scanner scanner=new Scanner(System.in);
        try(InputStream in = socket.getInputStream(); OutputStream out=socket.getOutputStream()){
            Scanner sc=new Scanner(in);
            PrintWriter pw=new PrintWriter(out);

            while (true){
                System.out.println("请输入请求->");
                String request= scanner.next();
                pw.println(request);//客户端写入时末尾加了“\n”,作为服务端读取结束标志
                pw.flush();

                String response=sc.next();
                System.out.printf("返回响应:");
                System.out.println(response);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Client client=new Client("127.0.0.1",9789);
        client.start();
    }
}
 运行结果

(3)相关解析
socket.close()

前面写过的DatagramSocket、ServerSocket都没有close(),因为这两个在整个程序中都只有一个对象,贯穿整个程序,一旦程序结束这两个对象都会自动被销毁,不存在资源泄漏的问题

但是这里服务端接收到的Socket对象则是在循环中每有一个新的客户来建立连接都会创建一个的

并且这个对象最多时用到该客户退出(断开连接)。此时若有很多个客户都来建立连接就意味着会创建很多个Socket对象,当连接断开此时这个对象就会占据着文件描述表的位置。

而在上面的  

try(InputStream in = socket.getInputStream();OutputStream out=socket.getOutputStream())

只是关闭了Socket对象上自带的流对象,而并没有关闭Socket对象本身

scanner.next()的工作原理

用于读取输入,直到遇到空格、Tab键或Enter键等分隔符为止,返回的是内容是连续的有效字符序列,而不包含任何前导或尾随的空白字符

scanner.next()方法提供了一个简单的方式来读取和处理以空白字符分隔的字符串输入 

        比如输入"abc xyz",则会读取并返回"abc",接着若再次调用next()它将返回"xyz"

        在这个过程中,输入的空格被next()视为分隔符,从而确保了每次调用next()时都会返回一段完整的数据

 

hasNext()工作原理

        用来判断缓冲区内是否还有内容,若还有内容,则返回true;若没有内容,不会返回false,而是堵塞当前程序,并且等待输入内容 

Ideal同时运行多个进程设置

 再点击右上角三角形运行图标就可以多个进程运行同一个代码了

  

 服务器同时处理数个客户端请求

上面的代码我们给出的解决方案是每来一个客户端就新创建一个线程处理请求,这样的解决办法在数个客户端请求的情况下,会频繁地来进行建立、断开连接,会导致服务器频繁地创建、销毁线程,这样的开销是巨大的!

为了进一步优化,我们可以使用线程池、协程、或者I/O多路复用等手段。

由于作者只会线程池,所以这里就写下线程池:

 public void start() throws IOException {
        ExecutorService service= Executors.newCachedThreadPool();
        while (true){
            Socket socket=server.accept();
            System.out.printf("[%s:%d] 客户端上线! \n",socket.getInetAddress(),socket.getPort());
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(socket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

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

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

相关文章

设计模式23——状态模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 状态模式&#xff08;State&am…

Mysql基础教程(12):JOIN

MySQL JOIN 在 MySQL 中&#xff0c;JOIN 语句用于将数据库中的两个表或者多个表组合起来。 比如在一个学校系统中&#xff0c;有一个学生信息表和一个学生成绩表。这两个表通过学生 ID 字段关联起来。当我们要查询学生的成绩的时候&#xff0c;就需要连接两个表以查询学生信…

内网渗透-隧道搭建ssp隧道代理工具

内网渗透-隧道搭建&ssp隧道代理工具 目录 内网渗透-隧道搭建&ssp隧道代理工具spp隧道代理工具spp工作原理图cs上线主机spp代理通信服务端配置客户端配置CS配置设置CS生成木马的监听器配置CS监听上线的监听器生成木马 spp隧道搭建服务端配置客户端配置CS配置 内网穿透&a…

hive安装-本地模式

1.安装mysql&#xff08;参考文章&#xff1a;centos7.8安装Mysql8.4-CSDN博客&#xff09; 2.将mysql驱动拷贝到/opt/module/hive/lib目录下 &#xff08;直接windows通过finalShell上传&#xff09; 3./opt/module/hive/conf目录下新建hive-site.xml文件&#xff0c;进行配置…

QT6.2.4 MSVC2019 连接MySql5.7数据库,无驱动问题

1.下载 查询一下数据库驱动 qDebug()<<QSqlDatabase::drivers(); 结果显示&#xff0c;没有QMYSQL的驱动。 QList("QSQLITE", "QMARIADB", "QODBC", "QPSQL") MySql6.2.4驱动下载地址&#xff0c;如果是别的版本&#xff0c;…

2024上海中小学生古诗文大会方案已发布,家长孩子最关心10个问题

昨天&#xff08;2024年5月30日&#xff09;下午15点&#xff0c;上海中小学生古诗文大会组委会通过两个公众号发布了《2024上海中小学生古诗文大会系列活动方案出炉》的推文&#xff08;下称《方案》&#xff09;。如我之前的分析和预测&#xff0c;5月份会发布今年的中小学生…

【EI会议|检索稳定】2024年通讯工程与云计算国际会议(CECC 2024)

2024年通讯工程与云计算国际会议&#xff08;CECC 2024&#xff09; 2024 International Conference on Communication Engineering and Cloud Computing 【重要信息】 大会地点&#xff1a;武汉 大会官网&#xff1a;http://www.iaccecc.com 投稿邮箱&#xff1a;iacceccsub-…

【Nginx】Nginx 日志配置

Nginx 日志配置 Nginx 主要有两种日志类型&#xff1a;访问日志&#xff08;access log&#xff09;和错误日志&#xff08;error log&#xff09;&#xff0c;可以帮助监控和调试服务的运行。 1.访问日志 访问日志主要记录客户端的请求&#xff0c;客户端向 Nginx 发起的每…

JUC常见类

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:Java锁的策略&#x1f649; &#x1f439;今日诗词:苟利国家生死以,岂因祸福避趋之&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小博主&#x1f64…

打造高效微服务的最佳实践

原文: 7 Best Practices for Building Effective Microservices Marc-Olivier Jodoin Unsplash 微服务架构是软件开发领域的热门话题&#xff0c;这一话题如此值得关注是因为这种架构模式几乎解决了单体软件系统的所有重要痛点。快速扩容、减少停机时间、高可用性是微服务的主要…

LIMS实验室管理系统品牌市场占有率 国内LIMS系统推荐

LIMS(Laboratory Information Management System)即实验室信息管理系统&#xff0c;是一种以数据库为核心的信息化技术与实验室管理需求相结合的信息化管理工具。以下是根据搜索结果整理的一些LIMS系统厂商&#xff1a; 国外厂商 Labware 作为国外实验室领域的两大巨头之一&a…

无法删除dll文件

碰到xxxxxx.dll文件无法删除不要慌&#xff01; 通过Tasklist /m dll文件名称 去查看它和哪个系统文件绑定运行&#xff0c;发现是explorer.exe。 我们如果直接通过del命令【当然需要在该dll文件所在的路径中】。发现拒绝访问 我们需要在任务管理器中&#xff0c;将资源管理器…

TLBCache的联合设计

PIPT 在使用虚拟存储器的系统中,仍旧可以使用物理Cache,这是最保守的一种做法 处理器送出的虚拟地址(VA)会首先被TLB转换为对应的物理地址(PA)&#xff0c;然后使用物理地址来寻址Cache,此时就像是没有使用虚拟存储器一样,直接使用了物理Cache,并且使用物理地址的一部分作为 Ta…

民国漫画杂志《时代漫画》第33期.PDF

时代漫画33.PDF: https://url03.ctfile.com/f/1779803-1248635648-d8235b?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

C#中的实体属性详解与示例

文章目录 实体属性的定义实体属性的访问实体属性的示例总结 在C#中&#xff0c;实体属性是面向对象编程的重要组成部分。实体属性允许我们定义对象的特征和行为&#xff0c;并提供了一种方式来访问和管理这些特征。通过实体属性&#xff0c;我们可以封装对象的状态&#xff0c;…

GPT-4o:免费且更快的模型

OpenAI GPT-4o 公告 OpenAI 推出了增强版 GPT-4 模型——OpenAI GPT-4o&#xff0c;用于支持 ChatGPT。首席技术官 Mira Murati 表示&#xff0c;更新后的模型速度更快&#xff0c;并在文本、视觉和音频处理方面有了显著提升。GPT-4o 将免费向所有用户开放&#xff0c;付费用户…

Rainbond 携手 TOPIAM 打造企业级云原生身份管控新体验

TOPIAM 企业数字身份管控平台&#xff0c; 是一个开源的IDaas/IAM平台、用于管理账号、权限、身份认证、应用访问&#xff0c;帮助整合部署在本地或云端的内部办公系统、业务系统及三方 SaaS 系统的所有身份&#xff0c;实现一个账号打通所有应用的服务。 传统企业 IT 采用烟囱…

【数据分享】中国科技统计年鉴Excel版(1991-2023年)

大家好&#xff01;今天我要向大家介绍一份重要的中国科技统计数据资源——《中国科技统计年鉴》。这份年鉴涵盖了从1991年到2023年中国科技统计全面数据&#xff0c;并提供限时免费下载。 数据介绍 在数字化时代的浪潮中&#xff0c;数据的重要性日益凸显。对于研究人员、政…

AI 赋能前端 -- 文本内容概要生成

幸福不在于你获得了什么,而在于你比他人多获得了什么 是比较出来的 大家好,我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder 此篇文章所涉及到的技术有 OpenAILangChainRust/WebAssemblyWeb Workerreact+ts+vite配置环境变量(env)因为,行文字数所限,有些概…

5. MySQL运算符和函数

文章目录 【 1. 算术运算符 】【 2. 逻辑运算符 】2.1 逻辑非 (NOT 或者 !)2.2 逻辑与运算符 (AND 或者 &&)2.3 逻辑或 (OR 或者 ||)2.4 异或运算 (XOR) 【 3. 比较运算符 】3.1 等于 3.2 安全等于运算符 <>3.3 不等于运算符 (<> 或者 !)3.4 小于等于运算符…