1.Socket
在了解什么是 bio 编程之前,先了解以下什么是 socket
1.1 socket 概念
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。一个Socket是一对IP地址和端口。
Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。你可以这么理解:socket是进程之间用来对话的中间层工具。
1.2 socket 的作用
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口,区分不同应用程序进程间的网络通信和连接。socket(套接字)是对 TCP/IP 或者UDP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。通过这个API程序员在开发网络应用程序的时候,就可以不用关心底层是怎么实现的,减轻开发的难度。
1.3 socket 的实现方式
- socket的实现方式
生成套接字,socket是一个中间层工具,它存在于操作系统的内核。电脑上的进程为了真正通过socket来发送/接受数据,socket必须和一个能够进行数据传送的接口连接起来,再通过那个接口送出数据。这个接口就是端口,而这个连接的过程就叫做绑定。应用层就可以和传输层通过套接字接口(API),区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
假设有两个主机要进行通信 Host A和Host B
Host A上的程序A将一段信息写入Socket中,Socket的内容被Host A的网络管理软件访问,并将这段信息通过Host A的网络接口卡发送到Host B,Host B的网络接口卡接收到这段信息后,传送给Host B的网络管理软件,网络管理软件将这段信息保存在Host B的Socket中,然后程序B才能在Socket中阅读这段信息。
- Socket连接的实现方式
要通过互联网进行通信,至少需要一对套接字,一个运行于客户机端,称之为ClientSocket,另一个运行于服务器端,称之为serverSocket。
基于Tcp协议的Socket通讯类似于B/S架构,面向连接,但不同的是服务器端可以向客户端主动推送消息。
(B/S架构的标准是基于HTTP协议,客户端发送请求,服务器给出响应的一问一答形式进行通信。这就意味着如果客户端不主动地向服务器发送消息,服务器就无法主动给客户端推送消息。当然,现在也有很多方式实现 服务端向客户端主动推送消息了,比如 WebSocket。TCP 通信中,只要建立连接后,服务端,客户端已经没有多大的区别了,所以很容易实现 服务器端向客户端推送消息)
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,要连接的目标是服务器端的套接字,为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求(connect),当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低。至于可靠性,可通过应用层的控制来满足。(不可靠连接)
建立一个套接字(Socket)、绑定服务器端IP地址及端口号–服务器端、通过SendTo()方法向指定主机发送消息(需提供主机IP地址及端口)、通过ReciveFrom()方法接收指定主机发送的消息(需提供主机IP地址及端口)
1.4 Socket与TCP/IP的关系
创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
socket则是对TCP/IP协议的封装和应用(程序员层面上)。也可以说,TPC/IP协议是传输层协议,主要解决数据 如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍:
“我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”
我们平时说的最多的socket是什么呢,实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。 实际上,Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现 只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、 listen、connect、accept、send、read和write等等。网络有一段关于socket和TCP/IP协议关系的说法比较容易理解:
TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”
实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,它要更底层一些.
我自己的理解:Socket 针对TCP或者UDP编程的接口,是一种规范,在我们操作系统中,有一个模块负责管理网络的,这一个模块 有建立 连接,发送消息,接受消息等 功能函数,但我们编写 程序时(应用层),有时候需要用到这个网络模块上的功能函数,我们总不能 直接使用 操作系统中的那些功能函数吧?于是 操作系统给我们提高了 Socket 接口,这是一种规范,在我们使用具体的语言来开发应用时,这具体的语言 为了我们 “实现” 了Socket 接口,我们便可以轻易的操作 网络模块中的功能。调用具体的语言 api
-> 调用 操作系统 中关于 TCP/IP 协议的功能函数
-> 发送连接等
2. 开篇介绍
在 java.net 包是网络编程的基础类库。其中 ServerSocket 和 Socket 是网络编程的基础类 型。ServerSocket 是服务端应用类型。Socket 是建立连接的类型。当连接建立成功后,服务 器和客户端都会有一个 Socket 对象示例,可以通过这个 Socket 对象示例,完成会话的所有 操作。 对于一个完整的网络连接来说,Socket 是平等的,没有服务器客户端分级情况。
什么是同步和异步 :
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发 IO 操作并 等待或者轮询的去查看 IO 操作是否就绪,而异步是指用户进程触发 IO 操作以后便开始做自 己的事情,而当 IO 操作已经完成的时候会得到 IO 完成的通知。
什么是阻塞和非阻塞 :
阻塞和非阻塞是针对于进程在访问数据的时候,根据 IO 操作的就绪状态来采取的不同 方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一 直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
I/O 模型基本说明
- I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
- Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO
- Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销
- Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
- Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
3.Bio 编程
这篇博客重要介绍 bio 编程
3.1 简介
Blocking IO: 同步阻塞的编程方式。
BIO 编程方式通常是在 JDK1.4 版本之前常用的编程方式。编程实现过程为:首先在服务 端启动一个 ServerSocket 来监听网络请求,客户端启动 Socket 发起网络请求,默认情况下 ServerSocket 回建立一个线程来处理此请求,如果服务端没有线程可用,客户端则会阻塞等 待或遭到拒绝。
且建立好的连接,在通讯过程中,是同步的。在并发处理效率上比较低。大致结构如下:
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就 需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可 以通过线程池机制改善
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择.
使用线程池机制改善后的 BIO 模型图如下:
3.2 实例
- 使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯。
- 使用线程池机制改善,可以连接多个客户端.
- 服务器端可以接收客户端发送的数据(telnet 方式即可)。
public class BIOServer {
public static void main(String[] args) throws Exception {
//线程池机制
//思路
//1. 创建一个线程池
//2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务器启动了");
while (true) {
System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
//监听,等待客户端连接
System.out.println("等待连接....");
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端");
//就创建一个线程,与之通讯(单独写一个方法)
newCachedThreadPool.execute(new Runnable() {
public void run() { //我们重写
//可以和客户端通讯
handler(socket);
}
});
}
}
//编写一个handler方法,和客户端通讯
public static void handler(Socket socket) {
try {
System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
byte[] bytes = new byte[1024];
//通过socket 获取输入流
InputStream inputStream = socket.getInputStream();
//循环的读取客户端发送的数据
while (true) {
System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
System.out.println("read....");
int read = inputStream.read(bytes);
if(read != -1) {
System.out.println(new String(bytes, 0, read
)); //输出客户端发送的数据
} else {
break;
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("关闭和client的连接");
try {
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
客户端不需要写,可以使用 Telnet 测试。
telnet 127.0.0.1 6666
send helloworld!
3.3 BIO 问题分析
-
每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write 。
-
当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
-
连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费
PS:下一篇将介绍 NIO 编程