Socket编程 - UDP / TCP

进程已经可以和进程通信(OS已经帮我们做到了,传输层及传输层以下)
我们只需要考虑:
1.如何使用OS提供的机制 ——Socket编程API
2.使用这些机制能用来走什么——不同的业务场景不同

一、前置知识

1. 什么是Socket

套接字,OS提供的一套标准接口(不是)java中的接口,可以让所有应用层的程序使用网络进行数据的交换

就好比你要去取钱,你会操作ATM达到目的,而不是直接操作银行内部的货币系统,这里的ATM所起的作用就是Soket在网络中所起的作用

2. 在应用层的目的

实现a进程和b进程的通信,其中a、b进程可以在同一个结点上,也可以不在同一个结点上。
在这里插入图片描述
这里就会面临一个问题:如果是两个不同的结点(这里的结点可以理解为一台设备)通信,如何让唯一确定通信的两个进程?
答:IP地址可以在互联网上唯一确定一个结点,port端口可以确定唯一的进程。(所谓端口,就是一个在[0,65535]之间的数字)

所以我们如何在互联网中确定一个进程? IP+端口

3. 一个端对端的通信通道需要确定双方

本地ip+本地port+远端ip+远端port(就是确定了两个结点上各自的一个进程)
将上面这个一般称为四元组,四元组标识互联网唯一一个通信通道,五元组=四元组+协议信息

这里提到的ip地址,都是公网IP(不包括127.0.0.1)
为了让IP地址可以唯一标识一台设备:一个IP地址只属于一台设备,一台设备可以有多个IP地址
为了让port端口可以唯一标识一个进程:一个port只能属于一个进程,一个进程可以有多个端口,
(可以理解为银行卡和持卡人的关系,或者是手机号和一个人的关系)

4. 应用层上的C/S架构

Client:请求服务的角色(主动)——可以理解为吃饭的人
Server:提供服务的角色(被动)——那服务器可以理解为饭店

这是一个相对的概念,比如饭店和菜市场这个关系里面,饭店又可以看成是一个Client,菜市场可以看成是Server

在这里插入图片描述

5.站在传输层以上也就是应用层写代码

传输层最常见的有两个协议:

  1. UDP:(User Datagram Protocal)用户报文协议
    (下面是从应用层看这三个特点)
  • 不可靠的
    如果你发送的数据过多或者网络不好的时候,可能会出现
    1)你发送的数据对方是否收到你是不知道的
    2)A是按照1234的顺序发送的,但是B不一定是按照这个顺序接收的
  • 无连接的
    不需要知道Server的任何东西,什么时候准备好了什么时候发,每次发送时带着Server的IP+PORT,不需要提前做准备
  • 面向报文的
    A调用了N次发送,对等的,B就要调用N次接收
  1. TCP:(Transmission Control Protocl)传输控制协议
  • 可靠的
  • 有连接的
  • 面向流的

二. UDP几个应用

1.简单的接收并打印Client发送来的数据

Server.java

package UDP;

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

/**
 * Created with IntelliJ IDEA.
 * 实现一个最简单的打印服务
 * @Created by A TOMATO
 * @Description: UDP 打印服务 Server
 * @Date 2020/4/6 18:33
 */
 
public class Server {
    //Server要提供服务,必须把port公开,否则客户端找不到服务器
    static final int PORT = 9527;
    //规定客户端和服务器端必须遵循同一种编码
    static final String CHARSET = "UTF-8";

    public static void main(String[] args) throws IOException {
        //1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
        try (DatagramSocket  serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
            byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)

            while (true){//一次循环是 请求-响应 的处理过程

                //2.接收对方发来的数据(请求)
                //2.1 必须先创建 DatagramPacket
                DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
                //2.2 接收数据
                serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
                //2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
                String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);

                System.out.println("收到的请求是:" + request);
            }
        }

    }

}

Client.java

package UDP;

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

/**
 * Created with IntelliJ IDEA.
 * 实现一个最简单的打印服务
 * @Created by A TOMATO
 * @Description: UDP 打印服务 Client
 * @Date 2020/4/6 18:57
 */
 
public class Client {
    private static final String serverIP = "127.0.0.1";

