Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lysu's Java Socket notes

1,804 views

Published on

learning socket

Published in: Technology, Education
  • Be the first to comment

Lysu's Java Socket notes

  1. 1. Socket 2011 春
  2. 2. Start !
  3. 3. Socket <ul><li>应用程序需要和其他应用程序或模块进行交互 </li></ul><ul><li>Java 为应用程序通过网络交互提供了抽象 API- Socket </li></ul>
  4. 4. 基本概念 <ul><li>网络:网络是由多态计算机和他们之间的信道组成。计算机可以被分为 Host 和 Router , Host 是网络的用户,而 Router 可连起多个信道。我们通常所说的信道是指两个 Host 间的通路 ( 以太网或 WIFI) </li></ul><ul><li>网络包: packet 包括供网络使用的控制信息和供程序使用的用户数据信息 </li></ul><ul><li>协议:规定了交互中信息的规格 </li></ul><ul><li>TCP/IP 协议套件包括了 IP (标记路由)和在此之上的 TCP UDP 协议 </li></ul><ul><li>TCP UDP 都通过端口( port )来区分同一 host 上的不通“地址”,都是端到端传输协议 </li></ul><ul><li>TCP 是面向连接的可靠的字节流通道,在交互前两应用得先握手建立连接再交互, TCP 能检测传输过程中出现的故障并试图恢复,所以程序中不用操心 </li></ul><ul><li>UDP 也是面向连接的,但不可靠,所以程序中可能需要处理丢包重复的错误 </li></ul>
  5. 5. 地址 <ul><li>TCP/IP 通过 IP 地址和端口来区分应用 </li></ul><ul><li>Port 是 16 位的二进制数字从 1~65535 ( 0 保留 - 代表任意端口) </li></ul><ul><li>特殊的 IP </li></ul><ul><ul><li>loopback 地址( echo ) 127.0.0.1 </li></ul></ul><ul><ul><li>私有地址 以 10 或 192.168 开头 </li></ul></ul><ul><ul><li>link-local 地址 以 192.254 开头 router 对此地址不做传递 </li></ul></ul><ul><ul><li>多播地址 地址的第一个数字为 224 或 239 </li></ul></ul>
  6. 6. 名字 <ul><li>TCP/IP 不认识名字,只认识地址 </li></ul><ul><li>用名字更容易记住,也使地址的使用更透明 </li></ul><ul><li>用名字的时系统做了额外的操作…用 TCP 或 UDP 向 DNS 请求解析名字为地址 </li></ul><ul><li>其次系统也有本地的名字地址映射库可以供本机使用 </li></ul>
  7. 7. 客户端和服务器端 <ul><li>客户端向服务器发起请求,服务器对客户端相应。。。 </li></ul><ul><li>客户端必须知道服务器地址(包括端口),服务器在接受客户端请求后有能力知道发起请求客户端的地址信息 </li></ul><ul><li>有一些常用的 Port 端口 </li></ul>
  8. 8. Socket 是神马 <ul><li>Socket 是对网络的抽象,应用程序可以通过他来发送和读取信息 </li></ul><ul><li>流式 Socket 使用 TCP 来建立可靠通信服务 </li></ul><ul><li>报文 Socket 使用 UDP 来提供高效通信,最大消息可以到 65500 字节 </li></ul><ul><li>注意同一应用也可使用同一个 Socket </li></ul>
  9. 9. Socket Taste
  10. 10. Network Address <ul><li>NetworkInterface : </li></ul><ul><ul><li>通过其 getNetworkInterfaces 静态工厂方法取得网络接口对象 </li></ul></ul><ul><ul><li>也可以通过 InetAddress 来取得所属的 NetworkInterface </li></ul></ul><ul><ul><li>通过其可以取得接口名字和接口对应的网络地址列表 </li></ul></ul><ul><ul><li>可以通过实列方法来取得网关信息 </li></ul></ul><ul><li>InetAddress </li></ul><ul><ul><li>代表具体的地址对象,所以可以取得所代表的名字和地址 </li></ul></ul><ul><ul><li>通过其静态方法可以取得对应名字的地址, vice versa ! </li></ul></ul><ul><ul><li>有可以用来检测地址属性的方法( loopback 等),也可以检查地址是否可达 </li></ul></ul>
  11. 11. TCP Socket <ul><li>Java 提供了两个 Socket 类: Socket 和 ServerSocket </li></ul><ul><li>Socket 代表 TCP 连接的一端, TCP 连接是双向的,并且各端都通过 IP 和 Port 来标识。 </li></ul><ul><li>在初始阶段客户端发出 Socket 请求, Server 在监听到请求后,同样取得 Socket 连接(所以服务器要两个都使用) </li></ul>
  12. 12. TCP 客户端 <ul><li>TCP 客户端通过连接到一个在持续等待的 Server 上进行工作 </li></ul><ul><li>三个步骤: </li></ul><ul><ul><li>建立 Socket 对象,此对象针对要请求的远程地址和端口 </li></ul></ul><ul><ul><li>通过 socket 上的流对象进行交互 </li></ul></ul><ul><ul><li>关闭 socket 用 close 方法 </li></ul></ul><ul><li>Key operation : </li></ul><ul><ul><li>Socket 操作的数据得先转换为 byte 数组 </li></ul></ul><ul><ul><li>通过指定地址和端口来创建 socket 对象 </li></ul></ul><ul><ul><ul><li>Socket 构造函数有一个没有参数的方法使用时得显示用 connect 来连接 </li></ul></ul></ul><ul><ul><li>发送使用 socket 的 outputstream 的 write 方法 </li></ul></ul><ul><ul><ul><li>如果对方 server 没有 read 会阻塞 </li></ul></ul></ul><ul><ul><ul><li>Close 后自动 flush </li></ul></ul></ul><ul><ul><li>可以取得 socket 的 socketAddress(InetSocketAddress) </li></ul></ul><ul><ul><li>使用 read 方法从流中读取数据 </li></ul></ul><ul><ul><ul><li>3 个参数: </li></ul></ul></ul><ul><ul><ul><li>用来接收的数组 </li></ul></ul></ul><ul><ul><ul><li>用来接受到的数据放在数组中的起始下标 </li></ul></ul></ul><ul><ul><ul><li>一次读取时在数组中放置的最大字节数 </li></ul></ul></ul><ul><ul><ul><li>会导致阻塞直到接收到服务器回发的数据,一直循环直到末尾 -1 或对方关闭连接 </li></ul></ul></ul><ul><ul><li>注意:不要认为一个 read 接收一个 write !请用循环将 read 包起! </li></ul></ul>
  13. 13. TCP 服务器 <ul><li>服务器建立端点等待客户端请求并开启新的 Socket 进行处理 </li></ul><ul><li>两个步骤: </li></ul><ul><ul><li>通过指定端口号来建立 ServerSocket ,此对象持续监听此端口 </li></ul></ul><ul><ul><li>重复: </li></ul></ul><ul><ul><ul><li>调用 accept 方法接收连接建立请求并返回 socket 对象 </li></ul></ul></ul><ul><ul><ul><li>同样通过输入输出流和客户端交互 </li></ul></ul></ul><ul><ul><ul><li>完成后关闭 socket 连接 </li></ul></ul></ul><ul><li>Key operation : </li></ul><ul><ul><li>还是要注意 read 不一定可以完全读取到指定数组大小的数据,所以如果要接着处理读取的到的信息,请使用 read 的返回参数作为接下来工作的大小! </li></ul></ul><ul><ul><li>在指定端口时使用 0 端口会随机选取任意端口 </li></ul></ul><ul><ul><li>同样有个没有参数的 serversocket 构造方法,同样等待之后用 bind 方法显示绑定之 </li></ul></ul><ul><ul><li>同样可以通过 ss 对象获得本地端口信息 </li></ul></ul>
  14. 14. UDP Socket <ul><li>UDP 是在 IP 之上的简单封装 </li></ul><ul><li>UDP 可以检测一些影响传输的数据错误,并且抛弃这些错误 </li></ul><ul><li>UDP 不用发起连接,只用将数据封装为报文,并标上目的地址和端口发送即可! </li></ul><ul><li>UDP 不保证所发送的数据可以到达目的地,所以效率更高,但使用应做排错准备 </li></ul><ul><li>为啥使用 UDP 而不是 TCP 呢? </li></ul><ul><ul><li>使用 UDP 效率更高,不用建立连接 </li></ul></ul><ul><ul><li>UDP 提供简单高效的基础来实现其他形式的传输 </li></ul></ul>
  15. 15. 报文 <ul><li>Java 通过 DatagramPacket 来提供报文抽象,封装报文数据 </li></ul><ul><ul><li>包括地址,发送时是目的地址,接受时是源地址 </li></ul></ul><ul><ul><li>构造函数有地址参数的用于发送,没地址参数的多用于接收报文用,当然在报文构建出来后任然有机会可以改变和获取报文地址信息 </li></ul></ul><ul><ul><li>报文中有可以获取设置发送读取长度的方法,同样也可设置报文内容 </li></ul></ul><ul><li>封装好的报文通过 DatagramSocket 的 send 和 receive 方法来发送和接收数据 </li></ul>
  16. 16. UDP 客户端 <ul><li>同样也大致通过三步来建立 UDP 客户端: </li></ul><ul><ul><li>通过指定地址和端口来建立报文 DatagramPacket </li></ul></ul><ul><ul><li>通过 DatagramSocket 的 send 和 receive 方法来发送和接收数据 </li></ul></ul><ul><ul><li>DatagramSocket 也同样可以指定端口和地址(通过构造函数或 connect 方法),因此也可以从中获取地址、端口、超时等信息 </li></ul></ul><ul><ul><li>关闭 DatagramSocket 释放资源 </li></ul></ul><ul><li>UDP 不保证数据传输的可靠性,所以调用 receive 后可能永远阻塞,因此一般要用 setSoTimeout 来设置超时防止不必要的永远阻塞 </li></ul>
  17. 17. UDP 服务器 <ul><li>UDP 是不用连接,所以不需建立连接的步骤 </li></ul><ul><li>也是三步 </li></ul><ul><ul><li>通过指定端口建立 DatagramSocket ,或可选的本机地址,这时已做好等待请求的准备 </li></ul></ul><ul><ul><li>通过调用 DatagramSocket 上的 receive 方法接收报文 </li></ul></ul><ul><ul><li>如有需要可以再次通过 send 的方法回发数据 </li></ul></ul>
  18. 18. UDP 收发区别观 <ul><li>与 TCP 不同, UDP 保留信息边界,一次 receive 基本对应一次 send </li></ul><ul><li>TCP 提供错误检测,所以在发送前有 buffer ,而 UDP 没有,所以直接发送数据到底层信道 </li></ul><ul><li>在接收到数据后都是通过一个队列来排队取包, TCP 能保证包完整,而 UDP 只是取指定长度的包,其他包都丢弃,所以最好把包的长度设为待发送包中的最大长度 </li></ul><ul><li>注意 GetData 返回整个包,而不管 offset 和 length </li></ul>
  19. 19. 细看收发数据
  20. 20. 规则 <ul><li>人们使用网络来交换数据,就必然要求对数据字节进行编码方便协同 </li></ul><ul><li>传送的信息不可避免的需要协定信息序列字节表现方式 --- 也就是编码 </li></ul><ul><li>协议规定了数据格式和意义 </li></ul><ul><li>对于特定应用使用的协议叫做应用协议 </li></ul><ul><li>TCP 要求使用 8bit 的块来收发数据也就是 1byte </li></ul>
  21. 21. 信息编码 <ul><li>Socket 只接受 java 中的一种类型 ---bytes 作为发送接收处理类型对于其他? </li></ul>
  22. 22. 整数编码 <ul><li>我们已经知道 socket 可以直接发送 byte ( 1-255 ),但对于大于 1 字节的整数类型,我们同样可以通过编码后来发送,但是收发者首先得达成一定写协定: </li></ul><ul><ul><li>类型的大小 </li></ul></ul><ul><ul><li>字节的发送顺序(从低到高,从高到低) </li></ul></ul><ul><ul><li>发送的数字是否有符号( java 中的数字全是有符号—存储:补码) </li></ul></ul><ul><li>可以自己实现编码并发送 位移 + 掩码 </li></ul><ul><li>Java 也提供了内建的编码方式,可以串起 ByteArrayOutputStream 和 DataOutputStream 来达到目的 </li></ul>
  23. 23. 字符串和文本编码 <ul><li>众所周知使用 string 的 getBytes 方法可以得到字符串的字节数组表现 </li></ul><ul><li>字符串内部由 char 数组表示,而 char 又被表现为整数,符号和数字的映射就是字符编码集 </li></ul><ul><li>对于用较小数字表示的字符编码集可以方便的使用 byte 来直接传输,但对于较大数字的编码,收发方需相互协调,协定编码方案 </li></ul><ul><li>编码后的字符和编码方案一起被称为 charset </li></ul><ul><li>可以在 getBytes 方法调用时加入字符名字作为参数来指定字符集 </li></ul>
  24. 24. 组合 I/O Stream <ul><li>For Java’s Input and Output stream, we can simply chain them to get the more powerful stream </li></ul>
  25. 25. 定界解析 <ul><li>经 TCP socket 发送的数据得在接收端进行解析,解析时接收者得知道数据流的具体起始位置(不能多收也不能少收) </li></ul><ul><li>一般有两种解决办法 </li></ul><ul><ul><li>使用定界符,再发送数据主体后发送一个双方协商好了的特殊符号进行识别( read -1 ) [ 注意发送信息里这个符号要检测 ] </li></ul></ul><ul><ul><li>使用显式的边界数据,在发送信息前,先发送数据的长度数据,再发送主体信息 </li></ul></ul><ul><li>UDP socket 可以自己定界,所以不需要此步骤哈 </li></ul>
  26. 26. Nice Example
  27. 27. Socket 进阶主题 开始发力
  28. 28. N 任务 <ul><li>在不使用多任务的情况下,服务器同一时刻只能处理单一客户的请求,并顺序处理,如果要同时处理则要求使用多个线程对个个客户同时分别相应! </li></ul><ul><li>可以使用每客户一线程和线程池的策略 </li></ul>
  29. 29. 复习线程 <ul><li>通过继承 Thread 的子类,通过重写 run 方法并实列化 </li></ul><ul><li>或通过产生实现了 Runnable 的对象,并传递给 Thread 的构造函数 </li></ul><ul><li>使用 start 方法来启动线程 </li></ul><ul><li>如果线程有访问共享资源,则要通过加锁或无锁方法保持正确使用 </li></ul>
  30. 30. 日志 <ul><li>在同时为多用户处理时日志就变得非常重要,现在有多种日志方案 log4j commons-logger slf4j 等…现讨论下 java 内建的 Logger </li></ul><ul><li>通过静态工厂方法 Logger.getLogger 来取得实列 </li></ul><ul><li>Level 对象用来表示消息的重要性,可以使用简便方法来直接使用不同的 level ,此外可以在 log 方法中传入参数 </li></ul><ul><li>可以通过设定不通的 Handler 来让日志记录到不同的地方 </li></ul>
  31. 31. 线程池 <ul><li>在不使用线程池的时候对每次请求的新分配一个线程,这样会导致过多的线程,过多的线程带来的是资源( stack )的消耗,和频繁的上下文切换… </li></ul><ul><li>通过线程池来限制线程总数,当请求时,从线程池中取得线程,用完后放回池中,当池中线程不足时,则先排队等待分配 </li></ul><ul><li>自己分配线程池中的线程数,不太容易… </li></ul><ul><li>通过使用 java.util.concurrent.Executor 来执行,可以使用不通的池策略,并且如果某线程终止,他会新创建线程来在池里补上,并且他们阻塞是在 Executor 中阻塞…不在程序中哈 </li></ul>
  32. 32. 阻塞与超时 <ul><li>在 socket 基本操作中存在各种阻塞,先来看这些阻塞操作,并看看如何减轻阻塞 </li></ul><ul><li>accept(), read(), receive() ,可以通过 setSoTimeout 来设置超时,之后在使用 socket 时可以通过 available 方法来检测是否可用 </li></ul><ul><li>连接服务器,可以不使用 socket 构造函数,换而使用 connect 方法并指定超时 </li></ul><ul><li>write 的阻塞不可避免 - -! </li></ul>
  33. 33. 多个接收者 <ul><li>考虑一对多的情况,相对于自己对每个接收者发送多份数据,依靠网络特性只发送一份数据并由网络分发给接收者 </li></ul><ul><li>两种一对多策略 </li></ul><ul><ul><li>Broadcast 每个宿主机都收到数据的一份拷贝 </li></ul></ul><ul><ul><ul><li>和普通 UDP 一样,但是使用 broadcast 地址 (255.255.255.255) ,消息被发送到同一网段的所有 host 上但不会被 router 转发 </li></ul></ul></ul><ul><ul><li>Multicast 数据发送到多播地址,并且由网络来广播,并可以在发送时指定可以接收的 host </li></ul></ul><ul><ul><ul><li>可以指定接收的范围 </li></ul></ul></ul><ul><ul><ul><li>通过使用 MulticastSocket(DatagramSocket 子类 ) </li></ul></ul></ul><ul><ul><ul><li>在发送方可以通过 setTimeToLive 来限制接收者距离(发送可以不使用 MulticastSocket ) </li></ul></ul></ul><ul><ul><ul><li>接收者一定得用 MulticastSocket ,并 joinGroup 才可以接收 </li></ul></ul></ul><ul><ul><li>注意:只有 UDP 才支持 broadcast 或 multicast , UDP 可以 </li></ul></ul><ul><ul><ul><li>通过单独的指定地址和端口的接入 </li></ul></ul></ul><ul><ul><ul><li>通过 multicast 到指定的组的指定端口接入 </li></ul></ul></ul><ul><ul><ul><li>通过 broadcast 可以到达的地址端口接入 </li></ul></ul></ul>
  34. 34. 控制默认行为 <ul><li>KeepAlive </li></ul><ul><ul><li>开启后会定时发包检测其他节点是否可用,不可用则会自动关闭(默认未开启此功能) </li></ul></ul><ul><li>收发缓冲区大小 </li></ul><ul><ul><li>对 socket 、 datagram 通过 ReceiveBufferSize 来建议分配指定大小缓冲 </li></ul></ul><ul><ul><li>对 serverSocket 的 ReceiveBufferSize 的设定则是和之后 accept 出来的 socket 相关 </li></ul></ul><ul><li>超时 </li></ul><ul><ul><li>通过 setSoTimeout 来设定超时,设为 0 则表示永不超时 </li></ul></ul><ul><li>地址复用 </li></ul><ul><ul><li>Socket, ServerSocket, DatagramSocket 的 ReuseAddress 属性来设置 </li></ul></ul><ul><li>消除缓冲延迟 </li></ul><ul><ul><li>TCP 希望避免小数据包的发送,他会先做缓冲然后一并发送 </li></ul></ul><ul><ul><li>当你要改变这一功能时可以通过 socket 的 TcpNoDelay 属性 </li></ul></ul><ul><li>紧急数据 </li></ul><ul><ul><li>通过 sendUrgentData 方法发送,并在接收者处设置 OOBInline , java 中这个不太常用,可以通过次序排在前面发送来达到 </li></ul></ul><ul><li>延迟关闭 </li></ul><ul><ul><li>通常在关闭 socket 时不管 buffer 里是否有数据,可以通过设置 SoLinger 来改变这一特性,使之等待发送完毕,或直到超时 </li></ul></ul><ul><li>广播授权 </li></ul><ul><ul><li>有的系统需要显示要求授权,显示设置 Broadcast 可以达到 </li></ul></ul><ul><li>交通类型 </li></ul><ul><ul><li>通过设定 TrafficClass 可以然网络据此进行特殊处理,但不一定保证一定能处理 - - </li></ul></ul><ul><li>协议建议 </li></ul><ul><ul><li>可以通过 PerformancePreferences 来建议在特定数据时使用不通协议 </li></ul></ul>
  35. 35. 关闭连接 <ul><li>关闭连接很重要,通过关闭,接收者才能知道发送方已经发送完毕 </li></ul><ul><li>但考虑这样一个情况,当发送者发起请求,服务器在接收请求处理后返回给请求者…如此发送者在发送后就不能立刻关闭 socket ,这样就没法收到! </li></ul><ul><li>因此可以对 socket 的 input 和 output 分开 shutdown !!! </li></ul>
  36. 36. NIO java.nio.channels java.nio
  37. 37. 为啥需要她 <ul><li>每个请求一线程的模式经过使用线程池后仍然在处理量大时力不从心 </li></ul><ul><li>对待处理的请求进行排队或区分优先级 </li></ul><ul><li>在使用多线程时对资源进行同步带来的编程复杂性,和对程序整体的内在性能损耗 </li></ul><ul><li>So ! </li></ul><ul><li>我们需要找到一种方法,从中可以处理多个客户端,并在需要服务时知道哪个客户需要被服务 </li></ul><ul><li>Channel 代表一个可被投票的和 socket 类似的处理目标,他可以被注册上 Selector </li></ul><ul><li>通过 Selector 的 select 方法,就可以知道被注册的那组 channel 哪个需要被服务,需要啥服务 </li></ul><ul><li>还有就是 Buffer ,一个大小固定的用来传递数据的数据容器,通过 buffer 对数据的读写都暴露在程序员面前,并且通过 buffer 可以直接和底层系统进行映射对应(减少了不必要的拷贝移动) </li></ul>
  38. 38. Channels with Buffers <ul><li>Channel 抽象了对设备的连接,对于 TCP 来说,可以使用 ServerSocketChannel 和 SocketChannel </li></ul><ul><li>Channel 一般使用静态工厂方法来创建 </li></ul><ul><li>通过 buffer 来读写数据, buffer 是大小固定、并且内建状态跟踪机制的数据容器, Buffer 是抽象类,具体功能由子类实现,并且他也是通过静态工厂方法 allocate 创建或者通过 wrap 来包装现有数组 </li></ul>
  39. 39. 非阻塞 <ul><li>通过 nio 的 channel ,我们可以达到非阻塞 </li></ul><ul><li>通过 configureBlocking 方法来配置非阻塞 </li></ul><ul><li>配置 channel 非阻塞后,各种之前阻塞的操作会立即返回不在阻塞,但因此在程序中就需要自己检测 channel 的状态,来进行下一步操作 </li></ul>
  40. 40. Selectors <ul><li>Selector 实时的检测所注册的那组 channel 的 IO 状态 </li></ul><ul><li>同样通过 open 静态工厂创建,之后在 channel 上注册,之后调用 select 方法,等待 channel 可以进行 IO 操作或发生超时, select 返回等待处理的 channel 数(没有时返回 0 ) </li></ul>
  41. 41. 细看 Buffer

×