网络原理(二):网络编程

上一章聊到了一些网络通信基础的概念,也举了例子,体会了以下大概的过程(微信聊天)。

什么是网络编程?

网络通常指的是计算机中的互联网,是由多台计算机通过网线或其他媒介相互连接组成的,编写基于网络的应用程序的过程称之为网络编程。

网络上的主机通过不同进程,以搬移的形式实现的网络通信(完成的数据传输);

当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。

网络编程又叫套接字,实现网络通信的两端就是套接字。分为服务器对应的套接字和客户端对应的套接字。

网络传输中的基本概念

发送端和接收端:

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

请求和响应:

一般来说,获取一个网络资源,涉及到两次网络数据传输:

  • 第一次:请求(request)数据的发送
  • 第二次:响应(response)数据的发送

客户端和服务端:

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

  • 客户端获取服务资源
  • 客户端保存资源在服务端

常见的客户端服务端模型

最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

  1. 客户端先发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)

Socket(套接字)

我们程序员编写网络编程主要是在应用层,其他层全部被打包好了。

真正要发送某个数据,需要上层调用下层协议,下层协议为上层提供一组 api 。

这个 api 就是Socket 。

这个Socket api本身是由 C语言实现的,Java又是基于C 语言和C++ 实现的,那么Java就将其打包成具有Java风格的 api。

我们一般将 Socket 分为三类:流套接字、数据报套接字、原始套接字(这里不涉及,因为用的很少)。

我们这一章,主要涉及到前面两种套接字的编写。

数据报套接字:使用传输层UDP协议

流套接字:使用传输层TCP协议

基于 UDP 的api

我们先来看看系统提供的 api 有哪些方法:

DatagramSocket API

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报

DatagramSocket 构造方法

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

服务器这里的socket 对象,必须关联一个 端口;

而服务器,则不需要手动关联,系统会自动分配一个空闲的端口。

我们举个例子:服务器就好像一个食堂某个窗口,而客户端就好像来用餐的人。

窗口需要提供 IP 地址(具体是哪个食堂),和 端口号(具体是哪个窗口),而用餐的人只需要 找到这个 IP地址和端口号,就能让窗口为我们提供服务。

再来看看 DatagramSocket 为用户提供的方法:

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

我们网络编程本质上也是个文件,既然是文件,就需要使用完后即关闭。

DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报。

DatagramPacket 构造方法:

方法签名 方法说明
DatagramPacket(byte[]
buf, int length)

构造一个DatagramPacket以用来接收数据报,接收的数据保存在

字节数组(第一个参数buf)中,接收指定长度(第二个参数length)

DatagramPacket(byte[]
buf, int offset, int length,
SocketAddress address)

构造一个DatagramPacket以用来发送数据报,发送的数据为字节
数组(第一个参数buf)中,从0到指定长度(第二个参数length)。

address指定目的主机的IP和端口

上面的一个不需要设置地址进去,通常用来接收消息;

下面的一个需要设置地址进去,通常用来发送消息。

DatagramPacket 方法:

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

等会,写代码的时候,边看代码边使用。

基于UDP 的服务器

基于上述所说,我们来编写一个最基础的服务器,回显服务器(Echo Service)

回显服务器:客户端发送什么,服务器就返回什么。

先来编写服务器:

编写服务器的三大步骤:

  1. 读取请求并解析
  2. 根据请求计算相应
  3. 将响应返回到客户端

回响客户端和服务器

服务器:

我们先来看看服务器的代码:


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

public class udpEchoService {
    // 创建一个 socket 对象
    // 想要实现一个网络编程必须要 有一个 socket 对象
    private DatagramSocket socket = null;

    // 绑定一个窗口,不一定能成功
    // 如果这个端口被其他进程占用,那么这么链接就会出错
    // 同一时刻,一个主机,一个进程,只能绑定一个 端口

    public udpEchoService(int port) throws SocketException {
        this.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);
            // 为了方便处理,这里把这个数据包转成 String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            // 2. 根据请求计算响应(此处省略这个步骤)
            String response = process(request);

            // 3.把相应结果写到客户端
            //    根据request 字符串构造一个DatagramPacket
            //    和请求 packet 不同, 此处构造响应的时候, 需要指定这个包要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.length(),
                    // requestPacket 是从客户端收来的, getSocketAddress() 会得到客户端的 IP 和 端口
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }
    // 这个方法希望是根据请求计算响应.
    // 由于咱们写的是个 回显 程序. 请求是啥, 响应就是啥!!
    // 如果后续写个别的服务器, 不再回显了, 而是有具体的业务了, 就可以修改 process 方法,
    // 根据需要来重新构造响应.
    // 之所以单独列成一个方法, 就是想让同学们知道, 这是一个服务器中的关键环节!!!
    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        udpEchoService udpEchoServer = new udpEchoService(1010);
        udpEchoServer.start();
    }
}

我们简单的来介绍一下服务器:

 

 process 方法啥也不干,以后写其他服务器的时候,根据需求再写代码。

 这样一个简单的回显服务器就写好了。

客户端:

我们再来看看回显客户端:

代码:

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

public class udpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;
    // 客户端启动, 需要知道服务器在哪里!!
    public udpEchoClient(String serverIP, int serverPort) throws SocketException {
        // 对于客户端来说, 不需要显示关联端口.
        // 不代表没有端口, 而是系统自动分配了个空闲的端口.
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        // 通过这个客户端可以多次和服务器进行交互.
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 分四步走:
            //  1. 先从控制台, 读取一个字符串过来
            //         先打印一个提示符, 提示用户要输入内容
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 把字符串构造成 UDP packet, 并进行发送.
            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);
            // 4. 把响应数据转换成 String 显示出来.
            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("127.0.0.1", 1010);
        client.start();
    }
}

同样我也来稍稍解析一下代码:

我们这里比服务器多了两个属性: IP 的 端口号。

我们的客户端需要去找到服务器的位置,而服务器不需要去找客户端的位置 。

 

这样一个简单的回显客户端和服务器就写好了。

我们来看看效果:先启动服务器再启动客户端,否则服务器无法接收到客户端发来的请求。

 

 

分析代码执行过程

 我们再来分析一下代码执行过程:

  1. 服务器先执行,走到receive ,线程阻塞
  2. 客户端启动,从控制台输入信息,并发送(send)给服务器。    第三步开始分两步走,一个是客户端,一个是服务器
  3. 客户端这边,继续往后走,走到receive 读取响应,造成阻塞等待
  4. 服务器这边,就从receive返回,读到请求数据(客户端发送的),往后走到process 拿到响应,再发送响应
  5. 客户端收到服务器send 回来的数据后,阻塞解除,继续执行后面的操作。
  6. 服务器进入下一次循环,再次阻塞在 receive ,等待客户端的请求
  7. 客户端继续下一轮循环,阻塞再 scanner.next 等待用户输入

 这样基于udp 的客户端服务器就做好了,下一章来看看基于tcp的客户端服务器。

猜你喜欢

转载自blog.csdn.net/weixin_67807492/article/details/130020957