网络编程.

一、网络编程

1.网络编程基础

(1).什么是网络编程

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

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

(1)发送端和接收端

在一次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

收发端:发送端和接收端两端,也简称为收发端

注意:发送端和接收端只是相对的。

(2)请求和响应

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

          第一次:请求数据的发送。

          第二次:响应数据的发送。

(3)客户端与服务端

         服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。

         客户端:获取服务的一方进程,称为客户端

(4)常见的客户端服务端模型

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

     1. 客户端先发送请求到服务端

     2. 服务端根据请求数据,执行相应的业务处理

     3. 服务端返回响应:发送业务处理结果

     4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)

2.Socket套接字

(1)概念

        Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程。是应用层与传输层之间的桥梁,是程序的入口

(2)分类

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

TCP,即Transmission Control Protocol(传输控制协议),传输层协议。

以下为TCP的特点

       有连接(有连接:类似打电话,需要先接通,才可以讲话,进行沟通(数据通信))

       可靠传输(传输的过程中,数据的发送方,知道这个数据有没有发送成功,有没有手袋数据,打电话就是可靠传输)

       面向字节流(以字节为单位,进行数据传输(类似于文件的字节流)TCP可以一次接收1个字节或10个,或20个字节。类似于发快递—发床,床可以拆分发好几个快递,一个快递是床头,一个快递是床板—TCP)

       有接收缓冲区,也有发送缓冲区(全双工)

       大小不限

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

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

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。

以下为UDP的特点:

     无连接(无连接:类似发短信,不需要打通,只需要知道电话号码就可以进行发送数据(电话号码存在))

     不可靠传输(传输的过程中,数据发送方不知道数据有没有成功到达,或者接收方有没有收到数据。微信,短信,QQ都是不可靠传输。发完之后,也不知道对方是否收到了)

     面向数据报(以数据报为单位,进行数据传输。UDP一次发送/接收必须是完整的数据报,不能是半个/一个半。类似于发快递,发电视机,电视机不能拆分,只能发送一个完整的电视机UDP)

     有接收缓冲区,无发送缓冲区(全双工:一条链路,双向通路。A和B可以同时互相发消息,两个方向同时传输,A既能发又能收,B也是)

     大小受限:一次最多传输64k

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

3.UDP数据报套接字编程

(1)DatagramSocket API (数据报套接字 API)

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

   DatagramSocket 构造方法:

   DatagramSocket 方法:

(2)DatagramPacket API (数据包 API)

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

    DatagramPacket 构造方法:

    DatagramPacket 方法:

(4)示例一:一发一收(无响应)

以下为一个客户端一次数据发送,和服务端多次数据接收(一次发送一次接收,可以接收多次),即只 有客户端请求,但没有服务端响应的示例:

UDP服务端

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

/**
 * 服务端
 */
public class UDPServer {
    //1. 创建套接字
    //2. 接收数据
    //3. 处理数据
    //4. 显示数据
    public DatagramSocket socket = null;

    //服务端, 需要指定端口号
    public UDPServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务端已启动, 等待接收数据...");
        /**
         * void receive(DatagramPacket p)
         *  DatagramPacket: 数据报
         */
        /**
         * DatagramPacket(byte buf[], int length)
         * 参数说明
         * buf: 接收数据的数组, 类似于食堂打饭的餐盘
         * length: 接收数据的长度
         */
        while (true) {
            byte buf[]  = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf,buf.length);
            socket.receive(packet);
            //处理数据
            String request = new String(packet.getData(),0,packet.getLength(),
                    "UTF-8");

            System.out.printf("接收到客户端的数据:[%s:%d]:%s\n",packet.getAddress().getHostName(),
                    packet.getPort(),request);
        }

    }

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

}

运行后,服务端就启动了,控制台输出如下:

可以看出,此时代码是阻塞等待在 socket.receive(packet) 代码行,直到接收到一个UDP数据报。

UDP客户端

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

