前言
socket 为网络通信提供了一种机制,分为TCP 和UDP 两种协议,为了进一步巩固netty,现在先记录下TCP 的主要内容。
1 使用多线程多个连接
server 服务端
package com.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Administrator on 2019/11/14.
*/
public class TcpServer {
public static void main(String[] args) throws IOException {
System.out.println("socket tcp服务器端启动....");
ServerSocket serverSocket = new ServerSocket(8080);
// 等待客户端请求
try {
while (true) {
Socket accept = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
InputStream inputStream = accept.getInputStream();
// 转换成string类型
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String str = new String(buf, 0, len);
System.out.println("服务器接受客户端内容:" + str);
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
serverSocket.close();
}
}
}
client 客户端
package com.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* Created by Administrator on 2019/11/14.
*/
public class TcpClient {
public static void main(String[] args) throws IOException {
System.out.println("socket tcp 客户端启动....");
while (true){
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream outputStream = socket.getOutputStream();
System.out.println("请输入:");
Scanner scanner = new Scanner(System.in);
String d = scanner.nextLine();
outputStream.write(d.getBytes());
socket.close();
}
}
}
启动两个服务,在客户端输入请求
socket tcp服务器端启动…
服务器接受客户端内容:我是赵四
服务器接受客户端内容:我是刘能
socket tcp 客户端启动…
请输入:
我是赵四
请输入:
我是刘能
请输入:
我们发现,每次客户端发送请求,服务端就开启一个线程处理,并且
InputStream inputStream = accept.getInputStream(); 这一行是阻滞的,开启线程后,服务端没有接收到数据会等待,直到接收到客户端发来的数据。
- 传统 scoket网络模型: 是阻滞IO,随后我们再介绍阻滞IO
2 使用线程池技术
客户端不变,改造服务端
package com.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2019/11/14.
*/
public class TcpServer {
public static void main(String[] args) throws IOException {
System.out.println("socket tcp服务器端启动....");
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(8080);
// 等待客户端请求
try {
while (true) {
Socket accept = serverSocket.accept();
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
InputStream inputStream = accept.getInputStream();
// 转换成string类型
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String str = new String(buf, 0, len);
System.out.println("服务器接受客户端内容:" + str);
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
serverSocket.close();
}
}
}
以上我们简单写了下socket 客户端和服务端的通信,由于是阻滞IO,,每次客户端连接后,服务端都需要一个线程来处理请求,如果连接后,客户端什么也不做,这就会造成服务端线程的浪费。这就是 阻滞IO 的缺点。
基于nio socket
上面采用多线程是伪异步的,也会浪费线程,用 非阻滞的IO 模型效率要高,不会造成性能浪费。
server :
package com.socket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* Created by Administrator on 2019/11/14.
*/
public class NioServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器端已经启动....");
// 1.创建通道
ServerSocketChannel sChannel = ServerSocketChannel.open();
// 2.切换读取模式
sChannel.configureBlocking(false);
// 3.绑定连接
sChannel.bind(new InetSocketAddress(8080));
// 4.获取选择器
Selector selector = Selector.open();
// 5.将通道注册到选择器 "并且指定监听接受事件"
sChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6. 轮训式 获取选择 "已经准备就绪"的事件
while (selector.select() > 0) {
// 7.获取当前选择器所有注册的"选择键(已经就绪的监听事件)"
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 8.获取准备就绪的事件
SelectionKey sk = it.next();
// 9.判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
// 10.若"接受就绪",获取客户端连接
SocketChannel socketChannel = sChannel.accept();
// 11.设置阻塞模式
socketChannel.configureBlocking(false);
// 12.将该通道注册到服务器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
// 13.获取当前选择器"就绪" 状态的通道
SocketChannel socketChannel = (SocketChannel) sk.channel();
// 14.读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
it.remove();
}
}
}
}
客户端
package com.socket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner;
/**
* Created by Administrator on 2019/11/14.
*/
public class NioClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端已经启动....");
// 1.创建通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
// 2.切换异步非阻塞
sChannel.configureBlocking(false);
// 3.指定缓冲区大小
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Scanner scanner= new Scanner(System.in);
while (scanner.hasNext()) {
String str=scanner.next();
byteBuffer.put((new Date().toString()+"\n"+str).getBytes());
// 4.切换读取模式
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
}
sChannel.close();
}
}