C++ CGI报“资源访问错误”问题分析

106 篇文章 1 订阅

 

一线上CGI偶发性会报“资源访问错误”,经过分析得出是因为CgiHost没有读取到CGI的任务输出,即CGI运行完成后连HTTP头都没有一点输出。

然而实际上,不可能没有任何输出,因为CGI至少有无条件的HTTP头部分输出,因此问题是输出丢失了。CGI和CgiHost间是通过重定向CGI的标准输出到Unix套接字进行交互的,如果这个套接字坏了,或者CGI的标准输出关闭了,自然不会有任何输出。但经测试,如果是关闭了套接字,报的错误不一样,因此直接排除这个可能。

经调查,该CGI的输出使用是C++库的std::cout,不是printf这套C库I/O,初步推断是std::cout对象的状态值不是goodbit。不是goodbit主要分三种情况:

  1. 人为置为badbit等;
  2. 程序有越界造成状态为badbit或failbit等;
  3. 有类似“char* str=NULL; std::cout << str”的调用出现,造成状态为badbit;
  4. std::cout调用的basic_streambuf<typename _CharT, typename _Traits>::sputn返回的大小不是期望值,造成状态为failbit。

 

从了解来看,第三种和第四种情况概率高出许多:

#include <iosfwd>

namespace std

{

  template<typename _CharT, typename _Traits = char_traits<_CharT> >

  class basic_ostream;

  typedef basic_ostream<char> ostream;

}

#include <iostream>

namespace std

{

  extern istream cin; /// Linked to standard input

  extern ostream cout; /// Linked to standard output

  extern ostream cerr; /// Linked to standard error (unbuffered)

  extern ostream clog; /// Linked to standard error (buffered)

}

#include <ostream>

namespace std

{

  // Partial specializations

  template<class _Traits>

  inline basic_ostream<char, _Traits>&

  operator<<(basic_ostream<char, _Traits>& __out, const char* __s) // 一个全局函数

  {

    if (!__s) // 如果“__s”此时是NULL

      __out.setstate(ios_base::badbit); // 其它一些情况,可查看源码文件ostream.tcc

    else

      __ostream_insert(__out, __s, static_cast<streamsize>(_Traits::length(__s)));

    return __out;

  }

 

  // __ostream_write是一个位于名字空间std中的全局函数

  // __ostream_write被兄弟函数__ostream_insert调用,

  // 而__ostream_insert又被函数全局函数operator<<调用

  template<typename _CharT, typename _Traits>

  inline void

  __ostream_write(basic_ostream<_CharT, _Traits>& __out,

    const _CharT* __s, streamsize __n)

  {

    typedef basic_ostream<_CharT, _Traits> __ostream_type;      

    typedef typename __ostream_type::ios_base __ios_base;

 

    // 类basic_streambuf的成员函数sputn实际调用的是兄弟函数xsputn

    // 而xsputn是一个虚拟函数。

    // 虚类basic_streambuf有两个具体的子类:stringbuffilebuf

    // 对std::cout而言,对应的是filebuf,xsputn底层调用的实际是write函数。

    // 注:

    // file版的xsputn实现在文件fstream.tcc中,

    // string版的xsputn实现在文件streambuf.tcc中。

    const streamsize __put = __out.rdbuf()->sputn(__s, __n);

    if (__put != __n)

      __out.setstate(__ios_base::badbit);

  }

 

  template<typename _CharT, typename _Traits>

  class basic_streambuf { // 虚拟基类

  public:

    virtual streamsize xsputn(const char_type* __s, streamsize __n);

  };

  class basic_stringbuf: public basic_streambuf; // 字符串子类

  class basic_filebuf: public basic_streambuf; // 文件子类

}

typedef _Ios_Iostate iostate;

enum _Ios_Iostate

{

  _S_goodbit  = 0,

  _S_badbit  = 1L << 0, // cout << (char*)NULL

  _S_eofbit = 1L << 1,

  _S_failbit = 1L << 2, // write(buf,n) < n

  _S_ios_iostate_end = 1L << 16

};

 

// 注:

// ios_base是一个普通类,并不是模板类

class ios_base

{

  Iostate _M_streambuf_state;

  basic_streambuf<_CharT, _Traits>* _M_streambuf; // 对于fstream实际为basic_filebuf

};

 

template<typename _CharT, typename _Traits>

class basic_ios : public ios_base

{

};

 

// 这里用到了virtual继承,

// 因为子类basic_iostream会同时继承basic_istream和basic_ostream,