/**
 * 客户端
 * <p>
 * 站在客户端的角度, 五元组
 * 源IP: 自己
 * 源端口号: 系统分配
 * 目的IP: 服务器的IP, 127.0.0.1
 * 目的端口: 服务器的端口,9090
 * 协议: UDP
 */
public class UDPClient {
    //1. 创建socket
    //2. 发送数据
    DatagramSocket socket = null;
    String serverIp;
    int serverPort;

    public UDPClient(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);
        /**
         * void send(DatagramPacket p)
         *
         * DatagramPacket  发送的数据报, 数据报中要指明目的IP和端口
         *
         * DatagramPacket(byte buf[], int offset, int length,
         *                           InetAddress address, int port)
         *  参数说明:
         *  buf: 发送的数据的byte数组
         *  offset: 从数组的某一位开始发送
         *  length: 发送的数据长度
         *  address: IP
         *  port: 端口号
         */
        while (true){
            System.out.println("请输入要发送的内容:");
            String request = scanner.nextLine();//从控制台接收一个数据

            DatagramPacket packet = new DatagramPacket(request.getBytes(),0,
                    request.getBytes().length, InetAddress.getByName(serverIp),serverPort );
            socket.send(packet);
        }

    }

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

}

客户端启动后会发送一个"hello world!" 的字符串到服务端,在服务端接收后,控制台输出内容如下

在客户端输入内容:

服务端响应接收到传输的内容:

从以上可以看出,发送的UDP数据报(假设发送的数据字节数组长度为M),在接收到以后(假设接收 的数据字节数组长度为N):

1. 如果N>M,则接收的byte[]字节数组中会有很多初始化byte[]的初始值0,转换为字符串就是空白 字符;

2. 如果N<M,则会发生数据部分丢失(可以自己尝试,把接收的字节数组长度指定为比发送的字节数组长度更短)。

要解决以上问题,就需要发送端和接收端双方约定好一致的协议,如规定好结束的标识或整个数据的长度。

(5)示例二:请求响应

客户端发送请求,服务端要进行响应,客户端根据响应进行处理。比如简单的字典翻译服务,服务端根据客户端发送的请求,进行翻译,并把翻译的结果返回给客户端。

UDP服务端

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

/**
 * 服务端
 */
public class UDPDictServer {
    //1. 创建套接字
    //2. 接收数据
    //3. 处理数据
    //4. 显示数据
    public DatagramSocket socket = null;
    public HashMap<String,String> dictMap = new HashMap<>();

    //服务端, 需要指定端口号
    public UDPDictServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
        dictMap.put("cat","猫");
        dictMap.put("dog","狗");
        dictMap.put("pig","猪");
        dictMap.put("fox","狐狸");
        dictMap.put("frog","青蛙");
    }

    public void start() throws IOException {
        System.out.println("服务端已启动, 等待接收数据...");
        /**
         * void receive(DatagramPacket p)
         *  DatagramPacket: 数据报
         */
        /**
         * DatagramPacket(byte buf[], int length)
         * 参数说明
         * buf: 接收数据的数组, 类似于食堂打饭的餐盘
         * length: 接收数据的长度
         */
        while (true) {
            byte buf[]  = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf,buf.length);
            socket.receive(packet);
            //处理数据
            String request = new String(packet.getData(),0,packet.getLength(),
                    "UTF-8");

//            System.out.printf("接收到客户端的数据:[%s:%d]:%s\n",packet.getAddress().getHostName(),
//                    packet.getPort(),request);
            String response = process(request);
            //发送给客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length,packet.getSocketAddress());
            socket.send(responsePacket);

            System.out.printf("接收到客户端的数据:[%s:%d]:%s, 返回数据:%s\n",packet.getAddress().getHostName(),
                    packet.getPort(),request,response);
        }

    }

    private String process(String request) {
        return dictMap.getOrDefault(request,"无法翻译");
    }

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

}

服务端启动:

UDP客户端

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

/**
 * 客户端
 * <p>
 * 站在客户端的角度, 五元组
 * 源IP: 自己
 * 源端口号: 系统分配
 * 目的IP: 服务器的IP, 127.0.0.1
 * 目的端口: 服务器的端口,9090
 * 协议: UDP
 */