    public static void main(String[] args) throws IOException {
        //1.创建UDP Socket, Client的Socket不需要传入端口,OS自动分配,Client地址不重要,Server地址很重要
        try(DatagramSocket clientSocket = new DatagramSocket()) {

            //2.准备好请求
            String request = "你好,我是西红柿!";
            byte[] requestBytes = request.getBytes(Server.CHARSET);//使用指定字符集编码

            //3.发送请求
            //3.1 先准备 DatagramPacket  需要提供Server的IP+PORT
            DatagramPacket packetToServer = new DatagramPacket(
                    requestBytes,0,requestBytes.length,//要发送的数据
                    InetAddress.getByName(serverIP),Server.PORT);//要发送到哪个地址
            //3.2 发送
            clientSocket.send(packetToServer);

        }

    }
}

运行结果:
注意server启动之后不能再次启动
在这里插入图片描述

2.回显服务(在1的基础上重构)

这里的回显服务需要重构的有:
第一步: Client部分:将固定的请求改为用户自己输入
第二步: Server部分:把收到的请求内容,作为响应直接发送回去
第三步: Client部分:读取第二步Server发回的响应并打印

Server.java

package UDP;

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

public class Server {
    //Server要提供服务,必须把port公开,否则客户端找不到服务器
    static final int PORT = 9527;
    //规定客户端和服务器端必须遵循同一种编码
    static final String CHARSET = "UTF-8";

    public static void main(String[] args) throws IOException {
        //1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
        try (DatagramSocket  serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
            byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)

            while (true){//一次循环是 请求-响应 的处理过程

                //2.接收对方发来的数据(请求)
                //2.1 必须先创建 DatagramPacket
                DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
                //2.2 接收数据
                serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
                //2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
                String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);

                System.out.println("收到的请求是:" + request);

                //3.业务处理
                String response = request;//把请求作为响应

                //4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
                //4.1所以这里要获取Client的IP+PORT
                InetAddress clientAddress = packetFromClient.getAddress();
                int clientPort = packetFromClient.getPort();

                byte[] responseBytes = response.getBytes(Server.CHARSET);
                DatagramPacket packetToClient = new DatagramPacket(
                        responseBytes,0,responseBytes.length,//要发送的数据
                        clientAddress,clientPort);//Client的IP+PORT

                serverSocket.send(packetToClient);
            }
        }

    }

}

Client.java

package UDP;

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

public class Client {
    private static final String serverIP = "127.0.0.1";

    public static void main(String[] args) throws IOException {
        //1.创建UDP Socket, Client的Socket不需要传入端口,OS自动分配,Client地址不重要,Server地址很重要
        try(DatagramSocket clientSocket = new DatagramSocket()) {

            Scanner scanner = new Scanner(System.in);

            byte[] receiveBuffer = new byte[8192];//接收Server响应的buffer

            System.out.print("请输入请求: ");
            while (scanner.hasNextLine()){
                //2.准备好请求
                String request = scanner.nextLine();
                byte[] requestBytes = request.getBytes(Server.CHARSET);//使用指定字符集编码
                //3.发送请求
                //3.1 先准备 DatagramPacket  需要提供Server的IP+PORT
                DatagramPacket packetToServer = new DatagramPacket(
                        requestBytes,0,requestBytes.length,//要发送的数据
                        InetAddress.getByName(serverIP),Server.PORT);//要发送到哪个地址
                //3.2 发送
                clientSocket.send(packetToServer);

                //4.接收响应
                //4.1先准备Packet
                DatagramPacket packetFromServer = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
                //4.2接收
                clientSocket.receive(packetFromServer);
                //4.3 转换
                String response = new String(receiveBuffer,0,packetFromServer.getLength(),Server.CHARSET);
                System.out.println("服务器的应答: "+response);

                System.out.print("请输入请求: ");
            }
        }

    }
}

运行结果:
在这里插入图片描述

上面这两种服务都是运行在我PC机器上的(没有公网IP),所以其他人是无法通过互联网访问我的服务器的,这时就需要云服务器了,需要购买

3.简单的字典翻译任务(在2的基础上增加Server的业务,Client不用动)

这里是一个固定的,还没有应用数据库动态实现,先实现一个写死的字典翻译,自己用Map提前准备好一份字典,Map<英文,中文意思>,Map<英文,例句>,这里数据先放到内存中
Server:
就添加了一个Map和业务处理部分的代码

