博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RPC-Thrift(二)
阅读量:4355 次
发布时间:2019-06-07

本文共 23167 字,大约阅读时间需要 77 分钟。

  TTransport

    TTransport负责数据的传输,先看类结构图。

      

    阻塞Server使用TServerSocket,它封装了ServerSocket实例,ServerSocket实例监听到客户端的请求会创建一个Socket对象,并将该Socket对象封装为一个TSocket对象用于通信。

    非阻塞Server使用TNonblockingServerSocket,它封装了一个ServerSocketChannel实例,ServerSocketChannel实例监听到客户端的请求会创建一个SocketChannel对象,并将该对象封装成一个TNonblockingSocket对象用于之后的通信。当读取完客户端的请求数据后,保存为本地的一个TTransport对象,然后封装为TFramedTransport对象进行处理。

    TTransport

      TTransport是客户端所有Transport的基类。

public abstract class TTransport {  public abstract boolean isOpen();//transport是否打开  public boolean peek() {
//是否还有数据要读,如果transport打开时还有数据要读 return isOpen(); } public abstract void open() throws TTransportException;//打开transport读写数据 public abstract void close();//关闭transport //读取len长度的数据到字节数组buf,off表示开始读的位置,返回实际读取的字节数(不一定为len) public abstract int read(byte[] buf, int off, int len) throws TTransportException; //确保读取len长度的数据到字节数组buf,off表示开始读的位置,通过循环调用read()实现,返回实际读取的字节数(len) public int readAll(byte[] buf, int off, int len) throws TTransportException { int got = 0; int ret = 0; while (got < len) { ret = read(buf, off+got, len-got); if (ret <= 0) { throw new TTransportException( "Cannot read. Remote side has closed. Tried to read " + len + " bytes, but only got " + got + " bytes. (This is often indicative of an internal error on the server side. Please check your server logs.)"); } got += ret; } return got; } //将buf中的全部数据写到output public void write(byte[] buf) throws TTransportException { write(buf, 0, buf.length); } //将buf中off位置开始len长度的数据写到output public abstract void write(byte[] buf, int off, int len) throws TTransportException; //清空transport缓存中的数据 public void flush() throws TTransportException {} //获取本地缓存的数据,没有则返回null public byte[] getBuffer() { return null; } //获取本地缓存的下一个读取位置,没有则返回0 public int getBufferPosition() { return 0; } //获取本地缓存的字节数,没有则返回-1 public int getBytesRemainingInBuffer() { return -1; } //从本地缓存中消费n个字节 public void consumeBuffer(int len) {}}

 

    TIOStreamTransport

      TIOStreamTransport是面向流的TTransport的子类,阻塞式,实现了流的操作。

public class TIOStreamTransport extends TTransport {  private static final Logger LOGGER = LoggerFactory.getLogger(TIOStreamTransport.class.getName());  protected InputStream inputStream_ = null;//输入流  protected OutputStream outputStream_ = null;//输出流  //一波构造函数  protected TIOStreamTransport() {}  public TIOStreamTransport(InputStream is) {    inputStream_ = is;  }  public TIOStreamTransport(OutputStream os) {    outputStream_ = os;  }  public TIOStreamTransport(InputStream is, OutputStream os) {    inputStream_ = is;    outputStream_ = os;  }  //streams必须在构造时已经被打开,so一直返回true  public boolean isOpen() {    return true;  }  //streams必须在构造时已经被打开,不需要这个方法  public void open() throws TTransportException {}  //关闭流  public void close() {    if (inputStream_ != null) {      try {        inputStream_.close();      } catch (IOException iox) {        LOGGER.warn("Error closing input stream.", iox);      }      inputStream_ = null;    }    if (outputStream_ != null) {      try {        outputStream_.close();      } catch (IOException iox) {        LOGGER.warn("Error closing output stream.", iox);      }      outputStream_ = null;    }  }  //将输入流中的指定数据读取到buf中  public int read(byte[] buf, int off, int len) throws TTransportException {    if (inputStream_ == null) {      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot read from null inputStream");    }    int bytesRead;    try {      bytesRead = inputStream_.read(buf, off, len);    } catch (IOException iox) {      throw new TTransportException(TTransportException.UNKNOWN, iox);    }    if (bytesRead < 0) {      throw new TTransportException(TTransportException.END_OF_FILE);    }    return bytesRead;  }  //将buf中的数据写出的输出流outputStream_  public void write(byte[] buf, int off, int len) throws TTransportException {    if (outputStream_ == null) {      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot write to null outputStream");    }    try {      outputStream_.write(buf, off, len);    } catch (IOException iox) {      throw new TTransportException(TTransportException.UNKNOWN, iox);    }  }  //清空输出流  public void flush() throws TTransportException {    if (outputStream_ == null) {      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot flush null outputStream");    }    try {      outputStream_.flush();    } catch (IOException iox) {      throw new TTransportException(TTransportException.UNKNOWN, iox);    }  }}

 

