【网络编程】从零开始彻底了解网络编程(二)

发布于:2025-04-21 ⋅ 阅读:(12) ⋅ 点赞:(0)

在这里插入图片描述

本篇博客给大家带来的是网络编程的知识点,.
🐎文章专栏: JavaEE初阶
🚀若有问题 评论区见
欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

要开心

要快乐

顺便进步

1. 网络编程的目的和概念

Ⅰ 为什么需要学习网络编程?

网络资源是非常丰富的, 用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。与本地打开视频文件类似,只是视频文件这个资源的来源是网络。
相比本地资源来说,网络提供了更为丰富的网络资源:

在这里插入图片描述
所谓的网络资源,其实就是在网络中可以获取的各种数据资源。
而所有的网络资源,都是通过网络编程来进行数据传输的。

Ⅱ 什么是网络编程?
网络编程指网络上的主机通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输).

在这里插入图片描述

当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程.
特殊地,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程.但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:
进程A:编程来获取网络资源.
进程B:编程来提供网络资源.



2. 网络编程中的基本概念

Ⅰ 发送端和接受端
在一次网络数据传输时:
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。

在这里插入图片描述

Ⅱ 请求和响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:
①:请求数据的发送
②:响应数据的发送。
好比在快餐店点一份炒饭:
先要发起请求:点一份炒饭,再有快餐店提供的对应响应:提供一份炒饭.

在这里插入图片描述

Ⅲ 客户端和服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
客户端:获取服务的一方进程,称为客户端。
对于服务端来说,一般是提供:
① 客户端获取服务资源.
② 客户端保存资源在服务端.

Ⅳ 常见的客户端服务端模型

最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:
① 客户端先发送请求到服务端.
② 服务端根据请求数据,执行相应的业务处理.
③ 服务端返回响应:发送业务处理结果.
④ 客户端根据响应数据,展示处理结果.(展示获取的资源,或提示保存资源的处理结果)

3. Socket套接字

Ⅰ 概念

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

Ⅱ 分类

Socket套接字主要针对传输层协议划分为如下三类:

①流套接字

使用传输层TCP协议TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点(细节后续再说):
有连接
可靠传输
面向字节流
有接收缓冲区,也有发送缓冲区
大小不限

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

② 数据报套接字
使用传输层UDP协议UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点(细节后续再学习):
无连接
不可靠传输
面向数据报
有接收缓冲区,无发送缓冲区
大小受限:一次最多传输64k

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

4. 两种Java通信模型

4.1 Java数据报套接字通信模型

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数据报,一次接收全部的数据报。
java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用DatagramPacket 作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:

在这里插入图片描述

以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。对于⼀个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下:

在这里插入图片描述

4.2 Java流套接字通信模型

在这里插入图片描述

Socket编程注意事项
① 客户端和服务端:开发时,经常是基于一个主机开启两个进程作为客户端和服务端,但真实的场景,一般都是不同主机.
② 注意的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程.
③ Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议,也需要考虑,这块我们在后续来说明如何设计应用层协议。
④ 关于端口被占用的问题
如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这种情况也叫端口被占用。对于java进程来说,端口被占用的常见报错信息如下:

在这里插入图片描述
此时需要检查进程B绑定的是哪个端口,再查看该端口被哪个进程占用。以下为通过端口号查进程的方式:
① 在cmd输入 netstat -ano | findstr 端口号 ,则可以显示对应进程的pid。如以下命令显示了8888进程的pid.
② 在任务管理器中,通过pid查找进程

解决端口被占用的问题:
① 如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B.
② 如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口.

5. UDP数据报套接字编程

1. DatagramSocket
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket 构造方法:

在这里插入图片描述

DatagramSocket 方法:

在这里插入图片描述

2. DatagramPacket
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:

在这里插入图片描述
DatagramPacket 方法:

在这里插入图片描述

3. InetSocketAddress

