JavaEE-网络编程-客户端服务器

协议是非常重要的芝士

协议分层,->解耦

TCP/IP五层协议

  1. 物理层 基础设施
  2. 数据链路层 两个相邻节点之间的数据传输
  3. 网络层 两个节点之间的路径规划
  4. 传输层 通信中的起点和重点
  5. 应用层 传过去的数据咋用

下层协议给上层提供服务,上层协议调用下层协议

封装和分用->发送和接受数据


网络编程(突破一台主机的限制)

一些网络编程中的基础概念

  1. 网络编程:两个/多个 进程,通过网络,来进行相互通信(写代码来实现)

进程具有隔离性(每个进程有自己独立的虚拟地址空间)

进程间通信->借助一个每个进程都能够访问到的公共区域,完成数据交换

网络编程也就是一种进程间通信的方式.

借助的公共区域就是网卡.是当下最主流的方式👇

既能够让同一个主机的多个进程间通信

也可以让不同主机的多个进程间通信

高并发,分布式,大数据

  1. 客户端(client)/服务器(server)

客户端:主动发送网络数据的一方

服务器:被动接受网络数据的一方

因为服务器无法知道客户端啥时候发来数据

因此就只能长时间运行,甚至7*24小时运行!

  1. 请求(request)/响应(response)

请求:客户端给服务器发送的数据

响应:服务器给客户端返回的数据.

  1. 客户端和服务器之间的交互方式

1)一问一答(最常见的方式)

客户端给服务器发送个请求

服务器给客户端返回个请求

2)多问一答(更少见一些,比如上传文件)

客户端发送多个请求

服务器返回一个响应

3)一问多答(出现的还行,比如播放视频,你点击一下,会返回弹幕,视频内容)

客户端发送一个请求

服务器返回多个相应

4)多问多答(远程控制,游戏串流)

客户端发送多个请求

服务器返回多个响应


进行网络编程,需要使用操作系统提供的网络编程API

 

传输层提供了两个非常重要的协议,截然不同

TCP

UDP

这两个协议对应的socket API也是截然不同的

如果谈到操作系统,最爱靠的就是多线程

那么谈到网络,最常考的就是TCP

简单概括

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

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

有连接:打电话.先建立连接,然后再通信

无连接:发微信.不必建立连接,直接通信即可

(网络通信是无法保证百分之百到达的)

可靠传输:数据对方收没收到,发送方能够有感知

不可靠传输:数据对方收没收到也不管, 不知道~

比如打电话,就是可靠传输

比如发微信,也就是不可靠传输

面向字节流:这里的字节流和文件那里的字节流是一样的(不光概念上是一样的,连代码编写都是一样的)

面向数据报:以数据报为传输的基本单位

全双工:双向通信,一个管道,能A->B,B->A 同时进行

半双工:单向通信,一个管道,同一时刻,要么A->B,要么B->A 不能同时进行

为啥一个管道能够双向通信,你想想公路就知道了.

 

网线,标准的以太网线,里面其实是八根铜线

有没有做过水晶头

 

打开之后就可以传输数据了

send():发送数据

receive():接收数据

close():关闭文件

DatagramPacket :

表示一个UDP数据报

UDP是面向数据报的协议

传输数据,就是以DatagramPacket为基本单位

InetSocketAddress IP地址+端口号

写一个UDP版本的回显服务器-客户端(echo server)

客户端发啥,服务器就返回啥~~

绑定一个端口,把这个进程和一个端口号关联起来

一个操作系统上面,有很多端口号.0-65535

程序如果需要进行网络通信,就需要获取到一个端口号

端口号相当于用来再网络上区分进程的身份标识符

(操作系统收到网卡的数据,就可以根据网络数据报中的端口号,来确定要把这个数据交给哪个进程)

分配端口号的过程

  1. 程序员手动指定

  2. 系统自动分配 new DatagramSocket();()系统会自动分配一个空闲的端口

 

一个端口,在通常情况下,是不能被同一个主机上的多个进程同时绑定的

一个进程是可以绑定多个端口的.

如果端口已经被占用的话