    TSocket

      TSocket类继承自TIOStreamTransport类,实现了对Socket实例的包装。inputStream_和outputStream_通过Socket初始化。

public class TSocket extends TIOStreamTransport {  private static final Logger LOGGER = LoggerFactory.getLogger(TSocket.class.getName());  private Socket socket_ = null;//包装socket_  private String host_  = null;//远程host  private int port_ = 0;//远程port  private int timeout_ = 0;//Socket超时时间  //三个构造函数  public TSocket(Socket socket) throws TTransportException {    socket_ = socket;    try {      socket_.setSoLinger(false, 0);      socket_.setTcpNoDelay(true);    } catch (SocketException sx) {      LOGGER.warn("Could not configure socket.", sx);    }    if (isOpen()) {      try {        //初始化inputStream_和outputStream_        inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);        outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);      } catch (IOException iox) {        close();        throw new TTransportException(TTransportException.NOT_OPEN, iox);      }    }  }  public TSocket(String host, int port) {    this(host, port, 0);  }  public TSocket(String host, int port, int timeout) {    host_ = host;    port_ = port;    timeout_ = timeout;    initSocket();  }  //初始化Socket  private void initSocket() {    socket_ = new Socket();    try {      socket_.setSoLinger(false, 0);      socket_.setTcpNoDelay(true);      socket_.setSoTimeout(timeout_);    } catch (SocketException sx) {      LOGGER.error("Could not configure socket.", sx);    }  }  public void setTimeout(int timeout) {    timeout_ = timeout;    try {      socket_.setSoTimeout(timeout);    } catch (SocketException sx) {      LOGGER.warn("Could not set socket timeout.", sx);    }  }  public Socket getSocket() {    if (socket_ == null) {      initSocket();    }    return socket_;  }  //检查socket_是否连接  public boolean isOpen() {    if (socket_ == null) {      return false;    }    return socket_.isConnected();  }  //打开socket连接,初始化输入流和输出流  public void open() throws TTransportException {    if (isOpen()) {      throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected.");    }    if (host_.length() == 0) {      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");    }    if (port_ <= 0) {      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open without port.");    }    if (socket_ == null) {      initSocket();    }    try {      socket_.connect(new InetSocketAddress(host_, port_), timeout_);      inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);      outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);    } catch (IOException iox) {      close();      throw new TTransportException(TTransportException.NOT_OPEN, iox);    }  }  //关闭socket  public void close() {    super.close();    if (socket_ != null) {      try {        socket_.close();      } catch (IOException iox) {        LOGGER.warn("Could not close socket.", iox);      }      socket_ = null;    }  }}

    TFramedTransport