构造UDP发送的数据报时,需要传入SocketAddress ,该对象可以使用 InetSocketAddress来创建.
InetSocketAddress ( SocketAddress 的子类 )构造方法:

在这里插入图片描述

4. 写一个简单的 UDP 的客户端/服务器通信的程序,这个程序没有啥业务逻辑,只是单纯的调用 socket api.让客户端给服务器发送一个请求,请求就是一个从控制台输入的字符串,服务器收到字符串之后,也就会把这个字符串原封不动的返回给客户端, 客户端再显示出来.

服务器代码示例:

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

public class test1Sever {
    private static DatagramSocket socket = null;

    public test1Sever(int port) throws SocketException {
    //服务器的端口号由程序猿指定则相对可控,不容易出问题,所以不必交给系统.
        socket = new DatagramSocket(port);//服务器需要指定端口号,而客户端不用.
    }

    public static void start() throws IOException {
        System.out.println("服务器启动!");
        while(true) {
            //1. 接受客户端传过来的请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //客户端传过来的数据以二进制的形式存在requestPacket中
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());

            //2. 根据请求计算响应
            //由于此处我们只是实现一个简单的回显服务器,所以请求是什么,响应就是什么.
            String respond = process(request);

            //3. 将响应构造为packet,发回给客户端.
            DatagramPacket respondPacket = new DatagramPacket(respond.getBytes(),respond.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(respondPacket);

            //打印一个日志
            System.out.printf("[%s,%d] req = %s resp = %s\n",requestPacket.getSocketAddress().toString(),
                    requestPacket.getPort(),request,respond);
        }
    }
    public static String process(String request) {
        return request;
    }

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

客户端代码示例:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class test1Client {
    private  DatagramSocket socket = null;
    private  String serverIp = "";
    private  int serverPort = 0;

    public test1Client(String ip, int port) throws SocketException {
        //客户端的端口号如果是由客户手动指定则容易起冲突,所以要交给系统分配.

        socket = new DatagramSocket();//客户端的端口号由系统自行分配.
        //记录对端的ip和端口号.
        serverIp = ip;
        serverPort = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner sc = new Scanner(System.in);
        while(true) {
            //1. 给服务器发送请求.
            System.out.println("-> ");
            String request = sc.next();
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            //2. 读取服务器发过来的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);

            //3. 把响应转成字符串显示出来
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

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

执行过程:
1.服务器先启动. 服务器启动之后,就会进入循环,执行到 receive 这里并阻塞 (此时还没有客户端过来)
2.客户端开始启动,也会先进入 while 循环,执行 sc.next.并且也在这里阻塞当用户在控制台输入字符串之后,next 就会返回,从而构造请求数据并发送出来.
3.客户端发送出数据之后,
服务器会从 receive 中返回,进一步的执行解析请求为字符串,执行 process 操作,执行 send 操作
客户端则继续往下执行,执行到 receive,等待服务器的响应
4.客户端收到从服务器返回的数据之后, 就会从 receive 中返回执行这里的打印操作,也就把响应给显示出来了.
5.服务器这边完成一次循环之后,又执行到 receive 这里.客户端这边完成一次循环之后,又执行到 sc.next 这里双双进入阻塞

这俩程序,都是在同一个主机上.并没有真正的"跨主机通信"效果,想要让其他人访问我的服务器,就得将程序放到云服务器上. 因为云服务器上有公网IP.

如果此时我想实现一个英译汉的服务器,只需要重写process
代码示例:

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

public class test1DictServer extends test1Sever{
    private Map<String,String> dict = new HashMap<>();

    public test1DictServer(int port) throws SocketException {
        super(port);

        dict.put("dog","小狗");
        dict.put("elephant","大象");
        dict.put("cat","小猫");
    }

    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"没有查询到这个词");
    }

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

本篇博客到这里就结束啦, 感谢观看 ❤❤❤

🐎期待与你的下一次相遇😊😊😊


网站公告

今日签到

点亮在社区的每一天
去签到