计算机网络(网络编程)

什么是网络编程

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

发送端和接收端

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

请求和响应

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

客户端和服务端

服务端: 提供服务
客户端: 获取服务

Socket套接字

概念

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

分类

Socket套接字主要针对传输层协议划分为如下三类:
流套接字:使用传输层TCP协议
TCP(即Transmission Control Protocol(传输控制协议),传输层协议)
特点:

1:有连接 2:可靠传输 3:面向字节流 4:有接受缓冲区,有发送缓冲区 5:大小不限 6:全双工
数据报套接字:使用传输层UDP协议 UDP(即User Datagram Protocol(用户数据报协议),传输层协议) 特点:
1:无连接 2:不可靠传输 3:面向数据报 4:有接受缓冲区,无发送缓冲区 5:大小受限,一次最多传输64k 6:全双工

UDP数据报套接字编程

DatagramSocket API

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

DatagramSocket构造方法:

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

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 方法:

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

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

InetSocketAddress API

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法签名 方法说明
InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址,包含IP地址和端口号

示例一:回显服务

服务器:

  1:创建一个 DatagramSocket 对象,创建的同时关联一个端口号
  2: 读取请求 并解析
  3:根据请求计算响应
  4:把响应写回到客户端
  5:打印日志

客户端:

   1: 创建一个 DatagramSocket 对象,创建的同时指定服务器的ip和端口号
   2:读取输入的数据
   3:构造请求 并 发送给服务器
   4:从服务器读取响应
   5:把数据显示给用户

这个案例不涉及什么业务逻辑,就只是单纯的请求内容是啥,得到的响应是啥.

UdpEchoServer

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

public class UdpEchoServer {
    
    

    //进行网络编程,第一步需要先准备好socket实例, 这是进行网络编程的大前提
    private DatagramSocket socket = null;

    //构造方法
    public UdpEchoServer(int port) throws SocketException {
    
    
socket = new DatagramSocket(port);
    }