      TFramedTransport作用是通过message之前的4-byte frame size确保读到的message时完整的,防止发生粘包拆包的问题。

//TFramedTransport作用是通过message之前的4-byte frame size确保读到的message时完整的,防止发生粘包拆包的问题public class TFramedTransport extends TTransport {  protected static final int DEFAULT_MAX_LENGTH = 16384000;//默认的本地缓存最大字节数  private int maxLength_;//本地缓存最大字节数  private TTransport transport_ = null;//封装的transport_,实际通过该对象实现数据的读取与写入  private final TByteArrayOutputStream writeBuffer_ = new TByteArrayOutputStream(1024);//输出BUffer,将字节数组输出  private TMemoryInputTransport readBuffer_ = new TMemoryInputTransport(new byte[0]);//输入buffer,用于数据读取  //工厂类,用于将一个TTransport实例封装为TFramedTransport实例  public static class Factory extends TTransportFactory {    private int maxLength_;    public Factory() {      maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH;    }    public Factory(int maxLength) {      maxLength_ = maxLength;    }    @Override    public TTransport getTransport(TTransport base) {      return new TFramedTransport(base, maxLength_);    }  }  //两个构造函数  public TFramedTransport(TTransport transport, int maxLength) {    transport_ = transport;    maxLength_ = maxLength;  }  public TFramedTransport(TTransport transport) {    transport_ = transport;    maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH;  }  //同transport_的三个方法  public void open() throws TTransportException {    transport_.open();  }  public boolean isOpen() {    return transport_.isOpen();  }  public void close() {    transport_.close();  }  //读数据,一次请求可能调用多次  public int read(byte[] buf, int off, int len) throws TTransportException {    if (readBuffer_ != null) {      //在一次客户端的请求中第一次调用该方法时,肯定返回got<0,就可以进入 readFrame()方法。      int got = readBuffer_.read(buf, off, len);//readBuffer_已读完或字节数为0时 肯定返回got<0      if (got > 0) {        return got;      }    }    readFrame();//从transport_读到本地缓存readBuffer_    return readBuffer_.read(buf, off, len);  }  @Override  public byte[] getBuffer() {    return readBuffer_.getBuffer();  }  @Override  public int getBufferPosition() {    return readBuffer_.getBufferPosition();  }  @Override  public int getBytesRemainingInBuffer() {    return readBuffer_.getBytesRemainingInBuffer();  }  @Override  public void consumeBuffer(int len) {    readBuffer_.consumeBuffer(len);  }  private final byte[] i32buf = new byte[4];  private void readFrame() throws TTransportException {    transport_.readAll(i32buf, 0, 4); //读前4个字节,FrameSize    int size = decodeFrameSize(i32buf);//由于发送数据方对FrameSize进行了编码,通过解码得到消息的大小    //校验FrameSize是否正确    if (size < 0) {      throw new TTransportException("Read a negative frame size (" + size + ")!");    }    if (size > maxLength_) {      throw new TTransportException("Frame size (" + size + ") larger than max length (" + maxLength_ + ")!");    }    byte[] buff = new byte[size];    transport_.readAll(buff, 0, size);//将FrameSize大小的全部数据读到buff    readBuffer_.reset(buff);//重置本地缓存  }  //write是向本地缓存写入数据,写完后,所有的调用方都要对输出流调用flush进行清空,所以下面一定会进入到flush方法,再通过transport_将本地缓存的数据写出去  public void write(byte[] buf, int off, int len) throws TTransportException {    writeBuffer_.write(buf, off, len);  }  @Override  public void flush() throws TTransportException {    byte[] buf = writeBuffer_.get();    int len = writeBuffer_.len();    writeBuffer_.reset();//清空    encodeFrameSize(len, i32buf);//对FrameSize进行编码    transport_.write(i32buf, 0, 4);//先写数据大小FrameSize    transport_.write(buf, 0, len);//在写真实数据    transport_.flush();//清空  }  //以下两个方法是对FrameSize进行编解码,将每个字节高位都位移到低位组成byte数组  public static final void encodeFrameSize(final int frameSize, final byte[] buf) {    buf[0] = (byte)(0xff & (frameSize >> 24));    buf[1] = (byte)(0xff & (frameSize >> 16));    buf[2] = (byte)(0xff & (frameSize >> 8));    buf[3] = (byte)(0xff & (frameSize));  }  public static final int decodeFrameSize(final byte[] buf) {    return       ((buf[0] & 0xff) << 24) |      ((buf[1] & 0xff) << 16) |      ((buf[2] & 0xff) <<  8) |      ((buf[3] & 0xff));  }}