读取客户端发来的请求,尝试读取,不是说调用了一定能读到

 

如果客户端没有发来请求,receive就会阻塞等待

知道真的有客户端请求过来了,receive才会返回

 

这个方法是通过参数来放置读取到的数据的,而不是通过返回值.

 

 

通过后面的new,就构造了一个空的DatagramPacket

输入输出的时候很容易发生异常.

 

一个服务器最容易坏的就是硬盘,特别是机械硬盘

 

👆这是最新的

String的构造方法,String(byte[], int)这个版本的构造方法,是被舍弃的

Java版本一直在更新,出新的东西的同时,也在舍弃旧的东西

旧的东西不能立即就删,得给程序员留下缓冲的时间.

先标记为被舍弃,也就是不建议程序员继续使用,可能会在未来的版本中删掉)👇

这个注解就是被舍弃的意思

 

 

这个也是一种构造DatagramPacket的方式

先是拿字符串俩面的字节数组中,来构造Packet的内容

还要把请求中的客户端的地址拿过来,也填写到包中.

 

把数据发出去

 

 

必须使用下面的哪个length,下面的表示字节数,而上面的表示字符数

//如果是服务器的端口号一般是指定的,客户端的端口号一般是自动分配的
//如果服务器自动分配,客户端就不知道服务器的端口是啥了.
//因此,服务器有固定的端口,客户端才方便访问
//客户端程序是安装在用户的电脑上的,用户电脑当前运行哪些程序是不可控的
//如果要是手动指定端口,说不好这个端口就和其他程序的端口冲突了
//就导致我们的代码无法运行了.

这是另外一种DatagramPacket的构造方式

getByName此处的127.0.0.1是环回IP.就表示当前主机.

端口号就填写服务器的端口号

因为这个包裹是要从客户端发送给服务器的

所以就要知道发送的内容以及发送给的目的地是在哪里(收件人地址+端口)

目前我们已经见到了三个版本的DatagramPacket的构造

  1. 只太填写缓冲区,用来接收数据的,是一个空的PACKET
  2. 填写缓冲区,并且填写把包发给谁,InetAddress对象来表示的
  3. 填写缓冲区,并且填写把包发给谁,InetAddress+port

先理清楚,客户端和服务器的工作流程

服务器:

  1. 读取请求并解析

  1. 根据请求计算响应

 

  1. 构造响应数据,并返回给客户端.

客户端的核心流程

  1. 根据用户输入构造请求

 

 

  1. 把请求发送给服务器

  1. 读取服务器返回的响应

  1. 解析响应,并显示给用户

 

客户端和服务器的核心流程是紧密相连的.

上述流程,不仅仅是回显服务器客户端如此,大部分的客户端服务器都是如此

这都是一台基本套路

我们学习网络编程

  1. 学习网络编程的基本套路(核心流程)
  2. 学习socket api的使用

一个服务器是可以同时给多个客户端提供服务的

如果我现在不是想写一个回显服务器了,而是一个带有业务逻辑的服务器,怎么实现呢?

什么叫业务逻辑呢?

我们当前的回显服务器,是没有业务逻辑的

请求和回想都一样

正经的服务器,应该是请求和响应都不一样的,这样才有意义

主要就是在这个方法里面进行修改

我们新写一个类,继承这个服务器

重写一下process

package network;

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

/**
 * 字典服务器/翻译服务器
 * 希望实现一个英译汉的效果
 * 请求是一个英文单词,响应是对应的中文翻译
 */
public class UDPDictServer extends UDPEchoServer{
    private Map<String, String> dict = new HashMap<>();

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

        /**
         * 这里可以无限地构造下去
         * 即使是有道词典这种,也是按照类似地方法实现(打表)
         */
        dict.put("cat","小猫");
        dict.put("dog","小狗");
    }

    @Override
    public String process(String req) {
        return dict.getOrDefault(req,"这个词俺也不会");
    }

    public static void main(String[] args) throws IOException {
        UDPDictServer server = new UDPDictServer(8000);//8000这个端口相当于被我们占用了
        server.start();//启动服务器
    }
}

 

