Java网络编程(Socket、ServerSocket、DatagramPackage、DatagramSocket)

简介: 1.网络编程通信模型2.Echo模型(服务器与客户端实现通信),3.BiO处理模型(实现多用户访问同个服务器),4.UDP程序

1.网络编程

有两种通信模型

C/S(Client/Server)基于客户端和服务器端,实现代码时候需要实现客户端与服务器端

B/S(Browser/Server)基于Http网页和服务器端,实现时候只需要实现服务器端即可

2.Echo模型(服务器与客户端实现通信)

所谓Echo模型就是指的是客户端发送消息到服务器端,服务器接收到消息然后将客户端接收到的信息进行回送到客户端。所以需要编写两个端口一个客户端,一个服务器端。java中可以使用Socket类实现用户端(客户端)与ServerSocket类(服务器端)

Socket与ServerSocket都是基于TCP(有连接的,可靠的传输服务)

Socket类(客户端)的常用方法:

方法 描述
public Socket(String host, int port) 创建一个套接字,连接到指定主机名和端口号
public OutputStream getOutputStream() 返回此套接字的输出流,用于向服务器发送数据,一般使用PrintStream
public InputStream getInputStream() 返回此套接字的输入流,用于从服务器接收数据

ServerSocket类(服务器端)的常用方法:

方法 描述
ServerSocket(int port) 创建一个服务器套接字,绑定到指定的端口号
Socket accept() 监听并接受客户端的连接请求,返回一个与客户端通信的新的 Socket 对象
void close() 关闭服务器套接字,停止监听客户端连接请求

Echo模型实现案例代码:

1.服务器端代码实现:

package Example1903;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;
//服务器端
public class javaDemo {
    public static void main(String[] args) throws  Exception{
        Date date = new Date();
        ServerSocket server = new ServerSocket(9999);
        System.out.println("等待客户端运行--------");
        Socket client = server.accept();
//        设置服务器的输入输出流保证能接收信息与输入信息
        Scanner scanner = new Scanner(client.getInputStream());
        PrintStream out = new PrintStream(client.getOutputStream());
//        接收信息并且将信息传入out输出流
        boolean flag = true;
        scanner.useDelimiter("\n");
        while (flag){
            if (scanner.hasNext()){
                String value = scanner.next().trim();
                if ("byebye".equalsIgnoreCase(value)){
                    out.println("ByeBye");
                    flag = false;
                }else out.println(date+ "[服务器]发送消息:"+value);
            }
        }
//        关闭所有服务
        server.close();
        client.close();
        scanner.close();
        out.close();
    }
}

2.客户端代码实现:

image.gif

package Example1904;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;
//客户端
public class javaDemo {
//    通过键盘输入信息
    private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));
    public  static String  getString(String promp) throws  Exception{
        System.out.print(promp);
        String str = KEYBOARD_INPUT.readLine();
        return  str;
    }
    public static void main(String[] args)throws Exception {
        Date date = new Date();
        Socket client = new Socket("localhost",9999);
//        获取同样的输入输出对象
        Scanner scanner = new Scanner(client.getInputStream());
        PrintStream out = new PrintStream(client.getOutputStream());
        scanner.useDelimiter("\n");//使用分隔符
        boolean flag = true;
        while (flag){
            String input = getString( date+ "[客户端]请输入要发送的信息:").trim();
            out.println(input);//将input内容放入PrintStream流中
            if (scanner.hasNext()){
                System.out.println(scanner.next());
            }
            if ("byebye".equalsIgnoreCase(input)){
                flag = false;
            }
        }
    }
}

服务器端运行结果

image.gif 客户端运行结果:

image.gif代码流程:

  1. 服务器端启动并等待连接:服务器启动后,通过 ServerSocket 监听指定端口(此处为9999),并调用 accept 方法等待客户端连接。一旦有客户端连接成功,服务器会创建一个新的线程来处理与该客户端的通信。
  2. 客户端连接服务器:客户端通过 Socket 构造函数连接到指定的服务器地址(此处为 localhost)和端口号(此处为 9999)。
  3. 客户端发送消息:客户端通过 getString 方法获取用户输入的消息,并将消息通过 PrintStreamprintln 方法发送给服务器。
  4. 服务器接收消息:服务器端在自己的线程中,通过 Scannernext 方法读取客户端发送的消息。
  5. 服务器处理消息:服务器端根据接收到的消息进行相应的处理,可以是对消息进行解析、判断、计算等操作。在这个示例中,服务器端简单地将接收到的消息原样发送回客户端。
  6. 服务器返回消息:服务器通过 PrintStreamprintln 方法将处理后的消息发送给客户端。
  7. 客户端接收消息:客户端通过 Scannernext 方法读取服务器返回的消息,并将其打印到控制台。
  8. 重复步骤3-7,直到客户端发送"byebye",表示结束通信。