       TFramedTransport用到了TMemoryInputTransport类,TMemoryInputTransport封装了一个字节数组byte[]来做输入流的封装。

public final class TMemoryInputTransport extends TTransport {  private byte[] buf_;//保存数据的字节数组  private int pos_;//可读数据的开始位置  private int endPos_;//可读数据的的结束位置  public TMemoryInputTransport() {  }  public TMemoryInputTransport(byte[] buf) {    reset(buf);  }  public TMemoryInputTransport(byte[] buf, int offset, int length) {    reset(buf, offset, length);  }  //重置buf  public void reset(byte[] buf) {    reset(buf, 0, buf.length);  }  public void reset(byte[] buf, int offset, int length) {    buf_ = buf;    pos_ = offset;    endPos_ = offset + length;  }  public void clear() {    buf_ = null;  }  @Override  public void close() {}  @Override  public boolean isOpen() {    return true;  }  @Override  public void open() throws TTransportException {}  @Override  public int read(byte[] buf, int off, int len) throws TTransportException {    int bytesRemaining = getBytesRemainingInBuffer();//获取剩余可读的数据大小    int amtToRead = (len > bytesRemaining ? bytesRemaining : len);    if (amtToRead > 0) {      System.arraycopy(buf_, pos_, buf, off, amtToRead);//将buf_中pos_开始的amtToRead个字节copy到buf中      consumeBuffer(amtToRead);//将可读数据的开始位置增加amtToRead    }    return amtToRead;  }  //不支持写  @Override  public void write(byte[] buf, int off, int len) throws TTransportException {    throw new UnsupportedOperationException("No writing allowed!");  }  @Override  public byte[] getBuffer() {    return buf_;  }  public int getBufferPosition() {    return pos_;  }  //剩余可读的数据大小  public int getBytesRemainingInBuffer() {    return endPos_ - pos_;  }  //将可读数据的开始位置向后移len  public void consumeBuffer(int len) {    pos_ += len;  }}

 

    TNonblockingTransport    

      TNonblockingTransport是TTransport的非阻塞抽象子类。

public abstract class TNonblockingTransport extends TTransport {  //连接初始化,参考SocketChannel.connect  public abstract boolean startConnect() throws IOException;  //连接完成,SocketChannel.finishConnect()  public abstract boolean finishConnect() throws IOException;  //注册到selector  public abstract SelectionKey registerSelector(Selector selector, int interests) throws IOException;  //读数据到buffer中  public abstract int read(ByteBuffer buffer) throws IOException;  //将buffer中的数据写出  public abstract int write(ByteBuffer buffer) throws IOException;}

 

    TNonblockingSocket

      TNonblockingSocket是TNonblockingTransport的子类,是非阻塞Socket的实现,用于异步客户端。

public class TNonblockingSocket extends TNonblockingTransport {  private static final Logger LOGGER = LoggerFactory.getLogger(TNonblockingSocket.class.getName());  private final SocketAddress socketAddress_;//Host and port info,用于非阻塞连接懒加载  private final SocketChannel socketChannel_;//Java NIO中实现非阻塞读写的核心类  //一波构造函数  public TNonblockingSocket(String host, int port) throws IOException {    this(host, port, 0);  }  public TNonblockingSocket(String host, int port, int timeout) throws IOException {    this(SocketChannel.open(), timeout, new InetSocketAddress(host, port));  }  public TNonblockingSocket(SocketChannel socketChannel) throws IOException {    this(socketChannel, 0, null);    if (!socketChannel.isConnected()) throw new IOException("Socket must already be connected");  }  private TNonblockingSocket(SocketChannel socketChannel, int timeout, SocketAddress socketAddress)      throws IOException {    socketChannel_ = socketChannel;    socketAddress_ = socketAddress;    socketChannel.configureBlocking(false);//设置socketChannel为非阻塞    Socket socket = socketChannel.socket();    socket.setSoLinger(false, 0);    socket.setTcpNoDelay(true);    setTimeout(timeout);  }  //将SocketChannel注册到selector上的感兴趣事件,当感兴趣事件就绪时会收到notify  public SelectionKey registerSelector(Selector selector, int interests) throws IOException {    return socketChannel_.register(selector, interests);  }  public void setTimeout(int timeout) {    try {      socketChannel_.socket().setSoTimeout(timeout);    } catch (SocketException sx) {      LOGGER.warn("Could not set socket timeout.", sx);    }  }  public SocketChannel getSocketChannel() {    return socketChannel_;  }  //检查是否处于连接状态  public boolean isOpen() {    // isConnected() does not return false after close(), but isOpen() does    return socketChannel_.isOpen() && socketChannel_.isConnected();  }  //不要调用该方法,该实现类提供了自己的懒加载方法startConnect()用于打开连接  public void open() throws TTransportException {    throw new RuntimeException("open() is not implemented for TNonblockingSocket");  }  //读数据到buffer中  public int read(ByteBuffer buffer) throws IOException {    return socketChannel_.read(buffer);  }  //读指定数据到buf中,通过socketChannel_实现  public int read(byte[] buf, int off, int len) throws TTransportException {    if ((socketChannel_.validOps() & SelectionKey.OP_READ) != SelectionKey.OP_READ) {      throw new TTransportException(TTransportException.NOT_OPEN,        "Cannot read from write-only socket channel");    }    try {      return socketChannel_.read(ByteBuffer.wrap(buf, off, len));    } catch (IOException iox) {      throw new TTransportException(TTransportException.UNKNOWN, iox);    }  }  //将buffer中的数据写出  public int write(ByteBuffer buffer) throws IOException {    return socketChannel_.write(buffer);  }  //将buffer中的指定数据写出  public void write(byte[] buf, int off, int len) throws TTransportException {    if ((socketChannel_.validOps() & SelectionKey.OP_WRITE) != SelectionKey.OP_WRITE) {      throw new TTransportException(TTransportException.NOT_OPEN,        "Cannot write to write-only socket channel");    }    try {      socketChannel_.write(ByteBuffer.wrap(buf, off, len));    } catch (IOException iox) {      throw new TTransportException(TTransportException.UNKNOWN, iox);    }  }  //socketChannel_不支持  public void flush() throws TTransportException {  }  //关闭socket  public void close() {    try {      socketChannel_.close();    } catch (IOException iox) {      LOGGER.warn("Could not close socket.", iox);    }  }  //开始初始化  public boolean startConnect() throws IOException {    return socketChannel_.connect(socketAddress_);  }  //是否完成连接  public boolean finishConnect() throws IOException {    return socketChannel_.finishConnect();  }}