一个服务器地灵魂所在

一个服务器要完成地工作,都是通过"根据请求计算响应"来体现地

不管是啥样地服务器,读取请求并解析,构造响应并返回,这两个步骤,大同小异

以下是本篇博客涉及到的两个服务器和一个客户端代码

package network;

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

public class UDPEchoClient {
    private DatagramSocket socket = null;

    public UDPEchoClient()throws SocketException {
        //客户端的端口号,一般都是由操作系统自动分配的,虽然手动指定也行,但还是自动分配比较好
        //如果是服务器的端口号一般是指定的,客户端的端口号一般是自动分配的
        //如果服务器自动分配,客户端就不知道服务器的端口是啥了.
        //因此,服务器有固定的端口,客户端才方便访问
        //客户端程序是安装在用户的电脑上的,用户电脑当前运行哪些程序是不可控的
        //如果要是手动指定端口,说不好这个端口就和其他程序的端口冲突了
        //就导致我们的代码无法运行了.
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.让客户端从控制台读取一个请求数据
            System.out.print("> ");
            String request = scanner.next();
            //2.把这个字符串请求发送给服务器,构造DatagramPacket
            //  我们构造的Packet既要包含要传输的数据,又要包含把数据发到哪里
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length,
                    InetAddress.getByName("127.0.0.1"),8000);
            //3.把数据包发给服务器
            socket.send(requestPacket);
            //4.我们要从服务器中读取响应数据
            //  因为服务器收到数据后会发出响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            //5.我们再把响应的数据获取出来,转成字符串
            String response = new String(responsePacket
                    .getData(),0,responsePacket.getLength());

            System.out.printf("req: %s;resp: %s\n",request,response);
        }
    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient client = new UDPEchoClient();
        client.start();
    }
}
package network;

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

public class UDPEchoServer {
    //要想创建UDP服务器,首先要先打开一个socket文件
    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);
            socket.receive(requestPacket);
            //2.对请求进行解析,把DatagramPacket转成一个String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //3.根据请求,处理响应.虽然咱们这个是个回显服务器,但是还是可以单独搞个方法来做这个事情
            String response = process(request);
            //4.把响应构造成DatagramPacket对象
            //  构造响应对象,要搞清楚,对象要发给谁,谁给咱们发的请求,我们就把响应发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length,
                    requestPacket.getSocketAddress());
            //5.把这个DatagramPacket对象返回给客户端
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req=%s;resp=%s\n",responsePacket.getAddress().toString(),responsePacket.getPort(),
                    request,response);//IP和端口,请求和响应
        }
    }
    //根据这个方法,实现根据请求计算响应 这个过程
    //由于是回显服务器,所以不涉及到其他裸机
    //但是如果是其他服务器,就可以在process里面,来加上一些其他逻辑的处理
    public String process(String req){
        return req;
    }

    public static void main(String[] args) throws IOException {
        //真正启动服务器,这个端口号说是随便写,但是也是有范围的.0-65535
        //但是一般来说1024以下的端口,都是系统保留
        //因此咱们自己写代码,端口尽量还是选择1024以上,65535以下的.
        UDPEchoServer server = new UDPEchoServer(8000);//8000这个端口相当于被我们占用了
        server.start();//启动服务器
    }
}

package network;

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

/**
 * 字典服务器/翻译服务器
 * 希望实现一个英译汉的效果
 * 请求是一个英文单词,响应是对应的中文翻译
 */
public class UDPDictServer extends UDPEchoServer{
    private Map<String, String> dict = new HashMap<>();

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

        /**
         * 这里可以无限地构造下去
         * 即使是有道词典这种,也是按照类似地方法实现(打表)
         */
        dict.put("cat","小猫");
        dict.put("dog","小狗");
    }

    @Override
    public String process(String req) {
        return dict.getOrDefault(req,"这个词俺也不会");
    }

    public static void main(String[] args) throws IOException {
        UDPDictServer server = new UDPDictServer(8000);//8000这个端口相当于被我们占用了
        server.start();//启动服务器
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gremmie2003

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值