当前位置: 首页 > news >正文

网络编程-

文章目录

    • 网络编程套接字
    • UDP/TCP的api使用


网络编程套接字

socket,是操作系统给应用程序(传输层给应用层)提供的api,Java也对这个api进行了封装。

socket提供了两组不同的api,UDP有一套,TCP有一套,本文主要介绍api的使用.

这里介绍TCP和UDP的区别:
在这里插入图片描述
连接:通信双方各自保存对方的信息,就是连接.两个通信实体之间建立的一条可靠的、有序的、双向的通信通道。

可靠传输和不可靠传输:可靠传输是发送方可以知道消息有没有被接收方收到

面向字节流:网络中传输的数据的基本单位是字节

面向数据报:每次传输的基本单位是数据报

全双工:一个信道可以双向通信,就像公路一样是双向车道

UDP/TCP的api使用

Java把系统原生的api进行了封装,操作系统中有一类文件叫做scoket文件,它抽象的表示了"网卡"这样的设备,通过操作scoket文件就可以对网卡进行操作.

通过网卡发送数据,就是写scoket文件.
通过网卡接收数据,就是读socket文件.

UDP scoket api的使用

DatagramScoket是UDP scoket,用于接收和发送数据报.

核心的类有两个:
1.DatagramScoket

DatagramScoket是UDP scoket,用于接收和发送数据报.
DatagarmScoket的构造方法

方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(port就是端口号)

DatagramScoket方法

方法名说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭数据报套接字

在这里插入图片描述
这里的DatagramPacket p是作为输出型参数的

小知识:
输出型参数:输出型参数是一个变量,函数会修改它的值,并将修改后的值传递回调用者。调用者可以通过这个参数获取函数处理后的数据。就像是我们自己带饭盒去食堂吃饭,饭盒就相当于DatagramPacket,打饭的阿姨会帮我们把饭盒装满饭菜,此时打饭的阿姨就是void receive.

2.DatagramPacket

UDP面向数据报,每次发送接收数据的基本单位,就是一个UDP数据报.

构造方法:

方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组buf中,接收指定的长度(length)
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组buf中,接收指定的长度(length),address表示指定的目的主机的ip和端口号

常用方法:

方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号,或者从发送的数据报中,获取接收端主机的端口号
byte[] getData()获取数据报中的数据

利用上述的方法及概念我们实现一个回显服务器程序;

package net;

/*
服务器
 */


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

public class UdpEchoServer {
    private DatagramSocket socket=null;



    //服务器一般都是要自己指定端口号
    public UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }

    public void start() throws IOException {
        //服务器需要不停的处理请求,所以我们这里用while循环
        while (true){
            //1.等待客户端发送请求,并用DatagramPacket保存,如果没有请求,就进入阻塞.
            DatagramPacket requstPacket=new DatagramPacket(new byte[4399],4399);
            socket.receive(requstPacket);

            //2.将收到的请求构造成字符串,便于后边做出响应的处理
            String requst=new String(requstPacket.getData(),0,requstPacket.getLength());

            //3.通过接收的数据,服务器做出响应,process就是具体做出响应的逻辑
            //这里的回显服务器我们直接返回客户端的输入即可,没什么深层次的逻辑
            String response=process(requst);

            //4.将响应后的数据报构造出并且发送
            //requstPacket.getSocketAddress()就是和服务器通信的对端的ip和端口.
            //这里就是把请求中的源ip和源端口,作为响应的目的ip和目的端口
            DatagramPacket resonsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requstPacket.getSocketAddress());
            socket.send(resonsePacket);
        }
    }

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

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

/*
客户端
*/
package net;

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

public class UdpEchoClient {
  private DatagramSocket socket=null;

  //目标服务器的IP和端口
  private String serverIP;
  private int serverPOrt;


  //客户端一般可以使用系统随机分配的端口号
  public UdpEchoClient(String serverIP,int serverPOrt) throws SocketException {
      this.serverIP=serverIP;
      this.serverPOrt=serverPOrt;

      socket=new DatagramSocket();
  }
  public void start() throws IOException {
      Scanner scanner=new Scanner(System.in);
      while (true){
          if (!scanner.hasNext()){
              break;
          }
          //将从控制台读取到的数据存入requst
          String requst=scanner.next();

          //1.将requst构成数据报,并发送给服务器
          DatagramPacket requstPacket=new DatagramPacket(requst.getBytes(),requst.getBytes().length, InetAddress.getByName(serverIP),serverPOrt);
          socket.send(requstPacket);

          //2.等待服务器返回响应,构造数据报进行接收,并进行输出
          DatagramPacket responsePacket=new DatagramPacket(new byte[4399],4399);
          socket.receive(responsePacket);

          String response=new String(responsePacket.getData(),0,responsePacket.getLength());
          System.out.println(response);

      }

  }

