美文网首页
JDBC创建sokect连接的过程

JDBC创建sokect连接的过程

作者: 7d972d5e05e8 | 来源:发表于2020-06-29 23:22 被阅读0次

一、创建连接 - TCP连接怎么创建

com.mysql.jdbc.ConnectionImpl#coreConnect方法:

JDBC源码如下:

private void coreConnect(Properties mergedProps) throws SQLException, IOException {
        int newPort = 3306;
        String newHost = "localhost";

        String protocol = mergedProps.getProperty(NonRegisteringDriver.PROTOCOL_PROPERTY_KEY);

        if (protocol != null) {
            // "new" style URL

            if ("tcp".equalsIgnoreCase(protocol)) {
                newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));
                newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));
            } else if ("pipe".equalsIgnoreCase(protocol)) {
                setSocketFactoryClassName(NamedPipeSocketFactory.class.getName());

                String path = mergedProps.getProperty(NonRegisteringDriver.PATH_PROPERTY_KEY);

                if (path != null) {
                    mergedProps.setProperty(NamedPipeSocketFactory.NAMED_PIPE_PROP_NAME, path);
                }
            } else {
                // normalize for all unknown protocols
                newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));
                newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));
            }
        } else {

            String[] parsedHostPortPair = NonRegisteringDriver.parseHostPortPair(this.hostPortPair);
            newHost = parsedHostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];

            newHost = normalizeHost(newHost);

            if (parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
                newPort = parsePortNumber(parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
            }
        }

        this.port = newPort;
        this.host = newHost;

        // reset max-rows to default value
        this.sessionMaxRows = -1;

        this.io = new MysqlIO(newHost, newPort, mergedProps, getSocketFactoryClassName(), getProxy(), getSocketTimeout(),
                this.largeRowSizeThreshold.getValueAsInt());
        this.io.doHandshake(this.user, this.password, this.database);
        if (versionMeetsMinimum(5, 5, 0)) {
            // error messages are returned according to character_set_results which, at this point, is set from the response packet
            this.errorMessageEncoding = this.io.getEncodingForHandshake();
        }
    }

看到

 this.io = new MysqlIO(newHost, newPort, mergedProps, getSocketFactoryClassName(), getProxy(), getSocketTimeout(),
                this.largeRowSizeThreshold.getValueAsInt());

这个MysqlIO的构造方法我们看下:

public MysqlIO(String host, int port, Properties props, String socketFactoryClassName, MySQLConnection conn, int socketTimeout,
            int useBufferRowSizeThreshold) throws IOException, SQLException {
        this.connection = conn;

        if (this.connection.getEnablePacketDebug()) {
            this.packetDebugRingBuffer = new LinkedList<StringBuilder>();
        }
        this.traceProtocol = this.connection.getTraceProtocol();

        this.useAutoSlowLog = this.connection.getAutoSlowLog();

        this.useBufferRowSizeThreshold = useBufferRowSizeThreshold;
        this.useDirectRowUnpack = this.connection.getUseDirectRowUnpack();

        this.logSlowQueries = this.connection.getLogSlowQueries();

        this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
        this.sendPacket = new Buffer(INITIAL_PACKET_SIZE);

        this.port = port;
        this.host = host;

        this.socketFactoryClassName = socketFactoryClassName;
        this.socketFactory = createSocketFactory();
        this.exceptionInterceptor = this.connection.getExceptionInterceptor();

        try {
            this.mysqlConnection = this.socketFactory.connect(this.host, this.port, props);

            if (socketTimeout != 0) {
                try {
                    this.mysqlConnection.setSoTimeout(socketTimeout);
                } catch (Exception ex) {
                    /* Ignore if the platform does not support it */
                }
            }

            this.mysqlConnection = this.socketFactory.beforeHandshake();

            if (this.connection.getUseReadAheadInput()) {
                this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384, this.connection.getTraceProtocol(),
                        this.connection.getLog());
            } else if (this.connection.useUnbufferedInput()) {
                this.mysqlInput = this.mysqlConnection.getInputStream();
            } else {
                this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(), 16384);
            }

            this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(), 16384);

            this.isInteractiveClient = this.connection.getInteractiveClient();
            this.profileSql = this.connection.getProfileSql();
            this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();

            this.needToGrabQueryFromPacket = (this.profileSql || this.logSlowQueries || this.autoGenerateTestcaseScript);

            if (this.connection.getUseNanosForElapsedTime() && TimeUtil.nanoTimeAvailable()) {
                this.useNanosForElapsedTime = true;

                this.queryTimingUnits = Messages.getString("Nanoseconds");
            } else {
                this.queryTimingUnits = Messages.getString("Milliseconds");
            }

            if (this.connection.getLogSlowQueries()) {
                calculateSlowQueryThreshold();
            }
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
        }
    }

核心在this.socketFactory.connect(this.host, this.port, props);方法里面。

这里我们就不说了。我这里最感兴趣的是:tcp连接创建后根据mysql应用层协议,数据库服务器收到socket建立时是要验证用户名和密码的,那么这里是发生在哪里呢?