package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Server {
    //Server要提供服务,必须把port公开,否则客户端找不到服务器
    static final int PORT = 9527;
    //规定客户端和服务器端必须遵循同一种编码
    static final String CHARSET = "UTF-8";
    //Map<英文单词,中文含义>
    private static final Map<String,String> meaningMap = new HashMap<>();
    //Map<英文单词,例句>,例句不止一条
    private static final Map<String, List<String>> exampleSentenceMap = new HashMap<>();

    //初始化Map
    static {
        //dog
        meaningMap.put("dog","n. 狗;卑鄙的人;(俚)朋友  vt. 跟踪;尾随");
        exampleSentenceMap.put("dog",new ArrayList<>());
        exampleSentenceMap.get("dog").add("The British are renowned as a nation of dog lovers.");
        //cat
        meaningMap.put("cat","n. 猫,猫科动物");
        exampleSentenceMap.put("cat",new ArrayList<>());
        exampleSentenceMap.get("cat").add("The lion is perhaps the most famous member of the cat family.");
        //galaxy
        meaningMap.put("galaxy","n. [天] 星系;银河系;一群显赫的人 n. 三星智能手机品牌 2015年三星中国公布Galaxy的中文名称为“盖乐世”");
        exampleSentenceMap.put("galaxy",new ArrayList<>());
        exampleSentenceMap.get("galaxy").add("The Galaxy consists of 100 billion stars.");
    }


    public static void main(String[] args) throws IOException {
        //1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
        try (DatagramSocket  serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
            byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)

            while (true){//一次循环是 请求-响应 的处理过程

                //2.接收对方发来的数据(请求)
                //2.1 必须先创建 DatagramPacket
                DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
                //2.2 接收数据
                serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
                //2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
                String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);

                System.out.println("收到的请求是:" + request);

                //3.业务处理
                //3.1根据请求的英文单词,获取含义+例句,需要考虑用户输入的单词不支持
                String response = "Sorry ! No such vocabulary";
                String template = "含义:\r\n%s\r\n示例语句:%s\r\n";
                String exampleTemplate = "%d. %s\r\n";

                if (meaningMap.containsKey(request)){
                    String meaning = meaningMap.get(request);
                    List<String> sentenceList = exampleSentenceMap.get(request);
                    StringBuilder exampleSB = new StringBuilder();
                    for (int i = 0 ;i < sentenceList.size(); i++){
                        exampleSB.append(String.format(exampleTemplate,i+1,sentenceList.get(i)));
                    }
                    response = String.format(template,meaning,exampleSB.toString());
                }

                //4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
                //4.1所以这里要获取Client的IP+PORT
                InetAddress clientAddress = packetFromClient.getAddress();
                int clientPort = packetFromClient.getPort();

                byte[] responseBytes = response.getBytes(Server.CHARSET);
                DatagramPacket packetToClient = new DatagramPacket(
                        responseBytes,0,responseBytes.length,//要发送的数据
                        clientAddress,clientPort);//Client的IP+PORT

                serverSocket.send(packetToClient);
            }
        }

    }

}

运行结果:在这里插入图片描述

4. 引入MySQL+JDBC实现字典翻译

将数据全部保存到MySQL的表中,通过JDBC来获取数据(不一定要使用MySQL,只要是持久化存储就可以,我这里使用的MySQL)
除了使用持久化存储外,还可以把字典存成本地文件,读取文件也可以,由于只是简单的字典应用,在建表那块仅仅为了说明问题就插入了三行数据

首先需要导入jar包并加到依赖箱中去
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
建库和建表.sql
(这里可以直接在IDEA配置好数据库直接运行,不用到cmd去建数据库的,但是我配置的时候出了点问题,所以就拿到cmd建库建表了。配置就是直接点击右侧Database,设置下默认库什么的就ok了,具体过程可以百度)

CREATE DATABASE IF NOT EXISTS Trans0406 CHARSET utf8mb4;
USE Trans0406;

CREATE TABLE IF NOT EXISTS vocabulary (
  en VARCHAR(20) NOT NULL UNIQUE,
  cn VARCHAR(100) NOT NULL
);

INSERT INTO vocabulary (en, cn) VALUES
  ("dog", "狗"),
  ("pig","猪"),
  ("cat", "猫");

在这里插入图片描述
Client不需要做改变
Server.java