答疑:

问1:两个代码是如何实现通信的 ?

首先两个进程之间的通信一定需要接收好对应的端口(计算机网络应用层知识)才能进行通信,然后两个代码共用了输入输出流(可以理解为两个人用了同一条电话线),客户端通过其输出流out将信息输出到服务器端,对于服务器外部输入就是输入流,即输出流变成了服务器的输入流,服务器通过scanner输入流接收客户端输出流的信息,同理服务器也通过其输出流输出到客户端,客户端接收到信息就输出

问2:scanner.useDelimiter("\n");useDelimiter方法知道是设置分隔符,但是搭配"\n"是有什么作用吗?

scanner.useDelimiter("\n"); 设置了扫描器的分隔符为"\n",即换行符。这样做的作用是让扫描器以每一行作为一个输入元素,而不是以空白字符(默认情况下)作为分隔符。在这个示例中,服务器和客户端之间通过换行符来分隔消息的发送和接收。

问3:为什么要用到PrintStream正常的输出System.out.println代替不可以吗?

可以使用System.out.println 来替代PrintStream,它们都用于向控制台输出内容。示例代码中使用 PrintStream 是为了将客户端发送给服务器的消息发送出去,以及将服务器返回的消息打印到客户端的控制台。

问4:服务器端用Scanner(socket.InputStream)是为了获取客户端数据这个可以理解,但是客户端为什么也是用Scanner(socket.InputStream)也是获取客户端?

在客户端和服务器端都使用 Scanner(socket.getInputStream()) 是因为它们需要从对应的套接字(socket)的输入流中读取数据。客户端需要读取服务器发送过来的消息,而服务器需要读取客户端发送过来的消息。

问5:为什么要有private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));代码,不能直接通过Scanner(System.in)替代吗?

private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in)); 这段代码定义了一个静态的、只读的 BufferedReader 类型的变量,通过 System.in 将键盘输入流与该变量关联起来。这样做的目的是为了在客户端使用 KEYBOARD_INPUT.readLine() 来获取用户输入的字符串。由于 Scanner(System.in) 是使用缓冲区扫描输入,并不能满足逐行读取的需求,因此需要使用 BufferedReader 来实现。


3.BIO处理模型(实现多用户访问同个服务器)

由于上一个模型里面一段时间只能有一个用户与服务器进行交互,并且一旦用户输入byebye服务器也就随之关闭,但是如果我想要有多个用户去与服务器交互那么该如何实现呢?所以引入了BIO处理模型。

image.gif如图结构可以知道,其解决方法是在ServeSocket内部创建多个Socket实例,并且将每一个Socket实例都封装在一个线程内,实现多线程通信,当有用户连接时候就创建一个独立的通信线程,每个用户可以独立关闭自己的线程

所以只需要修改服务器端的代码

服务器端:

