Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序连段都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等对的,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
套接字之间的连接过程可以分为四个步骤:服务器监听,客户端请求服务器,服务器确认,客户端确认,进行通信。
(1)服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是出于等待连接的状态,实时监控网络状态。
(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
(3)服务器端连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端。
(4)客户端连接确认:一旦客户端确认了此描述,连接就建立好了。双方开始进行通信。而服务器端套接字继续处于监听状态,继续接受其他客户端套接字的连接请求。
网络编程的基本模型是Client/Server模型,也就是两个进程直接进行相互通信,其中服务器提供配置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务器端监听的地址发起连接请求,通过三次握手建立连接,如果连接成功,则双方即可进行通信(网络套接字socket)。
package com.yhz.moudle.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
*传统模式jdk1.5版本之前
* @author yanghz
* @create 2018-11-23 22:20
**/
public class Server {
private static final int PORT=8888;
public static void main(String[] args) {
ServerSocket server=null;
try {
server=new ServerSocket(PORT);
Socket socket=server.accept();
System.out.println("服务启动!");
new Thread(new ServerHandler(socket)).start();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(server!=null){
server.close();
}
server=null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.yhz.moudle.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 客户端
*
* @author yanghz
* @create 2018-11-23 22:21
**/
public class Client {
private static final String HOST="127.0.0.1";
private static final int PORT=8888;
public static void main(String[] args) {
BufferedReader in=null;
PrintWriter out=null;
Socket socket=null;
try{
socket=new Socket(HOST,PORT);
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out=new PrintWriter(socket.getOutputStream(),true);
out.println("你好服务器!");
String body=in.readLine();
System.out.println(body);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.yhz.moudle.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务端线程处理任务
*
* @author yanghz
* @create 2018-11-23 22:22
**/
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket){
this.socket=socket;
}
@Override
public void run() {
BufferedReader in=null;
PrintWriter out=null;
try {
in=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out=new PrintWriter(this.socket.getOutputStream(),true);
String body=null;
while(true){
body=in.readLine();
if(body==null){
break;
}
System.out.println("服务端接收到的消息:"+body);
out.println("响应客户端信息:你好客户端!");
}
}catch(Exception e){
}finally{
try {
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
采用线程池和任务队列可以实现一种伪异步的IO通信框架。
其实就是讲客户端的socket封装成一个task任务(实现Runnable接口的类)然后投递到线程池中去,配置相应的队列进行实现。
示例:
Server.java
package com.yhz.moudle.bio2;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
/**
* 服务端
*传统模式jdk1.5版本之前
* @author yanghz
* @create 2018-11-23 22:20
**/
public class Server {
private static final int PORT=8888;
public static void main(String[] args) {
ServerSocket server=null;
try {
server=new ServerSocket(PORT);
System.out.println("server start!");
HandlerThreadPool pool= new HandlerThreadPool(50,1000);
while(true){
Socket socket=server.accept();
pool.execute(new ServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(server!=null){
server.close();
}
server=null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
HandlerThreadPool.java
package com.yhz.moudle.bio2;
import java.util.concurrent.*;
/**
* 自定义线程池
*
* @author yanghz
* @create 2018-11-23 22:50
**/
public class HandlerThreadPool {
private ExecutorService executor;
public HandlerThreadPool(int maxPoolSize, int queueSize) {
this.executor=new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueSize));
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
ServerHandler.java
package com.yhz.moudle.bio2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务端线程处理任务
*
* @author yanghz
* @create 2018-11-23 22:22
**/
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket){
this.socket=socket;
}
@Override
public void run() {
BufferedReader in=null;
PrintWriter out=null;
try {
in=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out=new PrintWriter(this.socket.getOutputStream(),true);
String body=null;
while(true){
body=in.readLine();
if(body==null){
break;
}
System.out.println("服务端接收到的消息:"+body);
out.println("响应客户端信息:你好客户端!");
}
}catch(Exception e){
}finally{
try {
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Client.java
package com.yhz.moudle.bio2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 客户端
*
* @author yanghz
* @create 2018-11-23 22:21
**/
public class Client {
private static final String HOST="127.0.0.1";
private static final int PORT=8888;
public static void main(String[] args) {
BufferedReader in=null;
PrintWriter out=null;
Socket socket=null;
try{
socket=new Socket(HOST,PORT);
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out=new PrintWriter(socket.getOutputStream(),true);
out.println("你好服务器!");
String body=in.readLine();
System.out.println(body);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
IO(BIO)和NIO的区别:其本质就是阻塞和非阻塞的区别。
阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。
非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。
BIO为同步阻塞形式,NIO为同步非阻塞形式。NIO并没有实现异步,在JDK1.7之后,升级了NIO库包,支持异步非阻塞通信模型即NIO2.0(AIO)
同步和异步:同步和异步一般是面向操作系统与应用程序对IO操作的层面上来区别的。
同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,知道数据准备就绪:或者采用轮循的策略实时检查数据的就绪状态,如果就绪则获取数据。
异步时,则所有的IO读写操作交给操作系统处理,与我们的应用程序没有直接关系,我们程序不需要关心IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据即可。
同步说的是的server服务器端的执行方式
阻塞说的是具体的技术,接受数据的方式、状态(io、nio)
NIO的几个概念:Buffer(缓冲区)、Channel(管道、通道)Selector(选择器、多路复用器)
Buffer是一个对象,它包含一些要写入或者要读取的数据。在NIO类库中加入Buffer对象,体现了 新库与原IO的一个重要的区别。在面向流的IO中,可以将数据直接写入或读取到Stream对象中。在NIO库中,所有的数据都是用缓冲区处理的(读写),缓冲区实质上是一个数组,通常它是一个直接数组(ByteBuffer),也可以使用其他类型的数组。这个数组为缓冲区提供了数据的访问读写操作属性,如位置、容量、上限等概念。
Buffer类型:我们最常用的就是ByteBuffer,实际上每一种java基本类型都对应了一种缓存区(除了Boolean类型)
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
TestBuffer.java
package com.yhz.moudle.nio.test;
import java.nio.IntBuffer;
/**
* @Auther: yanghz
* @Date: 2018/11/26 11:05
* @Description:
*/
public class TestBuffer {
public static void main(String[] args) {
//1、基本操作
//创建指定长度的缓冲区
IntBuffer buf=IntBuffer.allocate(10);
buf.put(23);//position位置:0->1
buf.put(52);//position位置:1->2
buf.put(21);//position位置:2->3
buf.flip();//把位置复位为0,也就是position位置:3 - > 0
System.out.println("使用flip复位:"+buf);
System.out.println("容量为:"+buf.capacity());
System.out.println("限制为:"+buf.limit());
System.out.println("获取下标为1的元素:"+buf.get(1));
System.out.println("get(index)方法,position位置不改变:"+buf);
buf.put(1,11);
System.out.println("put(index,change)方法,position位置不改变:"+buf);
for (int i = 0; i < buf.limit(); i++) {
//调用get方法会使缓冲区位置(position)向后递增一位
System.out.println(buf.get()+"\t");
}
System.out.println("buf对象遍历之后为:"+buf);
System.out.println("----------------------------------------");
//2、wrap方法使用
// wrap方法会包裹一个数组: 一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉。
// 并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化。
int[] arr=new int[]{2,4,6};
IntBuffer buf1=IntBuffer.wrap(arr);
System.out.println(buf1);
IntBuffer buf2=IntBuffer.wrap(arr,0,2);
//这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓存区的元素长度
System.out.println(buf2);
System.out.println("-----------------------------------------");
//3、其他方法
IntBuffer buf4=IntBuffer.allocate(10);
int[] arr1=new int[]{9,5,6,34,5};
buf4.put(arr1);
System.out.println("buf4=="+buf4);
//一种复制方法
IntBuffer buf5=buf4.duplicate();
System.out.println("buf5=="+buf5);
//设置buf4的位置属性
buf4.position(2);
System.out.println("buf4.position(2)后:"+buf4);
//可读数据表示position到capacity的值
System.out.println("可读数据为:"+buf4.remaining());
int[] arr2=new int[buf4.remaining()];
//将缓冲区数据放入arr2数组中去
buf4.get(arr2);
for (int i = 0; i <arr2.length ; i++) {
System.out.print(Integer.toString(arr2[i])+",");
}
}
}
输出结果:
使用flip复位:java.nio.HeapIntBuffer[pos=0 lim=3 cap=10]
容量为:10
限制为:3
获取下标为1的元素:52
get(index)方法,position位置不改变:java.nio.HeapIntBuffer[pos=0 lim=3 cap=10]
put(index,change)方法,position位置不改变:java.nio.HeapIntBuffer[pos=0 lim=3 cap=10]
23
11
21
buf对象遍历之后为:java.nio.HeapIntBuffer[pos=3 lim=3 cap=10]
----------------------------------------
java.nio.HeapIntBuffer[pos=0 lim=3 cap=3]
java.nio.HeapIntBuffer[pos=0 lim=2 cap=3]
-----------------------------------------
buf4==java.nio.HeapIntBuffer[pos=5 lim=10 cap=10]
buf5==java.nio.HeapIntBuffer[pos=5 lim=10 cap=10]
buf4.position(2)后:java.nio.HeapIntBuffer[pos=2 lim=10 cap=10]
可读数据为:8
6,34,5,0,0,0,0,0,
NIO Demo
Server.java
package com.yhz.moudle.nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @Auther: yanghz
* @Date: 2018/11/26 13:35
* @Description:服务端
*/
public class Server implements Runnable {
//1 多路复用器(管理所有的通道)
private Selector selector;
//建立读缓冲区
private ByteBuffer readBuf=ByteBuffer.allocate(1024);
//写缓冲区
private ByteBuffer writeBuf=ByteBuffer.allocate(1024);
public Server(int port){
try{
//1.打开多路复用器
this.selector=Selector.open();
//2.打开服务器通道
ServerSocketChannel ssc=ServerSocketChannel.open();
//3.设置服务器通道为非租塞模式
ssc.configureBlocking(false);
//4.绑定地址
ssc.bind(new InetSocketAddress(port));
//5.把服务器通道注册到多路复用器上,并且监听阻塞事件
ssc.register(this.selector,SelectionKey.OP_ACCEPT);
System.out.println("Server start ,port:"+port);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try{
//1.必须要让多路复用器开始监听
this.selector.select();
//2.返回多路复用器已经选择的结果集
Iterator<SelectionKey> keys=this.selector.selectedKeys().iterator();
//3.进行遍历
while (keys.hasNext()){
//4.获取一个选择的元素
SelectionKey key=keys.next();
//5.直接从容器中移出就可以了
keys.remove();
//6.如果是有效的
if(key.isValid()){
//7.如果为阻塞状态
if(key.isAcceptable()){
this.accept(key);
}
//8.如果为可读状态
if(key.isReadable()){
this.read(key);
}
//9.写数据
if(key.isWritable()){
// this.write(key);
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
//
public void accept(SelectionKey key){
try{
//1.获取服务通道
ServerSocketChannel ssc= (ServerSocketChannel) key.channel();
//2.执行阻塞方法
SocketChannel sc=ssc.accept();
//3.设置阻塞模式
sc.configureBlocking(false);
//4.注册到多路复用器上,并设置读取表示
sc.register(this.selector,SelectionKey.OP_READ);
}catch (Exception e){
e.printStackTrace();
}
}
public void read(SelectionKey key){
try{
//1.清空缓存区旧的数据
this.readBuf.clear();
//2.获取之前注册的socket通道对象
SocketChannel sc= (SocketChannel) key.channel();
//3.读取数据
int count=sc.read(this.readBuf);
//4.如果没有数据
if(count==-1){
key.channel().close();
key.cancel();
return;
}
//5.有数据则进行读取 读取之前需要进行复位方法(把position和limit进行复位)
this.readBuf.flip();
//6.根据缓冲区的数据长度创建相应大小的byte数组,接受缓冲区的数据
byte[] bytes=new byte[this.readBuf.remaining()];
//7.接收缓冲区数据
this.readBuf.get(bytes);
//8.打印结果
String body=new String(bytes);
System.out.println("Server:"+body);
//9.可以写回给客户端数据
}catch (Exception e){
e.printStackTrace();
}
}
public void write(SelectionKey key){
try {
ServerSocketChannel ssc= (ServerSocketChannel) key.channel();
ssc.register(this.selector,SelectionKey.OP_WRITE);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new Server(1024)).start();
}
}
Client.java
package com.yhz.moudle.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Auther: yanghz
* @Date: 2018/11/26 15:16
* @Description:
*/
public class Client {
//需要一个Selector
public static void main(String[] args) {
//创建连接的地址
InetSocketAddress address=new InetSocketAddress("127.0.0.1",1024);
//声明连接通道
SocketChannel sc=null;
//建立缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
try{
//打开通道
sc=SocketChannel.open();
//进行连接
sc.connect(address);
while(true){
//定义一个字节数组,然后使用系统录入功能:
byte[] bytes=new byte[1024];
System.in.read(bytes);
//把数据放到缓冲区中
buf.put(bytes);
//对缓冲区进行复位
buf.flip();
//写出数据
sc.write(buf);
//清空缓冲区数据
buf.clear();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(sc!=null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
AIO编程,在NIO基础之上引入了异步通道的概念,并提供了异步文件和异步套接字通道的实现,从而在真正意义上实现了异步非阻塞,NIO只是非阻塞而并非异步。而AIO它不需要通过多路复用器对注册的通道进行轮循操作即可实现异步读写,从而简化了NIO编程模型。也可以称之为NIO2.0,这种模式才真正的属于我们异步非阻塞的模型。
AsynchronousServerSocketChannel
AsynchronousSocketChannel
AIO HelloWorld
Server.java
package com.yhz.moudle.aio;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Auther: yanghz
* @Date: 2018/11/27 09:48
* @Description:
*/
public class Server {
//线程池
private ExecutorService executorService;
//线程组
private AsynchronousChannelGroup threadGroup;
//服务器通道
public AsynchronousServerSocketChannel assc;
public Server(int port){
try{
//1.创建一个缓存池
executorService=Executors.newCachedThreadPool();
//2.创建线程组
threadGroup=AsynchronousChannelGroup.withCachedThreadPool(executorService,1);
//3.创建服务通道
assc=AsynchronousServerSocketChannel.open(threadGroup);
//4.绑定端口
assc.bind(new InetSocketAddress(port));
System.out.println("server start port:"+port);
//5.进行阻塞
assc.accept(this,new ServerCompletionHandler());
//6.一直阻塞,不让服务器停止
Thread.sleep(Integer.MAX_VALUE);
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server=new Server(1024);
}
}
Client.java
package com.yhz.moudle.aio;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
/**
* @Auther: yanghz
* @Date: 2018/11/27 09:48
* @Description:
*/
public class Client implements Runnable{
private AsynchronousSocketChannel asc;
public Client() throws IOException {
asc=AsynchronousSocketChannel.open();
}
@Override
public void run() {
while (true){
}
}
public void connect(){
asc.connect(new InetSocketAddress("127.0.0.1",1024));
}
public void write(String request){
try {
asc.write(ByteBuffer.wrap(request.getBytes())).get();
read();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public void read(){
ByteBuffer buf=ByteBuffer.allocate(1024);
try {
asc.read(buf).get();
buf.flip();
byte[] respByte=new byte[buf.remaining()];
buf.get(respByte);
System.out.println(new String(respByte,"utf-8").trim());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Client c1=new Client();
c1.connect();
Client c2=new Client();
c2.connect();
Client c3=new Client();
c3.connect();
new Thread(c1,"c1").start();
new Thread(c2,"c2").start();
new Thread(c3,"c3").start();
Thread.sleep(1000);
c1.write("c1 aaa");
c2.write("c2 bbbb");
c3.write("c3 ccccc");
}
}
SercompletionHandler.java
package com.yhz.moudle.aio;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* @Auther: yanghz
* @Date: 2018/11/27 09:49
* @Description:
*/
public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel,Server> {
@Override
public void completed(AsynchronousSocketChannel asc, Server attachment) {
//当有下一个客户端接入的时候 直接调用Server的accept方法,这样反复执行下去,保证多个客户端都可以阻塞
attachment.assc.accept(attachment,this);
read(asc);
}
@Override
public void failed(Throwable exc, Server attachment) {
exc.printStackTrace();
}
private void read(final AsynchronousSocketChannel asc){
//读取数据
ByteBuffer buf=ByteBuffer.allocate(1024);
asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer resultSize, ByteBuffer attachment) {
//进行读取之后,重置标识位
attachment.flip();
//获得读取的字节数
System.out.println("Server -> 收到客户端的数据长度为:"+resultSize);
//获取读取的数据
String resultData=new String(attachment.array()).trim();
System.out.println("Server -> 收到客户端的数据信息为:"+resultData);
String response="服务器响应,收到了客户端发来的数据:"+resultData;
write(asc,response);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
private void write(AsynchronousSocketChannel asc,String response){
try{
ByteBuffer buf=ByteBuffer.allocate(1024);
buf.put(response.getBytes());
buf.flip();
asc.write(buf).get();
}catch(Exception e){
e.printStackTrace();;
}
}
}