先写一段服务端代码,用于接收客户端信息:
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器启动成功");
while(!serverSocket.isClosed()){
Socket request = serverSocket.accept();//阻塞
System.out.println("收到新连接:"+request.toString());
try{
//接收数据,打印
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
String msg;
while((msg = reader.readLine())!=null){//读操作是阻塞的
if(msg.length() == 0){
break;
}
System.out.println(msg);
}
System.out.println("收到数据,来自:"+request.toString());
}catch (IOException e){
e.printStackTrace();
}finally {
request.close();
}
}
}
}
上面主要需要注意的点就是serverSocket是阻塞接收客户端信息的,也就是说如果客户端的请求还没有过来的话,该线程就会被阻塞。
写完服务端再来写一下客户端,主要功能就是实现向服务端传输信息的功能:
public class BIOClient {
private static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost",8080);
OutputStream out = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
String s = scanner.nextLine();
out.write(s.getBytes(charset));//阻塞,写完成
scanner.close();
socket.close();
}
}
上述代码其实很好理解,用套接字指定了ip和端口,完成了定向的数据传输。
启动服务端和客户端来做一次测试:
首先就是上述服务端代码中的读是阻塞的,如果在还没读取到数据的时候,是无法获取到新的连接的。这个时候就要用到线程池了,通过不同的线程去执行,就可以绕开堵塞的线程而获取新的连接了。
上面是我们自己写的客户端去访问,结果是走的通的,那么我们此处使用浏览器来访问客户端是什么效果呢?做个实验:
显然浏览器是回应无法打开页面,再看服务端:
GET / HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Cookie: Idea-d0b330b7=0a0e8b39-9808-4855-b0fc-718952998b50
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: keep-alive
这是一个http协议的数据包,来详细解一下
可以把Http数据包主要分为四个部分
第一行就是请求行,里面的GET就是请求类型,后面一般会跟资源路径以及HTTP版本。(我的数据包里没有资源路径,因为我没指定)。
请求头部,就是紧接着请求行之后的部分,用来说明服务器要使用的附加信息。
空行,请求头部后面的空行是必须的,请求头部和数据主体之间必须有换行
请求数据也叫主体,可以添加任意的数据。这个例子的请求数据为空。
资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。
资源不可用时,IO请求离开返回,返回数据标识资源不可用。
应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败。
应用发送或接收数据后立刻返回,实际处理是异步返回的。
简述一下上面的差异。阻塞和非阻塞强调的是资源获取的方式。是拿不到资源苦苦等还是拿不到就放弃。而同步和异步强调的则是获取资源后的处理方式,是处理完了再返回结果还是先告诉他一声再自己处理。
上面使用的API中,ServerSocket#accpet、InputStream#read都是阻塞的API。操作系统底层的API中,默认Socket操作都是Blocking型,send/recv等接口都是阻塞的。