// 出现共享basic_ios,所以需要使用virtual继承

template<typename _CharT, typename _Traits>

class basic_ostream : virtual public basic_ios<_CharT, _Traits>

{

};

(gdb) ptype std::char_traits<char>

type = struct std::char_traits<char> {

  public:

    static void assign(char_type &, const char_type &);

    static char_type * assign(char_type *, std::size_t, char_type);

    static bool eq(const char_type &, const char_type &);

    static bool lt(const char_type &, const char_type &);

    static int_type compare(const char_type *, const char_type *, std::size_t);

    static std::size_t length(const char_type *);

    static const char_type * find(const char_type *, std::size_t, const char_type &);

    static char_type * move(char_type *, const char_type *, std::size_t);

    static char_type * copy(char_type *, const char_type *, std::size_t);

    static char_type to_char_type(const int_type &);

    static int_type to_int_type(const char_type &);

    static bool eq_int_type(const int_type &, const int_type &);

    static int_type eof(void);

    static int_type not_eof(const int_type &);

 

    typedef char char_type;

    typedef int int_type;

}

 

尝试使用GDB实地考察,遗憾的是无法对std::cout进行Debug,所以只有直接修改代码线上验证。但如果有办法取得std::cout的地址,然后根据对象的内存布局,找到成员_M_streambuf_state的内存位置,也是可以查看和动态修改的。

(gdb) p std::cout

No symbol "cout" in namespace "std".

 

(gdb) p &std::cout

No symbol "cout" in namespace "std".

 

找到std::cout在进程中的位置,以便找到其成员_M_streambuf_state在进程中的位置

(gdb) p _ZSt4cout

$1 = -144214548

 

(gdb) call write(1,"1234567890",10)

$6 = -1

 

(gdb) p *__errno_location()

$4 = 5

 

(gdb) whatis std::cout

type = std::ostream

 

(gdb) ptype std::cout

type = std::ostream

 

(gdb) p &std::cout

$1 = (std::ostream *) 0x7ffff7dd8700 <std::cout>

 

(gdb) ptype std::ostream   

type = std::ostream

 

(gdb) p std::cout

$1 = <incomplete type>

 

(gdb) p &std::cout

$3 = (std::ostream *) 0x7ffff7dd8700 <std::cout>

(gdb) p *(std::ostream *)&std::cout

$4 = <incomplete type>

 

(gdb) info symbol 0x7ffff7dd8700

std::cout in section .bss of /lib64/libstdc++.so.6

 

(gdb) info address std::cout

Symbol "std::cout" is static storage at address 0x7ffff7dd8700.

 

(gdb) set solib-search-path /lib64

(gdb) info share 或 info sharedlibrary

From                To                  Syms Read   Shared Object Library

0x00007ffff7ddbb10  0x00007ffff7df6460  Yes (*)     /lib64/ld-linux-x86-64.so.2

0x00007ffff7ef4060  0x00007ffff7ef54f8  Yes         /lib64/libonion.so

0x00007ffff7b2e510  0x00007ffff7b9559a  Yes (*)     /lib64/libstdc++.so.6

0x00007ffff77d6370  0x00007ffff7841278  Yes (*)     /lib64/libm.so.6

0x00007ffff75bdaf0  0x00007ffff75cd298  Yes (*)     /lib64/libgcc_s.so.1

0x00007ffff7216480  0x00007ffff735cc00  Yes (*)     /lib64/libc.so.6

0x00007ffff6ff3e60  0x00007ffff6ff4960  Yes (*)     /lib64/libdl.so.2

(*): Shared library is missing debugging information.

(gdb) sharedlibrary libstdc

Symbols already loaded for /lib64/libstdc++.so.6

(gdb) sharedlibrary libstdc++

Symbols already loaded for /lib64/libstdc++.so.6

# lsof -p 4442

cgihost  4442 root    0r   CHR         1,3      0t0      1028 /dev/null

cgihost  4442 root    1u   CHR         136,2    0t0         5 /dev/pts/2 (deleted)

cgihost  4442 root    2u   CHR         136,2    0t0         5 /dev/pts/2 (deleted)

cgihost  4442 root    3u   REG         253,17   1144245   2097190 /data/cgi/log/httpserver/cgi_test.log

cgihost  4442 root    4u  0000         0,9      0      6842 anon_inode

cgihost  4442 root    7u   REG         253,1    144   1360125 /usr/local/httpserver/bin/map/mem-cgi-bin-test