上面已经解释了传输层的tcp三次握手已经建立,下一步就应该进行mysql应用层报文的验证了。那么我们猜想:客户端和数据库服务器建立tcp连接后,肯定要把用户名和密码组装成mysql协议约定的连接报文,通过tcp连接传递给服务器。我们继续向后看源码验证下。

二、JDBC创建连接后,如何组装mysql请求报文,然后发送给服务器

上面在执行coreConnect方法里面,创建完 new MysqlIO()后,紧接着执行了:

this.io.doHandshake(this.user, this.password, this.database);

我们进去看一下,这个方法太大了,我们只截取感兴趣的代码:

{
                // Passwords can be 16 chars long
                packet = new Buffer(packLength);

                if ((this.clientParam & CLIENT_RESERVED) != 0) {
                    if ((versionMeetsMinimum(4, 1, 1) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {
                        packet.writeLong(this.clientParam);
                        packet.writeLong(this.maxThreeBytes);

                        // charset, JDBC will connect as 'latin1', and use 'SET NAMES' to change to the desired charset after the connection is established.
                        packet.writeByte((byte) 8);

                        // Set of bytes reserved for future use.
                        packet.writeBytesNoNull(new byte[23]);
                    } else {
                        packet.writeLong(this.clientParam);
                        packet.writeLong(this.maxThreeBytes);
                    }
                } else {
                    packet.writeInt((int) this.clientParam);
                    packet.writeLongInt(this.maxThreeBytes);
                }

                // User/Password data
                packet.writeString(user, CODE_PAGE_1252, this.connection);

                if (this.protocolVersion > 9) {
                    packet.writeString(Util.newCrypt(password, this.seed, this.connection.getPasswordCharacterEncoding()), CODE_PAGE_1252, this.connection);
                } else {
                    packet.writeString(Util.oldCrypt(password, this.seed), CODE_PAGE_1252, this.connection);
                }

                if (this.useConnectWithDb) {
                    packet.writeString(database, CODE_PAGE_1252, this.connection);
                }

                send(packet, packet.getPosition());
            }

看到没,packet包就是mysql验证密码,这个过程的报文(请求sql的报文可不长这样)。我们看下这个报文:packet先写8字节的clientParam,在写8字节的maxThreeBytes,在写1字节的8,在写23字节空数据。最后再写String类型的user,然后写加密后的密码,最后在写database。
最后一句send出去,给到服务器。服务器接收到这种类型的报文,就知道要验证用户名和密码,以及数据库名称了。

但是debug乱点的时候,验证执行的是下面代码

        // switch to pluggable authentication if available
        //
        if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
            proceedHandshakeWithPluggableAuthentication(user, password, database, buf);
            return;
        }

proceedHandshakeWithPluggableAuthentication这个方法,进去也是很复杂。但是可以看到,默认验证次数循环100次,如果100次还没得到结果,就报错。
源码省略了,反正只要知道jdbc在创建tcp之后,做了mysql应用层协议的一些事情。所以,创建连接很耗时,不仅要tcp三次握手,还要进行一次验证。3 + 2 = 5次网络来回,所以连接很宝贵,需要用连接池来管理,尽量减少连接的创建。

相关文章

  • JDBC创建sokect连接的过程

    一、创建连接 - TCP连接怎么创建 com.mysql.jdbc.ConnectionImpl#coreConn...

  • JDBC

    1.JDBC连接数据库的步骤1、加载JDBC驱动程序2、提供JDBC连接的URL3、创建数据库的连接4、创建一个S...

  • JDBC连接数据库步骤详解

    1、加载JDBC驱动程序 2、拼接JDBC需要连接的URL 3、创建数据库的连接 4、创建一个Statement ...

  • 撩课-JavaWeb之JDBC概述与连接Mysql

    什么是JDBC JDBC的API 连接Mysql 添加驱动 创建连接 [图片上传中...(image.png-b4...

  • 撩课-JavaWeb之JDBC概述与连接Mysql

    什么是JDBC JDBC的API 连接Mysql 添加驱动 创建连接 [图片上传中...(image.png-b4...

  • JDBC连接创建

    import java.sql JdbcDemo类

  • 第十七章 JDBC

    JDBC 一、JDBC访问数据库步骤 面试回答: ①加载JDBC驱动 ②创建数据库连接(Connection) ③...

  • Java连接到数据库讲解(一)

    完整java开发中JDBC连接数据库代码和步骤 JDBC连接数据库 • 创建一个以JDBC连接数据库的程序,包含7...

  • Java JDBC连接

    JDBC连接数据库 •创建一个以JDBC连接数据库的程序,包含7个步骤: 1、加载JDBC驱动程序: 在连接数据库...

  • JDBC的连接过程

    JDBC连接数据库,创建一个以JDBC连接数据库的程序,包含6个步骤: 1、加载JDBC驱动程序: 在连接数据库之...

网友评论

      本文标题:JDBC创建sokect连接的过程

      本文链接:https://www.haomeiwen.com/subject/gslzfktx.html