package UDP;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Server {
    //Server要提供服务,必须把port公开,否则客户端找不到服务器
    static final int PORT = 9527;
    //规定客户端和服务器端必须遵循同一种编码
    static final String CHARSET = "UTF-8";
    //创建一个连接池并初始化
    private static final DataSource dataSource;
    static {
        MysqlDataSource mysqlDataSource = new MysqlDataSource();

        //通过IP+PORT确定网络上唯一的进程——MySQL
        mysqlDataSource.setServerName("127.0.0.1");
        mysqlDataSource.setPort(3306);

        //MySQL登录需要用到的信息,是为了应用层使用的
        mysqlDataSource.setUser("root");
        mysqlDataSource.setPassword("");
        mysqlDataSource.setDatabaseName("Trans0406");
        mysqlDataSource.setCharacterEncoding("utf8");

        //设置一些MySQL连接需要的属性——不用SSL连接
        mysqlDataSource.setUseSSL(false);

        dataSource = mysqlDataSource;
    }


    public static void main(String[] args) throws IOException {
        //1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
        try (DatagramSocket  serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
            byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)

            while (true){//一次循环是 请求-响应 的处理过程

                //2.接收对方发来的数据(请求)
                //2.1 必须先创建 DatagramPacket
                DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
                //2.2 接收数据
                serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
                //2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
                String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);

                System.out.println("收到的请求是:" + request);

                //3.业务处理
                String response = "No Such Vocabulary,Please try Again ^ - ^";
                try (Connection con = dataSource.getConnection()){
                    String sql = "SELECT cn FROM vocabulary WHERE en = ?";
                    try (PreparedStatement statement = con.prepareStatement(sql)) {
                        statement.setString(1,request);//尤其要注意SQL注入的问题,不要完全信任用户
                        try (ResultSet rs = statement.executeQuery()){
                            if (rs.next()){
                                response = rs.getString("cn");
                            }
                        }

                    }

                } catch (SQLException e) {
                    e.printStackTrace();//打印在Server的
                    response = e.getMessage();//准备发送给客户端的响应
                }


                //4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
                //4.1所以这里要获取Client的IP+PORT
                InetAddress clientAddress = packetFromClient.getAddress();
                int clientPort = packetFromClient.getPort();

                byte[] responseBytes = response.getBytes(Server.CHARSET);
                DatagramPacket packetToClient = new DatagramPacket(
                        responseBytes,0,responseBytes.length,//要发送的数据
                        clientAddress,clientPort);//Client的IP+PORT

                serverSocket.send(packetToClient);
            }
        }

    }

}

运行结果:
在这里插入图片描述
至此就告一段落,在这里只要数据库有新插入的数据,我们的客户端和服务器端都不需要改动,就可以查询新插入的数据,这就是把逻辑和数据进行了一个分离,是一个好处

后面是利用JavaFX加上简单的客户端界面:

在这里插入图片描述
Controller.java

package sample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

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


public class Controller {
    @FXML TextField en;
    @FXML Label cn;
    private DatagramSocket clientSocket;

    {
        try {
            clientSocket = new DatagramSocket();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @FXML public void handleSubmit(ActionEvent event) {
        String request = en.getText();

        byte[] receiveBuffer = new byte[8192];

        try {
            byte[] requestBytes = request.getBytes("UTF-8");

            // 2. 发送请求
            // 2.1 先准备 DatagramPacket
            //     需要指定服务器的 ip + port
            //     创建 发送用的 Packet 的时候,需要提供两类信息
            //          1) 需要发送的数据信息   requestBytes + 0 + requestBytes.length
            //          2) 接收信息的唯一标识(ip + port)
            //              InetAddress.getByName("127.0.0.1") 会把 ip 地址转成 InetAddress 对象
            DatagramPacket packetToServer = new DatagramPacket(
                    requestBytes, 0, requestBytes.length,     // 要发送的数据
                    InetAddress.getByName("127.0.0.1"), 9527    // 要发送到互联网的哪个进程上
            );

            clientSocket.send(packetToServer);

            // 接收响应
            DatagramPacket packetFromServer = new DatagramPacket(
                    receiveBuffer, 0, receiveBuffer.length  // 提供的是用来装数据的容器信息
            );

            clientSocket.receive(packetFromServer);

            String response = new String(
                    receiveBuffer, 0, packetFromServer.getLength(), // 已经取到的数据
                    "UTF-8"
            );

            cn.setText(response);
        } catch (IOException e) {
            e.printStackTrace();;
        }
    }
}

Main.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

将Server和Client跑起来之后运行客户端
运行结果:
在这里插入图片描述
在这里插入图片描述

三. TCP

未完结

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读