cgihost  4442 root    8u  unix 0xffff880006933b80      0t0 831550706 socket

# pmap 4442

# objdump -t test|grep _ZSt4cout

0000000000601080 g     O .bss   0000000000000110              _ZSt4cout@@GLIBCXX_3.4

# readelf -s /usr/lib/libstdc++.so.6|grep cout

   Num:    Value  Size Type    Bind   Vis      Ndx Name

   884: 000e3ec0   140 OBJECT  GLOBAL DEFAULT   27 _ZSt4cout@@GLIBCXX_3.4

   902: 000e4140   144 OBJECT  GLOBAL DEFAULT   27 _ZSt5wcout@@GLIBCXX_3.4

# readelf -r /usr/lib/libstdc++.so.6|grep cout

Offset     Info    Type            Sym.Value  Sym. Name

000e2b64  00038606 R_386_GLOB_DAT    000e4140   _ZSt5wcout

000e2ea0  00037406 R_386_GLOB_DAT    000e3ec0   _ZSt4cout

 

如果希望能够Debug标准库中的设施,可在编译时加上开关“-D_GLIBCXX_DEBUG”。

实际上,即使不能GDB中直接得到std::cout的地址,特别是其成员_M_streambuf_state的地址,但可采取变通的办法取得。

先编写如下一小段代码,然后执行这一小段代码,取得成员_M_streambuf_state和std::cout间的偏移Offset,以方便得到std::cout地址时取得成员_M_streambuf_state的地址,以达到修改成员_M_streambuf_state值的目的。

#include <stdio.h>

#include <iostream>

int main()

{

  const int n = (unsigned long)&std::cout._M_streambuf_state - (unsigned long)&std::cout;

  printf("offset: %d\n", n);

  return 0;

}

 

注意,编译之前需要修改标准库头文件ios_base.h,将_M_streambuf_state的类型由protected改成public。不然,应当修改小段代码成如下:

#include <stdio.h>

#include <iostream>

class X: public std::ostream

{

public:

  using std::ostream::_M_streambuf_state;

};

int main()

{

  X x;

  const int n = (unsigned long)&x._M_streambuf_state - (unsigned long)&x;

  printf("offset: %d\n", n);

  return 0;

}

 

当前的多数环境上,offset值一般为40

如果是可执行程序文件,找cout的地址简单多(“0000000000601280”即为cout的内存地址):

# file xxx

xxx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped

# objdump -t xxx|grep cout

0000000000601280 g     O .bss   0000000000000110              _ZSt4cout@@GLIBCXX_3.4

 

这里介绍一种通用的取std::cout地址,及std::cout的_M_streambuf_state成员偏移方式。得到std::cout的地址和成员_M_streambuf_state的偏移,实际也就得到了成员_M_streambuf_state的地址,有了地址就可以控制它了。

首先,编写如下这样的一小段Hook代码:

// hooker.cpp

#include <iostream>

#include <stdio.h>

class Hooker

{

public:

  Hooker()

  {

    // 得到std::cout的成员_M_streambuf_state偏移

    const int offset = (unsigned long)&std::cout._M_streambuf_state - (unsigned long)&std::cout;

    FILE* fp = fopen("/tmp/hooker.txt", "w+");

    fprintf(fp, "&std::cout: %p, offset: %d, _M_streambuf_state: %p\n",

      &std::cout, offset, &std::cout+offset);

    fclose(fp);    

  }

};

static Hooker __hooker;

 

这小段代码的目的是为得到std::cout成员_M_streambuf_state的内存地址,以方便在GDB中控制它。将Hook代码编译成共享库:

g++ -g -o libhooker.so -fPIC -shared hooker.cpp

 

如果是64位系统,需要编译成32位的共享库,则编译命令为:

g++ -m32 -g -o libhooker.so -fPIC -shared hooker.cpp

 

假设将libhooker.so放在/tmp目录下,先使用GDB进入目标进程,假设目标进程ID为2019,则:

# gdb -p 2019

在GDB中加载共享库,这样共享库中的全局变量的构造函数将执行,从而可从文件/tmp/hooker.txt中得到想要的信息

(gdb) call dlopen("/tmp/libhooker.so",258)

(gdb) c

 

假设/tmp/hooker.txt中std::cout的地址为0xf76f9ec0,成员_M_streambuf_state的偏移为24,则在GDB中可如下查看_M_streambuf_state的值:

(gdb) p *(int*)(0xf76f9ec0+24)

 