    TServerTransport

      服务端Transport层共同的父类,主要包括开启监听和接收客户端连接请求两个方法。

public abstract class TServerTransport {  //开启监听客户端连接  public abstract void listen() throws TTransportException;  //连接请求到达后,创建transport实例  public final TTransport accept() throws TTransportException {    TTransport transport = acceptImpl();//具体方法由子类实现    if (transport == null) {      throw new TTransportException("accept() may not return NULL");    }    return transport;  }  //关闭监听  public abstract void close();  protected abstract TTransport acceptImpl() throws TTransportException;  public void interrupt() {}}

 

     TServerSocket

      阻塞服务时使用,TServerSocket对ServerSocket类进行包装,具体实现由TServerSocket完成,代码相对比较简单。

public class TServerSocket extends TServerTransport {  private static final Logger LOGGER = LoggerFactory.getLogger(TServerSocket.class.getName());  private ServerSocket serverSocket_ = null;//基于ServerSocket实现  private int clientTimeout_ = 0;//接收Client连接请求的超时时间  public TServerSocket(ServerSocket serverSocket) {    this(serverSocket, 0);  }  //几个构造函数略过。。。创建TServerSocket实例  public TServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws TTransportException {    clientTimeout_ = clientTimeout;    try {      serverSocket_ = new ServerSocket();      serverSocket_.setReuseAddress(true);      serverSocket_.bind(bindAddr);// Bind to listening port    } catch (IOException ioe) {      serverSocket_ = null;      throw new TTransportException("Could not create ServerSocket on address " + bindAddr.toString() + ".");    }  }  public void listen() throws TTransportException {    if (serverSocket_ != null) {      try {        serverSocket_.setSoTimeout(0);//等待客户端连接的超时时间,0表示无限超时      } catch (SocketException sx) {        LOGGER.error("Could not set socket timeout.", sx);      }    }  }  //accept客户端连接并封装为TSocket  protected TSocket acceptImpl() throws TTransportException {    if (serverSocket_ == null) {      throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");    }    try {      Socket result = serverSocket_.accept();      TSocket result2 = new TSocket(result);      result2.setTimeout(clientTimeout_);      return result2;    } catch (IOException iox) {      throw new TTransportException(iox);    }  }  //关闭serverSocket_  public void close() {    if (serverSocket_ != null) {      try {        serverSocket_.close();      } catch (IOException iox) {        LOGGER.warn("Could not close server socket.", iox);      }      serverSocket_ = null;    }  }  public void interrupt() {    close();  }  public ServerSocket getServerSocket() {    return serverSocket_;  }}

 