package Example1907;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
class EchoServer implements Runnable {
//    初始化
    Socket client = null;
    Scanner scanner = null;
    PrintStream out = null;
    boolean flag = true;
//    传入通信对象实现创建输入输出流与确认客户端对象
    public EchoServer(Socket client){
        try {
            this.client = client;
            this.scanner = new Scanner(client.getInputStream());
            this.out = new PrintStream(client.getOutputStream());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
//    实现通信
    @Override
    public void run() {
        while (this.flag){
            String value = scanner.nextLine().trim();
            if (value.equalsIgnoreCase("byebye")){
                this.flag = false;
                out.println("Bye Bye~");
            }else {
                out.println("[服务器端]:"+value);
            }
        }
//        实现完通信则关闭所有服务
        try {
            scanner.close();
            out.close();
            client.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//服务器端
public class javaDemo  {
    public static void main(String[] args) throws  Exception {
        ServerSocket server = new ServerSocket(9999);
        boolean flag = true;
        System.out.println("等待客户端连接---------");
//        通过多线程接收多个客户端
        while (flag){
            Socket client = server.accept();
            new Thread(new EchoServer(client)).start();
        }
    }
}

image.gif客户端:还是一样的(这里可以锻炼自己重写一遍代码)

package Example1908;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//客户端
public class javaDemo {
    private  static  final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));
    public static String getString(String promp)throws  Exception{
        System.out.print(promp);
        String input = KEYBOARD_INPUT.readLine().trim();
        return input;
    }
    public static void main(String[] args) throws Exception{
        Socket client = new Socket("localhost",9999);
        Scanner scanner = new Scanner(client.getInputStream());
        PrintStream out = new PrintStream(client.getOutputStream());
        scanner.useDelimiter("\n");
        boolean flag = true;
        while (flag){
            String msg = getString("[客户端]请输入你想要发送的消息").trim();
            out.println(msg);
            if (scanner.hasNext()){
                System.out.println(scanner.next().trim());
            }
            if ("byebye".equalsIgnoreCase(msg)){
                flag = false;
            }
        }
        scanner.close();
        out.close();
        client.close();
    }
}

image.gif效果展示:

服务器端:

image.gif以下javaDemo2是用户1,javaDemo4是用户2

客户端1:

image.gif客户端2:

image.gif这种模型是BIO模式,但是可以发现这里没有对线程数量的限制,也就意味着一旦用户数量急剧增加的时候就会出现性能大幅下降的情况所以需要追加对线程的限制, 也就是真正的BIO(Blocking Io 阻塞Io的模式)

4.UDP程序

UDP传输与Tcp不同的是其面向的是无连接的,不可靠的通信,如果Tcp是打电话,必须要知道对方号码建立连接才能进行通信,那么UDP就是在大街上随便喊一嗓子,有些人就听得到你说的话,也有些人离你太远听不到,但是你并不在乎。

这种传输方式常用于游戏之中,所谓的丢包就是因为udp并不可靠出现的问题,但是这种传输方式非常快延迟低。

UDP常用类有

DatagramPackage的常用方法:

方法名 描述
public DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket 对象,使用指定的字节数组作为数据,指定的长度作为有效数据的长度。
public byte[] getData() 获取该 DatagramPacket 对象中的数据字节数组。
public int getLength() 获取该 DatagramPacket 对象中数据的长度。

DatagramSocket的常用方法:

方法名
描述
DatagramSocket() 创建一个未绑定的数据报套接字。
DatagramSocket(int port) 创建一个绑定到指定端口的数据报套接字。
DatagramSocket(int port, InetAddress address) 创建一个绑定到指定端口和指定本地 IP 地址的数据报套接字。
void send(DatagramPacket packet) 发送指定的数据包到目的地。
void receive(DatagramPacket packet) 接收一个数据包,并将其存储在指定的数据包对象中

以下案例实现一个客户端通过udp发送报文到服务器并输出服务器返回值

在上一个服务器开着的前提下:

package Example1910;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
public class javaDemo {
    public static void main(String[] args)throws Exception {
//        通过键盘输入数据
        System.out.println("请输入你想要发送的数据");
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        String data = input.readLine();
//      发送数据包
        DatagramSocket client = new DatagramSocket(9999);
        DatagramPacket packet = new DatagramPacket(data.getBytes(StandardCharsets.UTF_8),0,data.length(), InetAddress.getByName("localhost"),9999);
        client.send(packet);
//        接收数据包
        byte redata[] = new byte[1024];
        DatagramPacket repacket = new DatagramPacket(redata,0,redata.length);
        client.receive(repacket);
        System.out.println("接收的数据是"+new String(redata,0,redata.length,StandardCharsets.UTF_8));
        client.close();
    }
}

image.gifimage.gif编辑

目录
相关文章
|
27天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
49 9
|
27天前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
43 7
|
27天前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
47 11
|
27天前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
|
27天前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
51 9
|
5天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的知识,并提供一些实用的技巧和建议,帮助读者更好地保护自己的网络安全和信息安全。
|
4天前
|
安全 算法 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在当今数字化时代,网络安全和信息安全已经成为了全球关注的焦点。随着技术的发展,网络攻击手段日益狡猾,而防范措施也必须不断更新以应对新的挑战。本文将深入探讨网络安全的常见漏洞,介绍加密技术的基本概念和应用,并强调培养良好安全意识的重要性。通过这些知识的分享,旨在提升公众对网络安全的认识,共同构建更加安全的网络环境。
|
3天前
|
存储 安全 网络安全
云计算与网络安全:探索云服务、网络安全和信息安全的交汇点
在数字化时代,云计算已成为企业和个人存储、处理数据的关键技术。然而,随着云服务的普及,网络安全问题也日益凸显。本文将深入探讨云计算与网络安全的关系,分析云服务中的安全挑战,并提出相应的解决方案。同时,我们还将介绍一些实用的代码示例,帮助读者更好地理解和应对网络安全问题。
|
6天前
|
安全 算法 网络协议
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字时代,网络安全和信息安全已经成为了我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,帮助读者更好地了解网络安全的重要性和应对措施。通过阅读本文,您将了解到网络安全的基本概念、常见的网络安全漏洞、加密技术的原理和应用以及如何提高个人和组织的网络安全意识。
|
3天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已经成为了我们生活中不可或缺的一部分。本文将深入探讨网络安全漏洞、加密技术和安全意识等方面的内容,帮助读者更好地了解网络安全的重要性,并提高自己的网络安全防护意识。通过本文的学习,你将能够了解到网络安全的基本概念、常见的网络安全漏洞、加密技术的应用以及如何提高自己的安全意识。让我们一起来探索这个充满挑战和机遇的领域吧!
下一篇
无影云桌面