网络编程套接字(Socket编程)

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

网络编程套接字(Socket编程)_第1张图片

 网络编程套接字(Socket编程)_第2张图片

Socket API 分类:

网络编程套接字(Socket编程)_第3张图片

1.UDP数据报套接字编程:

网络编程套接字(Socket编程)_第4张图片

DatagramSocket 构造方法:

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


DatagramSocket 方法:

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

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发送的数据报时,需要传入 SocketAddress ,该对象可以使用InetSocketAddress 来创建。

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

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

示例一:实现一个回显服务(EchoServer)=》一发一收(发什么就返回什么)

UDP服务端代码实现:

package network;

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

// 站在服务器的角度:
// 1. 源 IP: 服务器程序本机的 IP
// 2. 源端口: 服务器绑定的端口 (此处手动指定了 9090)
// 3. 目的 IP: 包含在收到的数据报中. (客户端的IP)
// 4. 目的端口: 包含在收到的数据报中. (客户端的端口)
// 5. 协议类型: UDP
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. 根据请求计算响应(由于咱们这是一个回显服务, 2 省略)
            String response = process(request);
            // 3. 把响应写回到客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n",
                    requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
        }
    }

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

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

代码解析:

网络编程套接字(Socket编程)_第5张图片

网络编程套接字(Socket编程)_第6张图片

网络编程套接字(Socket编程)_第7张图片

网络编程套接字(Socket编程)_第8张图片

UDP客户端代码实现:

package network;

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();
    }
}

代码解析:


网络编程套接字(Socket编程)_第9张图片

网络编程套接字(Socket编程)_第10张图片

网络编程套接字(Socket编程)_第11张图片

示例二:写一个简单的翻译程序(使用UDP协议)

网络编程套接字(Socket编程)_第12张图片

UDP服务器代码:
 

package network;

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

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

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

        // 简单构造几个词
        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("fuck", "卧槽");
        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();
    }
}

注意:如果想要启动多个客户端,可以设置一下IDEA

网络编程套接字(Socket编程)_第13张图片

2.TCP流套接字编程:

  • ServerSocket API
  • Socket API

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() 返回此套接字的输出流


网络编程套接字(Socket编程)_第14张图片

示例一:写一个回显服务(使用TCP)

TCP服务端代码:

package network;

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 TcpEchoServer {
    // listen => 英文原意 监听~~
    // 但是在 Java socket 中是体现不出来 "监听" 的含义的~~
    // 之所以这么叫, 其实是 操作系统原生的 API 里有一个操作叫做 listen
    // private ServerSocket listenSocket = null;
    private ServerSocket serverSocket = null;

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

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
            // accept 就是在 "接电话", 接电话的前提是, 有人给你打了~~, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
            // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
            // 进一步讲, serverSocket 就干了一件事, 接电话~~
            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();
    }
}

代码解析:

网络编程套接字(Socket编程)_第15张图片

网络编程套接字(Socket编程)_第16张图片

网络编程套接字(Socket编程)_第17张图片

 网络编程套接字(Socket编程)_第18张图片

TCP客户端代码:

package network;

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();
    }
}

代码解析:

网络编程套接字(Socket编程)_第19张图片

 问题:

网络编程套接字(Socket编程)_第20张图片

网络编程套接字(Socket编程)_第21张图片

 网络编程套接字(Socket编程)_第22张图片

方法一:使用多线程

网络编程套接字(Socket编程)_第23张图片

 方法二:使用线程池:
网络编程套接字(Socket编程)_第24张图片

示例二:完成一个简单的翻译程序(使用TCP)

同样的也只需要修改TCP服务端即可

TCP服务端代码:

package network;

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

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

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

        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("pig", "小猪");
        dict.put("fuck", "卧槽");
    }

    @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();
    }
}

代码解析:

网络编程套接字(Socket编程)_第25张图片

网络编程套接字(Socket编程)_第26张图片

你可能感兴趣的:(Java,Web,大数据,websocket,java,网络)