    TNonblockingServerTransport

      TNonblockingServerTransport是非阻塞实现的抽象基类,定义了一个向Selector注册对象的抽象方法。

public abstract class TNonblockingServerTransport extends TServerTransport {  public abstract void registerSelector(Selector selector);}

 

    TNonblockingServerSocket

      TNonblockingServerSocket是TNonblockingServerTransport的具体实现,对ServerSocketChannel的封装。

public class TNonblockingServerSocket extends TNonblockingServerTransport {  private static final Logger LOGGER = LoggerFactory.getLogger(TNonblockingServerTransport.class.getName());  private ServerSocketChannel serverSocketChannel = null;//接收Client请求,Java NIO中的Channel  private ServerSocket serverSocket_ = null;//serverSocketChannel中的对象  private int clientTimeout_ = 0;//客户端连接建立超时时间  //几个构造函数略过。。。创建TNonblockingServerSocket实例  public TNonblockingServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws TTransportException {    clientTimeout_ = clientTimeout;    try {      serverSocketChannel = ServerSocketChannel.open();//创建serverSocketChannel实例      serverSocketChannel.configureBlocking(false);//设置为非阻塞      serverSocket_ = serverSocketChannel.socket();//创建serverSocket_实例      serverSocket_.setReuseAddress(true);      serverSocket_.bind(bindAddr);//绑定监听端口    } catch (IOException ioe) {      serverSocket_ = null;      throw new TTransportException("Could not create ServerSocket on address " + bindAddr.toString() + ".");    }  }  //开启监听客户端连接  public void listen() throws TTransportException {    if (serverSocket_ != null) {      try {        serverSocket_.setSoTimeout(0);//等待客户端连接的超时时间,0表示无限超时      } catch (SocketException sx) {        sx.printStackTrace();      }    }  }  //接收客户端连接的具体实现,接收客户端连接并封装为TNonblockingSocket返回  protected TNonblockingSocket acceptImpl() throws TTransportException {    if (serverSocket_ == null) {      throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");    }    try {      SocketChannel socketChannel = serverSocketChannel.accept();      if (socketChannel == null) {        return null;      }      TNonblockingSocket tsocket = new TNonblockingSocket(socketChannel);      tsocket.setTimeout(clientTimeout_);      return tsocket;    } catch (IOException iox) {      throw new TTransportException(iox);    }  }  //向selector注册OP_ACCEPT事件,接收新的连接  public void registerSelector(Selector selector) {    try {      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);    } catch (ClosedChannelException e) {    }  }  //关闭serverSocket_  public void close() {    if (serverSocket_ != null) {      try {        serverSocket_.close();      } catch (IOException iox) {        LOGGER.warn("WARNING: Could not close server socket: " + iox.getMessage());      }      serverSocket_ = null;    }  }  public void interrupt() {    close();  }}

 

   总结

    注意介绍了TTransport的实现方式。

 

 

 

 

 

 参考资料

  

  

 

 

 

    

  

 

转载于:https://www.cnblogs.com/zaizhoumo/p/8206591.html

你可能感兴趣的文章
第十三周学习进度表
查看>>
java JDBC 数据库链接
查看>>
Wcf扩展
查看>>
2018.12.14 codeforces 932E. Team Work(组合数学)
查看>>
浅析C#中的Thread ThreadPool Task和async/await
查看>>
adb command not found / abd' 不是内部或外部命令,也不是可运行的程序 或批处理文件。最简易修改...
查看>>
java操作数据库的事务支持
查看>>
前端学习笔记 - Css初级篇
查看>>
Java8简明学习之新时间日期API
查看>>
The way to Go(7): 变量
查看>>
17秋 软件工程 第六次作业 Beta冲刺 Scrum1
查看>>
Javascript 解析字符串生成 XML DOM 对象。
查看>>
NOI2013 矩阵游戏 【数论】
查看>>
【算法题】找出一个整型数组里两个不同数字
查看>>
iOS开发--网络下载
查看>>
【第七次JAVA课,java语法基础】课件总结
查看>>
一些思维的碎片(一)
查看>>
Centos6 yum安装nginx
查看>>
日志级别简述
查看>>
如何获得运行在跨平台的信息和属性的情况下,文件
查看>>