public class UDPDictClient {
    //1. 创建socket
    //2. 发送数据
    DatagramSocket socket = null;
    String serverIp;
    int serverPort;

    public UDPDictClient(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);
        /**
         * void send(DatagramPacket p)
         *
         * DatagramPacket  发送的数据报, 数据报中要指明目的IP和端口
         *
         * DatagramPacket(byte buf[], int offset, int length,
         *                           InetAddress address, int port)
         *  参数说明:
         *  buf: 发送的数据的byte数组
         *  offset: 从数组的某一位开始发送
         *  length: 发送的数据长度
         *  address: IP
         *  port: 端口号
         */
        while (true){
            System.out.println("请输入要发送的内容:");
            String request = scanner.nextLine();//从控制台接收一个数据

            DatagramPacket packet = new DatagramPacket(request.getBytes(),0,
                    request.getBytes().length, InetAddress.getByName(serverIp),serverPort );
            socket.send(packet);

            //接收响应
            byte[] buf = new byte[1024];
            DatagramPacket responsePacket = new DatagramPacket(buf,buf.length);
            socket.receive(responsePacket);
            //解析并打印出来
            String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
            System.out.printf("发送数据:%s,接收到数据:%s\n",request,response);
        }

    }

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

}

客户端启动:

在客户端输入要翻译的英语单词:

服务端接收到数据后,返回翻译结果:

4.TCP流套接字编程

(1)ServerSocket API

ServerSocket 是创建TCP服务端Socket的API。(客户端不用)

ServerSocket 构造方法:

ServerSocket 方法:

(2)Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据 的。

Socket 构造方法:

Socket 方法:

(3)TCP中的长短链接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数 据。

长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以 多次收发数据。

对比以上长短连接,两者区别如下:

建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要 第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时 的,长连接效率更高。

主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送 请求,也可以是服务端主动发。

两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于 客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的 消耗是不能承受的。

由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。

一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的 处理请求。

(4)示例一:一发一收(短连接)

以下为一个客户端一次数据发送,和服务端多次数据接收(一次发送一次接收,可以接收多次),即只 有客户端请求,但没有服务端响应的示例:

TCP服务器:


import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * TCP 服务端
 */