    //启动服务器.
    public void start() throws IOException {
    
    
        System.out.println("启动服务器!");

        //UDP是无连接,不需要建立连接,直接接收从客户端来的数据即可
        while(true){
    
    
            //1.读取客户端发来的请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);  //为了接收数据,需要先准备一个空的DatagramPacket对象,由receive来进行填充

            //把DatagramPacket解析成一个String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");


            //2.根据请求计算响应(咱们是一个回显服务)
            String response = process(request);


            //3.把响应写回客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(requestPacket);
            System.out.printf("[%s : %d] req:%s,resp:%s\n",
                    requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }


    //由于是回显服务,响应就和请求一样了.
    //然而对于一个真实服务器来说,这个过程其实是最复杂的,为了实现这个过程,可能需要几万行,甚至是几十万行代码...
    private String process(String request) {
    
    
        return request;
    }

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

在这里插入图片描述

UdpEchoClient

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 UdpEchoClient {
    
    
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    // 站在客户端的角度:
    // 源 IP: 本机 IP
    // 源端口: 系统分配的端口
    // 目的 IP: 服务器的 IP
    // 目的端口: 服务器的端口
    // 协议类型: UDP
    public UdpEchoClient(String ip, int port) throws SocketException {
    
    
        // 此处的 port 是服务器的端口.
        // 客户端启动的时候, 不需要给 socket 指定端口. 客户端自己的端口是系统随机分配的~~
        socket = new DatagramSocket();
        serverIP = ip;
        serverPort = port;
    }

    public void start() throws IOException {
    
    
        Scanner scanner = new Scanner(System.in);
        while (true) {
    
    
            // 1. 先从控制台读取用户输入的字符串
            System.out.print("-> ");
            String request = scanner.next();


            // 2. 把这个用户输入的内容, 构造成一个 UDP 请求, 并发送.
            //    构造的请求里包含两部分信息:
            //    1) 数据的内容. request 字符串
            //    2) 数据要发给谁~ 服务器的 IP + 端口
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIP), serverPort);
            socket.send(requestPacket);


            // 3. 从服务器读取响应数据, 并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");


            // 4. 把响应结果显示到控制台上.
            System.out.printf("req: %s, resp: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
    
    

        // 由于服务器和客户端在同一个机器上, 使用的 IP 仍然是 127.0.0.1 . 如果是在不同的机器上, 当然就需要更改这里的 IP 了
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

在这里插入图片描述在这里插入图片描述这个就是端口号被占用了,(一个手机号不能给两个人用)

示例二:翻译功能

我们实现一个输入英文,输出对应的汉字翻译
跟上面那个实验的服务器差不多,接收响应就行了,我们直接重写

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

public class UdpDictServer extends UdpEchoServer {
    
    
    private HashMap<String, String> dict = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
    
    
        super(port);
        
        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("duck", "小丫");
        dict.put("pig", "小猪");
    }

    @Override
    public String process(String request) {
    
    
        return dict.getOrDefault(request, "该词无法被翻译!");
    }

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

在这里插入图片描述

TCP流套接字编程

ServerSocket API

ServerSocket是创建TCP服务端Socket的API
ServerSocket 构造方法:

方法签名 方法说明
ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket 方法:

方法签名 方法说明
Socket accept() 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close() 关闭此套接字

Socket API

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

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

Socket 构造方法:

方法签名 方法说明
Socket(String host,int port) 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket方法:

方法签名 方法说明
InetAddress getInetAddress() 返回套接字所连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

示例一:回显服务

这个只是普通的一对一(因为里面第一次accept结束之后,就会进入processConnection,里面又有一个循环,如果里面不结束,他就无法执行,然后就又会导致外层循环卡住,就不能接收第二个客户的连接了)

一个客户端,一个服务器

TcpEchoServer

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    
    
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
    
    
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
    
    
        System.out.println("服务器启动!");
        while (true) {
    
    
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) {
    
    
        System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下来来处理请求和响应
        // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
        try (InputStream inputStream = clientSocket.getInputStream()) {
    
    
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
    
    
                // 循环的处理每个请求, 分别返回响应
                Scanner scanner = new Scanner(inputStream);
                while (true) {
    
    
                    // 1. 读取请求
                    if (!scanner.hasNext()) {
    
    
                        System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
                    String request = scanner.next();
                    
                    // 2. 根据请求, 计算响应
                    String response = process(request);
                    
                    // 3. 把这个响应返回给客户端
                    // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
                    printWriter.flush();

                    System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 此处要记得来个关闭操作.
            try {
    
    
                clientSocket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
    
    
        return request;
    }

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

在这里插入图片描述

TCPEchoClient

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

public class TcpEchoClient {
    
    
    // 用普通的 socket 即可, 不用 ServerSocket 了
    // 此处也不用手动给客户端指定端口号, 让系统自由分配.
    private Socket socket = null;

    public TcpEchoClient(String serverIP, int serverPort) throws IOException {
    
    
        // 其实这里是可以给的. 但是这里给了之后, 含义是不同的. ~~
        // 这里传入的 ip 和 端口号 的含义表示的不是自己绑定, 而是表示和这个 ip 端口建立连接!!
        socket = new Socket(serverIP, serverPort);
    }

    public void start() {
    
    
        System.out.println("和服务器连接成功!");
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream()) {
    
    
            try (OutputStream outputStream = socket.getOutputStream()) {
    
    
                while (true) {
    
    
                    // 1. 从控制台读取字符串
                    System.out.print("-> ");
                    String request = scanner.next();

                    // 2. 根据读取的字符串, 构造请求, 把请求发给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);

                    printWriter.flush(); // 如果不刷新, 可能服务器无法及时看到数据.
                    // 3. 从服务器读取响应, 并解析
                    Scanner respScanner = new Scanner(inputStream);
                    String response = respScanner.next();

                    // 4. 把结果显示到控制台上.
                    System.out.printf("req: %s, resp: %s\n", request, response);
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

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


在这里插入图片描述
改进 利用多线程来处理多个客户端的介入

一个服务器,多个客户端
在这里插入图片描述

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.Scanner;

public class TcpThreadEchoServer {
    
    
    private ServerSocket serverSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
    
    
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
    
    
        System.out.println("服务器启动!");
        while (true) {
    
    
            Socket clientSocket = serverSocket.accept();
            // [改进方法] 在这个地方, 每次 accept 成功, 都创建一个新的线程, 由新线程负责执行这个 processConnection 方法~
            Thread t = new Thread(() -> {
    
    
                processConnection(clientSocket);
            });
            t.start();
        }
    }

    private void processConnection(Socket clientSocket) {
    
    
        System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());

        // 接下来来处理请求和响应
        // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
        try (InputStream inputStream = clientSocket.getInputStream()) {
    
    
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
    
    
                // 循环的处理每个请求, 分别返回响应
                Scanner scanner = new Scanner(inputStream);
                while (true) {
    
    

                    // 1. 读取请求
                    if (!scanner.hasNext()) {
    
    
                        System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next();

                    // 2. 根据请求, 计算响应
                    String response = process(request);

                    // 3. 把这个响应返回给客户端
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 此处要记得来个关闭操作.
            try {
    
    
                clientSocket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
    
    
        return request;
    }

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

在这里插入图片描述还可以优化一下,用线程池:::

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

public class TcpThreadPoolEchoServer {
    
    
    private ServerSocket serverSocket = null;

    public TcpThreadPoolEchoServer(int port) throws IOException {
    
    
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
    
    
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
    
    
            Socket clientSocket = serverSocket.accept();
            // 通过线程池来实现
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    processConnection(clientSocket);
                }
            });
        }
    }

    private void processConnection(Socket clientSocket) {
    
    
        System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下来来处理请求和响应
        // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
        try (InputStream inputStream = clientSocket.getInputStream()) {
    
    
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
    
    
                // 循环的处理每个请求, 分别返回响应
                Scanner scanner = new Scanner(inputStream);
                while (true) {
    
    
                    
                    // 1. 读取请求
                    if (!scanner.hasNext()) {
    
    
                        System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next();
                    
                    // 2. 根据请求, 计算响应
                    String response = process(request);
                    
                    // 3. 把这个响应返回给客户端
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
                    printWriter.flush();

                    System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 此处要记得来个关闭操作.
            try {
    
    
                clientSocket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public String process(String request) {
    
    
        return request;
    }

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

示例二:翻译字典

这个直接继承刚刚的TCP线程池的服务器

TCPDictServer

import java.io.IOException;
import java.util.HashMap;

public class TcpDictServer extends TcpThreadPoolEchoServer {
    
    
    private HashMap<String, String> dict = new HashMap<>();

    public TcpDictServer(int port) throws IOException {
    
    
        super(port);

        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("pig", "小猪");
        dict.put("duck", "小鸭");
    }

    @Override
    public String process(String request) {
    
    
        return dict.getOrDefault(request, "当前的词无法翻译");
    }

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

在这里插入图片描述在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/chenbaifan/article/details/124140166
今日推荐