    public static void main(String[] args) throws IOException {
        //"127.0.0.1"通用,代表本机端口号
        UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

代码分析:

在这里插入图片描述
在这里插入图片描述

TCP socket api的使用
TCP是面向字节流的,传输的基本单位是字节.

连接建立:从客户端Socket的构造方法发送连接请求,服务器的SerevrSocket监听到请求后并且调用accept()方法,这样就建立了连接,然后accept()方法在服务器中会生成一个新的Socket对象用来进行通信.

api介绍:
ServerSocket

构造方法:

方法签名说明
ServerSocket(int port)创建⼀个服务端流套接字Socket,并绑定到指定端⼝

方法:

方法说明
Socket accept()开始监听指定端⼝(创建时绑定的端⼝),有客⼾端连接后,返回一个Socket对象,并且基于Socket建立与客户端的连接,没有就阻塞等待
void close()关闭套接字

Socket

构造方法:

方法说明
Socket(String host, int port)创建⼀个客⼾端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

方法:

方法说明
InetAddress getInetAddress()返回套接字锁连接的地址
InputStream getInputStream()返回此套接字的输⼊流
OutputStream getOutputStream()返回此套接字的输出流

ServerSocket只能在服务器中使用,而Socket既可以在服务器中使用也可以在客户端使用.

TCP是有连接的,就类似需要客户端拨打电话,服务器来接听.

接下来我们用回显服务器案例来介绍TCP的api的使用以及是如何通信的.

首先我们编写一下服务器程序.

 private ServerSocket requstSocket=null;
    public TcpEchoServer(int port) throws IOException {
        requstSocket=new ServerSocket(port);
    }

通过ServerSocket提供的构造方法,给服务器分配一个端口号(这里我们需要注意,服务器是必须要指定端口号的,而客户端系统自己分配端口号就可以)

服务器的ServerSocket是用来监听请求的,如果有客户端发送请求,那么ServerSocket就会感知到

服务器:

 public void start() throws IOException {

        /*
            当ServerSocket监听到有客户端发来请求后,会调用accept()方法
            一旦连接建立,accept()方法就会返回一个新的Socket对象,这个Socket就是专门用来与该客户端进行通信的
         */
        Socket clientSocket=requstSocket.accept();

        //把处理请求和返回相应的逻辑这里我们放到这个方法中来实现
        processConntion(clientSocket);

    }

当ServerSocket监测到有客户端发来连接的请求,ServerSocket会调用accept()方法,accept()方法会返回一个新的Socket对象,这就算是建立了连接而这个新的对象就是用来与客户端通信

上述过程就像是客户端的Socket想要通过服务器的ServerSocket认识服务器中的Socket.于是客户端的Socket就请求服务器的ServerSocket帮忙牵个线,搭个桥.服务器的ServerSocket就把服务器的Socket的电话号码给了客户端,而客户端的构造方法就类似于给服务器拨通了电话,而当前只是在响铃,而accept()方法就类似接听,只有调用accept()的方法后才算真正建立连接

在这里插入图片描述

private void processConntion(Socket clientSocket) {
        try(InputStream inputStream=clientSocket.getInputStream();
        OutputStream outputStream=clientSocket.getOutputStream()){
            Scanner scanner=new Scanner(inputStream);
            while (true){
                if (!scanner.hasNext()){
                    break;
                }
                String requst=scanner.next();
                String response=process(requst);

                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
            }



        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String process(String requst) {

        return requst;
    }
try(InputStream inputStream=clientSocket.getInputStream();
        OutputStream outputStream=clientSocket.getOutputStream())

InputStream inputStream=clientSocket.getInputStream();就是从网卡内读数据

OutputStream outputStream=clientSocket.getOutputStream()就是往网卡内写数据

TCP中操作socket文件,对其进行读写(InputStream,OutputStream),就是在操作网卡,操作系统把网卡抽象成了一个文件.就像看电视,我们只需要操作遥控器即可,而不需要直接去电视脸上操作

客户端


    public TcpEchoClient(String serverIP,int serverPort) throws IOException {
        socket=new Socket(serverIP,serverPort);
    }

执行上述代码就会和对应的服务器进行tcp的连接建立流程(在系统内核中)
内核走完流程,服务器这边就会从accept()返回,于是就可以通信了.

public void start() throws IOException {
        try (InputStream inputStream=socket.getInputStream();
             OutputStream outputStream=socket.getOutputStream()){
            Scanner scannerConsle=new Scanner(System.in);
            Scanner scannernetWork=new Scanner(inputStream);
            while (true){
                if (!scannerConsle.hasNext()){
                    break;
                }
                String requst=scannerConsle.next();

                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(requst);
                printWriter.flush();

                String response=scannernetWork.next();
                System.out.println(response);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            socket.close();
        }
    }
Scanner scannerConsle=new Scanner(System.in);
 Scanner scannernetWork=new Scanner(inputStream);

在客户端上,scannerConsle是在控制台中读取数据,也就是我们用户输入的时候读取数据,并且转变为String 发送给服务器(就是通过OutputStream写入操作网卡的文件).

而scannernetWork就是在服务器做出响应后,通过inputStream读取网卡上的数据 最终打印出结果.

PrintWriter

PrintWriter 是 Java 中的一个类,位于 java.io 包中,用于以文本形式写入输出数据。它继承了 Writer 抽象类,提供了多种方法来方便地写入字符和字符串到文件或其他输出流中.

printWriter.flush();

这个方法的作用是刷新缓冲区.因为IO都是比较低效的操作,一次一次读写,太麻烦.缓冲区就将先把数据放到内存缓冲区中,等攒够了数据一起发送,这样就变得高效了,而printWriter.flush();就是将缓存区刷新,将数据一点一点发送出去,不用等到满了一股脑发出去.

在客户端运行完毕之后我们要cloose()

  socket.close();

在这里插入图片描述

TCP服务器客户端执行流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上述客户端代码还存在一个问题:
如果有多个客户端发送连接请求,我们就会发现第一个客户端连接上服务器之后,服务器会从accept这里返回,进入具体处理请求的processConntion方法,接下来在具体处理请求方法的内部scanner.hasNext()处阻塞,等待客户端发送请求,此时如果有客户端发送请求,hasNextt继续向下执行,构造String,计算响应.返回响应给客户端.执行完上述操作后继续循环,等待客户端发送请求.

上面这种情况,如果是很多个客户端一起执行,即使其他客户端在内核已经建立了连接,但是由于服务器一直在processConntion方法中为第一个发送请求的客户端服务,一直在processConntion方法循环处理请求,所以其他的客户端无法执行到accept()方法,只是在内核建立连接,没有调用accept方法就不能算真正意义上的连接.其他的客户端也就无法执行.

针对上述问题我们如何改进?
就要用到我们之前学到的多线程,一个线程处理连接,另一个线程处理请求返回响应的逻辑.
但是如果有很多客户端,每次发送请求的时候都要创建一个新的线程,处理完成后销毁,对于系统资源的开销是比较大的,所以我们这里使用线程池.

完整代码展示:

/*
服务器
*/
package net;

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 TcpEchoServer {
    private ServerSocket socket=null;

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

    public void start() throws IOException {
        ExecutorService pool= Executors.newCachedThreadPool();
      while (true){
          Socket cilentSocket =socket.accept();

          pool.submit(new Runnable() {
              @Override
              public void run() {
                  processConnetion(cilentSocket);
              }
          });
      }
    }

    private void processConnetion(Socket cilentSocket) {
        try (InputStream inputStream=cilentSocket.getInputStream();
             OutputStream outputStream=cilentSocket.getOutputStream()){
            Scanner scanner=new Scanner(inputStream);
            while (true){
                if (!scanner.hasNext()){
                    break;
                }
                String requst=scanner.next();
                String response=process(requst);
                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
            }



        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

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

/*
客户端
*/
package net;

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 {
        socket=new Socket(serverIP,serverPort);
    }


    public void start() throws IOException {
        try (InputStream inputStream=socket.getInputStream();
             OutputStream outputStream=socket.getOutputStream()){
            Scanner scannerCon=new Scanner(System.in);
            Scanner scannerNet=new Scanner(inputStream);

            while (true){
                if (!scannerCon.hasNext()){
                    break;
                }

                String requst=scannerCon.next();
                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(requst);
                printWriter.flush();

                String response=scannerNet.next();
                System.out.println(response);

            }



        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            socket.close();
        }
    }

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

}

http://www.dtcms.com/a/20312.html

相关文章:

  • 设计模式Python版 命令模式(下)
  • Keysight E5071C (Agilent) 网络分析仪的特性和规格
  • DeepSeek 本地部署(电脑安装)
  • 笔试题笔记#6 模拟三道题和总结知识
  • CTF-web:java-h2 堆叠注入rce -- N1ctf Junior EasyDB
  • 消息中间件深度剖析:以 RabbitMQ 和 Kafka 为核心
  • vue2和vue3响应式区别最通俗易懂的理解
  • 图文教程 | 2024年IDEA安装使用教程,JDK简易下载方法
  • SpringBoot 统一功能处理
  • 面试经典150题——分治
  • SkyWalking 10.1.0 实战:从零构建全链路监控,解锁微服务性能优化新境界
  • element-ui时间组件同一个月内选择/30天内选择
  • AI 学习入门之概述篇
  • KEPServerEX 的接口类型与连接方式的详细说明
  • 基于和声搜索(Harmony Search, HS)的多中心点选址优化算法matlab仿真
  • Flutter_学习记录_动画的简单了解
  • 【华为OD机考】华为OD笔试真题解析(7)--基站维修工程师
  • 【Qt】实现定期清理程序日志
  • 排序算法详解、应用对比与C语言实现
  • 【AI学习】DeepSeek-R1-Distill的意义和影响
  • 【TI C2000】F28002x的系统延时、GPIO配置及SCI(UART)串口发送、接收
  • Git命令摘录
  • C++ -- stack的模拟实现 介绍适配器模式
  • Zookeeper(45) 如何在Zookeeper中删除节点?
  • CentOS上安装WordPress
  • 在SpringBoot如何调用DeepSeek接口
  • 【Qt】模型/视图(Model/View)框架详解(一):基本概念
  • 【Axure教程】数字滚动效果
  • 深入解析LVS命令参数及DR模式下的ARP抑制原理
  • rustdesk远程桌面自建服务器