public class TCPServer {
    public ServerSocket serverSocket = null;
    //对serversocket 进行初始化
    //服务端的ServerSocket 需要指定端口号
    public TCPServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //创建一个固定线程数的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void start() throws IOException {
        //1.等待建立连接
        System.out.println("服务端已启动,等待客户端建立连接...");
        //没有客户端请求的话, accept会一直被阻塞, 直到有客户端来请求建立连接
        //clientSocket  就类似餐馆返回给用户的小票
        while (true){
            Socket clientSocket = serverSocket.accept();
            //2.接收数据并处理数据
            executorService.submit(()->{
                process(clientSocket);//此时, 服务端返回Socket, 服务端和客户端已经建立连接
            });

        }

    }

    private void process(Socket clientSocket) {
        System.out.printf("和客户端建立连接[%s:%d]\n",
                clientSocket.getInetAddress().getHostName(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream()){
            //方便起见, 使用Scanner
            Scanner scanner = new Scanner(inputStream);
            while (true){
                if (!scanner.hasNext()){
                    System.out.printf("和客户端断开连接[%s:%d]\n",
                            clientSocket.getInetAddress().getHostName(),clientSocket.getPort());
                    break;
                }
                String request = scanner.nextLine();
                System.out.printf("接收到客户端发送的数据[%s:%d]:%s\n",
                        clientSocket.getInetAddress().getHostName(),clientSocket.getPort(),request);

            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

TCP客户端:

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * TCP 客户端
 */
public class TCPClient {
    //建立连接
    public Socket socket = null;

    public TCPClient(String serverIp, int serverPort) throws IOException {
        //如果服务端没有启动, 连接是建立不成功的
        socket = new Socket(serverIp,serverPort);//和服务端建立连接
    }

    public void start(){
        System.out.println("和服务端建立连接....");
        try(OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter writer = new PrintWriter(outputStream);
            //发送数据
            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.println("请输入发送的数据:");
                String request = scanner.nextLine();//从控制台输入发送的数据
                writer.println(request);
                writer.flush();
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

启动两个客户端,并发送不同的数据:

服务端接收到数据后:

以上客户端与服务端建立的为短连接,每次客户端发送了TCP报文,及服务端接收了TCP报文后,双方 都会关闭连接。

(5)示例二:请求响应(短连接)

TCP服务端:

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.HashMap;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * TCP 服务端
 */
public class TCPDictServer {
    public ServerSocket serverSocket = null;

    public HashMap<String,String> dictMap = new HashMap<>();
    //对serversocket 进行初始化
    //服务端的ServerSocket 需要指定端口号
    public TCPDictServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
        dictMap.put("cat","猫");
        dictMap.put("dog","狗");
        dictMap.put("pig","猪");
        dictMap.put("fox","狐狸");
        dictMap.put("frog","青蛙");
    }

    //创建一个固定线程数的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void start() throws IOException {
        //1.等待建立连接
        System.out.println("服务端已启动,等待客户端建立连接...");
        //没有客户端请求的话, accept会一直被阻塞, 直到有客户端来请求建立连接
        //clientSocket  就类似餐馆返回给用户的小票
        while (true){
            Socket clientSocket = serverSocket.accept();
            //2.接收数据并处理数据
            executorService.submit(()->{
                process(clientSocket);//此时, 服务端返回Socket, 服务端和客户端已经建立连接
            });

        }

    }

    private void process(Socket clientSocket) {
        System.out.printf("和客户端建立连接[%s:%d]\n",
                clientSocket.getInetAddress().getHostName(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream()){
            try(OutputStream outputStream = clientSocket.getOutputStream()) {
                //方便起见, 使用Scanner
                Scanner scanner = new Scanner(inputStream);
                while (true){
                    if (!scanner.hasNext()){
                        System.out.printf("和客户端断开连接[%s:%d]\n",
                                clientSocket.getInetAddress().getHostName(),clientSocket.getPort());
                        break;
                    }
                    String request = scanner.nextLine();

                    //对接收的数据, 进行处理(翻译)
                    String response = processRequest(request);
                    //把响应内容, 返回给客户端
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    writer.flush();
                    System.out.printf("接收到客户端发送的数据[%s:%d]:%s,返回响应数据: %s\n",
                            clientSocket.getInetAddress().getHostName(),clientSocket.getPort(),request,response);

                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String processRequest(String request) {
        return dictMap.getOrDefault(request,"无法翻译");
    }

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

TCP客户端:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * TCP 客户端
 */
public class TCPDictClient {
    //建立连接
    public Socket socket = null;

    public TCPDictClient(String serverIp, int serverPort) throws IOException {
        //如果服务端没有启动, 连接是建立不成功的
        socket = new Socket(serverIp,serverPort);//和服务端建立连接
    }

    public void start(){
        System.out.println("和服务端建立连接....");
        try(OutputStream outputStream = socket.getOutputStream()) {
            try(InputStream inputStream = socket.getInputStream()){
                PrintWriter writer = new PrintWriter(outputStream);
                //发送数据
                Scanner scanner = new Scanner(System.in);//此行代码, 是为了从控制台拿到数据
                while (true){
                    System.out.println("请输入发送的数据:");
                    String request = scanner.nextLine();//从控制台输入发送的数据
                    writer.println(request);
                    writer.flush();

                    //接收响应
                    Scanner responseScanner = new Scanner(inputStream);
                    String response = responseScanner.nextLine();
                    System.out.printf("发送数据:%s,接收到响应数据:%s\n",request,response);
                }
            }



        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

启动服务端,进行翻译:

启动客户端:

TCP与UDP都是传输层的协议,其他层的协议都要和传输层的协议相匹配。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值