也可在GDB中执行如下命令确认:

(gdb) info symbol (0xf76f9ec0+24)

 

可在GDB中执行如下命令修改_M_streambuf_state的值:

(gdb) set *(int*)(0xf76f9ec0+24)=1

 

掌握了Hook方法后,查明原因就十分简单了。这个方法有一个前提,目标进程有链接libdl.so,因为它提供了加载共享库函数dlopen的。确认是否链接了libdl.so方法,使用ldd即可:

$ ldd z

        linux-vdso.so.1 =>  (0x00007ffde7822000)

        /$LIB/libonion.so => /lib64/libonion.so (0x00007f4872630000)

        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f487220f000)

        libm.so.6 => /lib64/libm.so.6 (0x00007f4871f0d000)

        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f4871cf7000)

        libc.so.6 => /lib64/libc.so.6 (0x00007f4871933000)

        libdl.so.2 => /lib64/libdl.so.2 (0x00007f487172f000)

        /lib64/ld-linux-x86-64.so.2 (0x00007f4872517000)

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引言 1. 前提 2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 实现方案的隐藏 1.4 方案的重复使用 1.5 继承:重新使用接口 1.5.1 改善基础类 1.5.2 等价和类似关系 1.6 多形对象的互换使用 1.6.1 动态绑定 1.6.2 抽象的基础类和接口 1.7 对象的创建和存在时间 1.7.1 集合与继承器 1.7.2 单根结构 1.7.3 集合库与方便使用集合 1.7.4 清除时的困境:由谁负责清除? 1.8 违例控制:解决错误 1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回 1.13 Java还是C++? 第2章 一切都是对象 2.1 用句柄操纵对象 2.2 必须创建所有对象 2.2.1 保存在什么地方 2.2.2 特殊情况:主类型 2.2.3 Java中的数组 2.3 绝对不要清除对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 新建数据类型:类 2.4.1 字段和方法 2.5 方法、自变量和返回值 2.5.1 自变量列表 2.6 构建Java程序 2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档标记 2.8.7 方法文档标记 2.8.8 文档示例 2.9 编码样式 2.10 总结 2.11 练习 第3章 控制程序流程 3.1 使用Java运算符 3.1.1 优先级 3.1.2 赋值 3.1.3 算术运算符 3.1.4 自动递增和递减 3.1.5 关系运算符 3.1.6 逻辑运算符 3.1.7 按位运算符 3.1.8 移位运算符 3.1.9 三元if-else运算符 3.1.10 逗号运算符 3.1.11 字串运算符+ 3.1.12 运算符常规操作规则 3.1.13 造型运算符 3.1.14 Java没有“sizeof” 3.1.15 复习计算顺序 3.1.16 运算符总结 3.2 执行控制 3.2.1 真和假 3.2.2 if-else 3.2.3 反复 3.2.4 do-while 3.2.5 for 3.2.6 中断和继续 3.2.7 切换 3.3 总结 3.4 练习 第4章 初始化和清除 4.1 由构建器保证初始化 4.2 方法过载 4.2.1 区分过载方法 4.2.2 主类型的过载 4.2.3 返回值过载 4.2.4 默认构建器 4.2.5 this关键字 4.3 清除:收尾和垃圾收集 4.3.1 finalize()用途何在 4.3.2 必须执行清除 4.4 成员初始化 4.4.1 规定初始化 4.4.2 构建器初始化 4.5 数组初始化 4.5.1 多维数组 4.6 总结 4.7 练习 第5章 隐藏实施过程 5.1 包:库单元 5.1.1 创建独一无二的包名 5.1.2 自定义工具库 5.1.3 利用导入改变行为 5.1.4 包的停用 5.2 Java访问指示符 5.2.1 “友好的” 5.2.2 public:接口访问 5.2.3 private:不能接触 5.2.4 protected:“友好的一种” 5.3 接口与实现 5.4 类访问 5.5 总结 5.6 练习 第6章 类再生 6.1 合成的语法 6.2 继承的语法 6.2.1 初始化基础类 6.3 合成与继承的结合 6.3.1 确保正确的清除 6.3.2 名字的隐藏 6.4 到底选择合成还是继承 6.5 protected 6.6 递增开发 6.7 上溯造型 6.7.1 何谓“上溯造型”? 6.8 final关键字 6.8.1 final数据 6.8.2 final方法 6.8.3 final类 6.8.4 final的注意事项 6.9 初始化和类装载 6.9.1 继承初始化 6.10 总结 6.11 练习 第7章 多形性 7.1 上溯造型 7.1.1 为什么要上溯造型 7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6 内部类 7.6.1 内部类和上溯造型 7.6.2 方法和作用域中的内部类 7.6.3 链接到外部类 7.6.4 static内部类 7.6.5 引用外部类对象 7.6.6 从内部类继承 7.6.7 内部类可以覆盖吗? 7.6.8 内部类标识符 7.6.9 为什么要用内部类:控制框架 7.7 构建器和多形性 7.7.1 构建器的调用顺序 7.7.2 继承和finalize() 7.7.3 构建器内部的多形性方法的行为 7.8 通过继承进行设计 7.8.1 纯继承与扩展 7.8.2 下溯造型与运行期类型标识 7.9 总结 7.10 练习 第8章 对象的容纳 8.1 数组 8.1.1 数组和第一类对象 8.1.2 数组的返回 8.2 集合 8.2.1 缺点:类型未知 8.3 枚举器(反复器) 8.4 集合的类型 8.4.1 Vector 8.4.2 BitSet 8.4.3 Stack 8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 实用工具 8.8 总结 8.9 练习 第9章 违例差错控制 9.1 基本违例 9.1.1 违例自变量 9.2 违例的捕获 9.2.1 try块 9.2.2 违例控制器 9.2.3 违例规范 9.2.4 捕获所有违例 9.2.5 重新“掷”出违例 9.3 标准Java违例 9.3.1 RuntimeException的特殊情况 9.4 创建自己的违例 9.5 违例的限制 9.6 用finally清除 9.6.1 用finally做什么 9.6.2 缺点:丢失的违例 9.7 构建器 9.8 违例匹配 9.8.1 违例准则 9.9 总结 9.10 练习 第10章 Java IO系统 10.1 输入和输出 10.1.1 InputStream的类型 10.1.2 OutputStream的类型 10.2 增添属性和有用的接口 10.2.1 通过FilterInputStream从InputStream里读入数据 10.2.2 通过FilterOutputStream向OutputStream里写入数据 10.3 本身的缺陷:RandomAccessFile 10.4 File类 10.4.1 目录列表器 10.4.2 检查与创建目录 10.5 IO流的典型应用 10.5.1 输入流 10.5.2 输出流 10.5.3 快捷文件处理 10.5.4 从标准输入中读取数据 10.5.5 管道数据流 10.6 StreamTokenizer 10.6.1 StringTokenizer 10.7 Java 1.1的IO流 10.7.1 数据的发起与接收 10.7.2 修改数据流的行为 10.7.3 未改变的类 10.7.4 一个例子 10.7.5 重定向标准IO 10.8 压缩 10.8.1 用GZIP进行简单压缩 10.8.2 用Zip进行多文件保存 10.8.3 Java归档(jar)实用程序 10.9 对象串联 10.9.1 寻找类 10.9.2 序列化的控制 10.9.3 利用“持久性” 10.10 总结 10.11 练习 第11章 运行期类型鉴定 11.1 对RTTI的需要 11.1.1 Class对象 11.1.2 造型前的检查 11.2 RTTI语法 11.3 反射:运行期类信息 11.3.1 一个类方法提取器 11.4 总结 11.5 练习 第12章 传递和返回对象 12.1 传递句柄 12.1.1 别名问题 12.2 制作本地副本 12.2.1 按值传递 12.2.2 克隆对象 12.2.3 使类具有克隆能力 12.2.4 成功的克隆 12.2.5 Object.clone()的效果 12.2.6 克隆合成对象 12.2.7 用Vector进行深层复制 12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 13.3 制作按钮 13.4 捕获事件 13.5 文本字段 13.6 文本区域 13.7 标签 13.8 复选框 13.9 单选钮 13.10 下拉列表 13.11 列表框 13.11.1 handleEvent() 13.12 布局的控制 13.12.1 FlowLayout 13.12.2 BorderLayout 13.12.3 GridLayout 13.12.4 CardLayout 13.12.5 GridBagLayout 13.13 action的替用品 13.14 程序片的局限 13.14.1 程序片的优点 13.15 视窗化应用 13.15.1 菜单 13.15.2 对话框 13.16 新型AWT 13.16.1 新的事件模型 13.16.2 事件和接收者类型 13.16.3 用Java 1.1 AWT制作窗口和程序片 13.16.4 再探早期示例 13.16.5 动态绑定事件 13.16.6 将商业逻辑与UI逻辑区分开 13.16.7 推荐编码方法 13.17 Java 1.1 UI API 13.17.1 桌面颜色 13.17.2 打印 13.17.3 剪贴板 13.18 可视编程和Beans 13.18.1 什么是Bean 13.18.2 用Introspector提取BeanInfo 13.18.3 一个更复杂的Bean 13.18.4 Bean的封装 13.18.5 更复杂的Bean支持 13.18.6 Bean更多的知识 13.19 Swing入门 13.19.1 Swing有哪些优点 13.19.2 方便的转换 13.19.3 显示框架 13.19.4 工具提示 13.19.5 边框 13.19.6 按钮 13.19.7 按钮组 13.19.8 图标 13.19.9 菜单 13.19.10 弹出式菜单 13.19.11 列表框和组合框 13.19.12 滑杆和进度指示条 13.19.13 树 13.19.14 表格 13.19.15 卡片式对话框 13.19.16 Swing消息框 13.19.17 Swing更多的知识 13.20 总结 13.21 练习 第14章 多线程 14.1 反应灵敏的用户界面 14.1.1 从线程继承 14.1.2 针对用户界面的多线程 14.1.3 用主类合并线程 14.1.4 制作多个线程 14.1.5 Daemon线程 14.2 共享有限的资源 14.2.1 资源访问错误方法 14.2.2 Java如何共享资源 14.2.3 回顾Java Beans 14.3 堵塞 14.3.1 为何会堵塞 14.3.2 死锁 14.4 优先级 14.4.1 线程组 14.5 回顾runnable 14.5.1 过多的线程 14.6 总结 14.7 练习 第15章 网络编程 15.1 机器的标识 15.1.1 服务器和客户机 15.1.2 端口:机器内独一无二的场所 15.2 套接字 15.2.1 一个简单的服务器和客户机程序 15.3 服务多个客户 15.4 数据 15.5 一个Web应用 15.5.1 服务器应用 15.5.2 NameSender程序片 15.5.3 15.5.3 要注意的问题 15.6 Java与CGI的沟通 15.6.1 CGI数据的编码 15.6.2 程序片 15.6.3 用C++写的CGI程序 15.6.4 POST的概念 15.7 用JDBC连接数据库 15.7.1 获得学习示例 15.7.2 查找程序的GUI版本 15.7.3 JDBC API为何如何复杂 15.8 远程方法 15.8.1 远程接口概念 15.8.2 远程接口的实施 15.8.3 创建根与干 15.8.4 使用远程对象 15.8.5 RMI的替选方案 15.9 总结 15.10 练习 第16章 设计范式 16.1 范式的概念 16.1.1 单子 16.1.2 范式分类 16.2 观察器范式 16.3 模拟垃圾回收站 16.4 改进设计 16.4.1 “制作更多的对象” 16.4.2 用于原型创建的一个范式 16.5 抽象的应用 16.6 多重派遣 16.6.1 实现双重派遣 16.7 访问器范式 16.8 RTTI有害吗 16.9 总结 16.10 练习 第17章 项目 17.1 文字处理 17.1.1 提取代码列表 17.1.2 检查大小写样式 17.2 方法查找工具 17.3 复杂性理论 17.4 总结 17.5 练习 附录A 使用非Java代码 A.1 Java固有接口 A.1.1 调用固有方法 A.1.2 访问JNI函数:JNIEnv自变量 A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 其他J/Direct特性 A.4 本原接口(RNI) A.4.1 RNI总结 A.5 Java/COM集成 A.5.1 COM基础 A.5.2 MS Java/COM集成 A.5.3 用Java设计COM服务器 A.5.4 用Java设计COM客户 A.5.5 ActiveX/Beans集成 A.5.6 固有方法与程序片的注意事项 A.6 CORBA A.6.1 CORBA基础 A.6.2 一个例子 A.6.3 Java程序片和CORBA A.6.4 比较CORBA与RMI A.7 总结 附录B 对比C++和Java 附录C Java编程规则 附录D 性能 D.1 基本方法 D.2 寻找瓶颈 D.2.1 安插自己的测试代码 D.2.2 JDK性能评测[2] D.2.3 特殊工具 D.2.4 性能评测的技巧 D.3 提速方法 D.3.1 常规手段 D.3.2 依赖语言的方法 D.3.3 特殊情况 D.4 参考资源 D.4.1 性能工具 D.4.2 Web站点 D.4.3 文章 D.4.4 Java专业书籍 D.4.5 一般书籍

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值