BIO 有的称之为 basic(基本) IO,有的称之为 block(阻塞) IO,主要应用于文件 IO 和网络 IO, 这里不再说文件 IO, 大家对此都非常熟悉,本次课程主要讲解网络 IO。 在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个 ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每 个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果 没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行, 这就是阻塞式 IO
直接带来的问题就是,假如说有10000个客户端和服务器连接,那么服务器需要创建10000个线程来解决,问题是如果只有10个客户端需要和服务端互动,其它的所有客户端都只连接上,没有和服务器互动,那么浪费了多少资源
socket:socket其实是ip+port的网络进程。底层调用操作系统通知网卡去监听数据
接下来通过一个例子复习回顾一下 BIO 的基本用法(基于 TCP)
public class TCPServer {
public static void main(String[] args) throws Exception {
//1.创建 ServerSocket 对象
ServerSocket ss=new ServerSocket(9999);
while(true){
//2、监听客户端
Socket s = ss.accept();//阻塞
//3.从连接中取出输入流来接收消息
InputStream is = s.getInputStream();
byte[] b = new byte[10];
is.read(b);//阻塞
String clientIP = s.getInetAddress().getHostAddress();
System.out.println(clientIP + "说:" + new String(b).trim());
//4.从连接中取出输出流并回话
OutputStream os = s.getOutputStream();
os.write("没钱".getBytes());
//5.关闭
s.close();
}
}
}
说明:
上述代码编写了一个服务器端程序,绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里
public class TcpClient {
public static void main(String[] args) throws IOException {
while(true){
//1、创建Socket对象
Socket s = new Socket("127.0.0.1", 9999);
//2.从连接中取出输出流并发消息
OutputStream os = s.getOutputStream();
System.out.println("请输入:");
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
os.write(msg.getBytes());
//3.从连接中取出输入流并接收回话
InputStream is = s.getInputStream();
byte[] b = new byte[20];
is.read(b);//阻塞
System.out.println("老板说:" + new String(b).trim());
//4.关闭
s.close();
}
}
}
说明:
上述代码编写了一个客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来 等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。
案例2:
服务端程序
public static void main(String[] args) throws IOException {
byte[] bs = new byte[1024];
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9876));
while(true){
System.out.println("等待一个连接");
Socket socket = serverSocket.accept(); //阻塞
System.out.println("连接成功");
socket.getInputStream().read(bs); //读取到客户端的内容 存到一个bs里面 也会阻塞
System.out.println("收到数据");
String content = new String(bs);
System.out.println(content);
}
}
客户端程序
public class BioClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",9876));
Scanner scanner = new Scanner(System.in);
System.out.println("请输入内容");
while (true){
String next = scanner.next();
socket.getOutputStream().write(next.getBytes());
}
}
}
对于上面的问题我们如果自己实现一个服务器创建单线程来解决,基本思路
//服务器端用单线程来处理并发的问题 NIO
public class NioServer {
public static void main(String[] args) throws Exception {
List<SocketChannel> list = new ArrayList<>();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
//Bio叫serversocket nio叫serversocketchannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9091));
ssc.configureBlocking(false); //非阻塞
while(true){
SocketChannel socketChannel = ssc.accept();
if(socketChannel == null){ //表示没人连接
Thread.sleep(1000);
System.out.println("没人连接");
//循环以前的socket 看看是否有发消息的 就是以前连接上的socket 看看是否有发消息的
for(SocketChannel channel:list){
int k = channel.read(byteBuffer);
if(k!=0){
System.out.println("xxx");
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
}
}
}else{
System.out.println("有人来连接");
//有人来连接 socketChannel 类似 Bio的socket
socketChannel.configureBlocking(false);
list.add(socketChannel);
//得到套接字,循环所有的套接字,通过套接字获取数据
for (SocketChannel channel:list){
int k = channel.read(byteBuffer);
System.out.println();
if(k!=0){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
}
}
}
}
}catch(IOException e){
e.printStackTrace();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
java.nio 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了 一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO)。新增了许多用于处理输入输出 的类,这些类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写,新增 了满足 NIO 的功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CWTYw0l-1687146862792)(assets/image-20210812165946019.png)]
NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同,BIO 以流的方式操作数据,同时需要自己定义缓冲区(数组)来取数据,而 NIO 以Channel(通道)的方式操作数据(块IO),同时,内部不需要我们去维护创建数组缓冲区,内部提供了buffer缓冲区;块 I/O 的效率比流 I/O 高很多。另外,NIO 是非阻塞式的, 这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。
NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统的 BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
1、缓冲区(Buffer):实际上是一个容器,是一个特殊的数组,缓冲区对象内置了一些机 制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道, 但是读取或写入的数据都必须经由 Buffer,如下图所示
在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类,常用的 Buffer 子类有:
ByteBuffer,存储字节数据到缓冲区
ShortBuffer,存储字符串数据到缓冲区
CharBuffer,存储字符数据到缓冲区
IntBuffer,存储整数数据到缓冲区
LongBuffer,存储长整型数据到缓冲区
DoubleBuffer,存储小数到缓冲区
FloatBuffer,存储小数到缓冲区
对于 Java 中的基本数据类型,都有一个 Buffer 类型与之相对应,最常用的自然是 ByteBuffer 类(二进制数据),该类的主要方法如下所示:
2、通道(Channel):类似于 BIO 中的 stream,例如 FileInputStream 对象,用来建立到目标(文件,网络套接字,硬件设备等)的一个连接,但是需要注意:BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,既可以用来进行读操作,也可以用来进行写操作。常用的 Channel 类有:FileChannel、 DatagramChannel、ServerSocketChannel 和 SocketChannel。FileChannel 用于文件的数据读写, DatagramChannel 用于 UDP 的数据读写ServerSocketChannel 和 SocketChannel 用于 TCP 的 数据读写,这些类都继承Channel类
这里我们先讲解 FileChannel 类,该类主要用来对本地文件进行 IO 操作,主要方法如下所示:
public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
public int write(ByteBuffer src) ,通过通道把缓冲区的数据写到通道中
public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道
接下来我们通过 NIO 实现几个案例,分别演示一下本地文件的读、写和复制操作,并和 BIO 做个对比。
1. 往本地文件中写数据
public class A {
public static void main(String[] args) throws Exception {
String str="hello,nio,我是张三";
FileOutputStream fos=new FileOutputStream("basic.txt");
FileChannel fc=fos.getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put(str.getBytes());
buffer.flip();
fc.write(buffer);
fos.close();
}
}
NIO 中的通道Channel是从输出流对象里通过 getChannel 方法获取到的,该通道是双向的,既可以读,又可以写。在往通道里写数据之前,必须通过 put 方法把数据存到 ByteBuffer 中,然后通过通道的 write 方法写数据。在 write 之前,需要调用 flip 方法翻转缓冲区,把内部置到初始位置,这样在接下来写数据时才能把所有数据写到通道里。
2. 从本地文件中读数据
public class A {
public static void main(String[] args) throws Exception {
File file = new File("basic.txt");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
fc.read(buffer);
System.out.print(new String(buffer.array()));
fis.close();
}
}
上述代码从输入流中获得一个通道,然后提供 ByteBuffer 缓冲区,该缓冲区的初始容量和文件的大小一样,最后通过通道的 read 方法把数据读取出来并存储到了 ByteBuffer 中。
3. 复制文件
public class A {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("F:\\上课录屏\\14期\\1初始化项目.mp4");
FileOutputStream fos = new FileOutputStream("d:\\1.mp4");
byte[] b = new byte[1024];
while (true) {
int res = fis.read(b);
if (res == -1) {
break;
}
fos.write(b, 0, res);
}
fis.close();
fos.close();
}
}
上述代码分别通过输入流和输出流实现了文件的复制,这是通过传统的 BIO 实现的,大家都比较熟悉,不再多说
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("d:\\1.mp4");
FileOutputStream fos = new FileOutputStream("d:\\oracle.mp4");
FileChannel sourceCh = fis.getChannel();
FileChannel destCh = fos.getChannel();
destCh.transferFrom(sourceCh, 0, sourceCh.size());
sourceCh.close();
destCh.close();
}
上述代码分别从两个流中得到两个通道,sourceCh 负责读数据,destCh 负责写数据,然后直接调用 transferFrom 方法一步到位实现了文件复制
前面在进行文件 IO 时用到的 FileChannel 并不支持非阻塞操作,学习 NIO 主要就是进行 网络 IO,Java NIO 中的网络通道是非阻塞 IO 的实现,基于事件驱动,非常适用于服务器需要维持大量连接,但是数据交换量不大的情况,例如一些即时通信的服务等等…
在 Java 中编写 Socket 服务器,通常有以下几种模式:
一个客户端连接用一个线程,优点:程序编写简单;缺点:如果连接非常多,分配的线程也会非常多,服务器可能会因为资源耗尽而崩溃(如:BIO方式)。
把每一个客户端连接交给一个拥有固定数量线程的连接池,优点:程序编写相对简单, 可以处理大量的连接。确定:线程的开销非常大,连接如果非常多,排队现象会比较严重(如:BIO方式)。
使用 Java 的 NIO,用非阻塞的 IO 方式处理。这种模式可以用一个线程,处理大量的客户端连接。
该类的常用方法如下所示:
该类的常用方法如下所示:
public static SocketChannel open(),得到一个 SocketChannel 通道
public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式, 取值 false 表示采用非阻塞模式
public boolean connect(SocketAddress remote),连接服务器
public boolean finishConnect(),如果上面的方法连接失败,接下来就要通过该方法完成连接操作
public int write(ByteBuffer src),往通道里写数据
public int read(ByteBuffer dst),从通道里读数据
public final SelectionKey register(Selector sel, int ops, Object att),注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
public final void close(),关闭通道
API 学习完毕后,接下来我们使用 NIO 开发一个入门案例,实现服务器端和客户端之间的数据通信(非阻塞)。
服务端程序:
//网络服务器端程序
public class NIOServer {
public static void main(String[] args) throws Exception{
//1. 得到一个ServerSocketChannel对象 老大
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2. 得到一个Selector对象 间谍
Selector selector=Selector.open();
//3. 绑定一个端口号
serverSocketChannel.bind(new InetSocketAddress(9999));
//4. 设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5. 把ServerSocketChannel对象注册给Selector对象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 干活
while(true){
//6.1 监控客户端
if(selector.select(2000)==0){ //nio非阻塞式的优势 在2s内有没有客户端要连接我 或者是给我发消息
System.out.println("Server:没有客户端搭理我,我就干点别的事");
continue;
}
//6.2 得到SelectionKey,判断通道里的事件
Iterator<SelectionKey> keyIterator=selector.selectedKeys().iterator();
while(keyIterator.hasNext()){
SelectionKey key=keyIterator.next();
if(key.isAcceptable()){ //客户端连接请求事件
System.out.println("OP_ACCEPT");
SocketChannel socketChannel=serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){ //读取客户端数据事件
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=(ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("客户端发来数据:"+new String(buffer.array()));
}
// 6.3 手动从集合中移除当前key,防止重复处理
keyIterator.remove();
}
}
}
}
上面代码用 NIO 实现了一个服务器端程序,能不断接受客户端连接并读取客户端发过来的数据
//网络客户端程序
public class NIOClient {
public static void main(String[] args) throws Exception{
//1. 得到一个网络通道
SocketChannel channel=SocketChannel.open();
//2. 设置非阻塞方式
channel.configureBlocking(false);
//3. 提供服务器端的IP地址和端口号
InetSocketAddress address=new InetSocketAddress("127.0.0.1",9999);
//4. 连接服务器端
if(!channel.connect(address)){
while(!channel.finishConnect()){ //nio作为非阻塞式的优势
System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情");
}
}
//5. 得到一个缓冲区并存入数据
String msg="hello,Server";
ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());
//6. 发送数据
channel.write(writeBuf);
System.in.read();
}
}
上面代码通过 NIO 实现了一个客户端程序,连接上服务器端后发送了一条数据,运行效果如下图所示
刚才我们通过 NIO 实现了一个入门案例,基本了解了 NIO 的工作方式和运行流程,接下来我们用 NIO 实现一个多人聊天案例,具体代码如下所示
服务器端
//聊天程序服务器端
public class ChatServer {
private ServerSocketChannel listenerChannel; //监听通道 老大
private Selector selector;//选择器对象 间谍
private static final int PORT = 9999; //服务器端口
public ChatServer() {
try {
// 1. 得到监听通道 老大
listenerChannel = ServerSocketChannel.open();
// 2. 得到选择器 间谍
selector = Selector.open();
// 3. 绑定端口
listenerChannel.bind(new InetSocketAddress(PORT));
// 4. 设置为非阻塞模式
listenerChannel.configureBlocking(false);
// 5. 将选择器绑定到监听通道并监听accept事件
listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
printInfo("Chat Server is ready.......");
} catch (IOException e) {
e.printStackTrace();
}
}
//6. 干活儿
public void start() throws Exception{
try {
while (true) { //不停监控
if (selector.select(2000) == 0) {
System.out.println("Server:没有客户端找我, 我就干别的事情");
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) { //连接请求事件
SocketChannel sc=listenerChannel.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
System.out.println(sc.getRemoteAddress().toString().substring(1)+"上线了...");
}
if (key.isReadable()) { //读取数据事件
readMsg(key);
}
//一定要把当前key删掉,防止重复处理
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//读取客户端发来的消息并广播出去
public void readMsg(SelectionKey key) throws Exception{
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
int count=channel.read(buffer);
if(count>0){
String msg=new String(buffer.array());
printInfo(msg);
//发广播
broadCast(channel,msg);
}
}
//给所有的客户端发广播
public void broadCast(SocketChannel except,String msg) throws Exception{
System.out.println("服务器发送了广播...");
for(SelectionKey key:selector.keys()){
Channel targetChannel=key.channel();
if(targetChannel instanceof SocketChannel){// && targetChannel!=except){
SocketChannel destChannel=(SocketChannel)targetChannel;
ByteBuffer buffer=ByteBuffer.wrap(msg.getBytes());
destChannel.write(buffer);
}
}
}
private void printInfo(String str) { //往控制台打印消息
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("[" + sdf.format(new Date()) + "] -> " + str);
}
public static void main(String[] args) throws Exception {
new ChatServer().start();;
}
}
上述代码使用 NIO 编写了一个聊天程序的服务器端,可以接受客户端发来的数据,并能把数据广播给所有客户端
客户端程序
//聊天程序客户端
public class ChatClient {
private final String HOST = "127.0.0.1"; //服务器地址
private int PORT = 9999; //服务器端口
private SocketChannel socketChannel; //网络通道
private String userName; //聊天用户名
public ChatClient() throws IOException {
//1. 得到一个网络通道
socketChannel=SocketChannel.open();
//2. 设置非阻塞方式
socketChannel.configureBlocking(false);
//3. 提供服务器端的IP地址和端口号
InetSocketAddress address=new InetSocketAddress(HOST,PORT);
//4. 连接服务器端
if(!socketChannel.connect(address)){
while(!socketChannel.finishConnect()){ //nio作为非阻塞式的优势
System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情");
}
}
//5. 得到客户端IP地址和端口信息,作为聊天用户名使用
userName = socketChannel.getLocalAddress().toString().substring(1);
System.out.println("---------------Client(" + userName + ") is ready---------------");
}
//向服务器端发送数据
public void sendMsg(String msg) throws Exception{
if(msg.equalsIgnoreCase("bye")){
socketChannel.close();
return;
}
msg = userName + "说:"+ msg;
ByteBuffer buffer=ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer);
}
//从服务器端接收数据
public void receiveMsg() throws Exception{
ByteBuffer buffer = ByteBuffer.allocate(1024);
int size=socketChannel.read(buffer);
if(size>0){
String msg=new String(buffer.array());
System.out.println(msg.trim());
}
}
}
上述代码通过 NIO 编写了一个聊天程序的客户端,可以向服务器端发送数据,并能接收服务器广播的数据
测试程序
//启动聊天程序客户端
public class TestChat {
public static void main(String[] args) throws Exception {
ChatClient chatClient=new ChatClient();
new Thread(){
public void run(){
while(true){
try {
chatClient.receiveMsg();
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}.start();
Scanner scanner=new Scanner(System.in);
while (scanner.hasNextLine()){
String msg=scanner.nextLine();
chatClient.sendMsg(msg);
}
}
}
上述代码运行了聊天程序的客户端,并在主线程中发送数据,在另一个线程中不断接收服务器端的广播数据,该代码运行一次就是一个聊天客户端,可以同时运行多个聊天客户端,聊天效果如下图所示
JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,常用到两种模式:Reactor和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的 处理。
AIO 即 NIO2.0,叫做异步不阻塞的 IO。AIO 引入异步通道的概念,采用了 Proactor 模式, 简化了程序编写,一个有效的请求才启动一个线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。目前 AIO 还没有广泛应用,并且也不是本课程的重点内容,这里暂不做讲解。
IO 的方式通常分为几种:同步阻塞的 BIO、同步非阻塞的 NIO、异步非阻塞的 AIO。
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持
举个例子再理解一下:
对比总结 | BIO | NIO | AIO |
---|---|---|---|
IO 方式 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
API 使用难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
服务器端:
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress("127.0.0.1",8899));
System.out.println("等待客户端的连接............");
Socket socket = server.accept(); // 开始监听8899端口,并接收到此套接字的连接 有阻塞
System.out.println("客户端已连接.........");
try {
//2、拿到输入流(客户端发送的信息就在这里) 读取客户端发送的消息的对象
//注意:socket.getInputStream的读数据的方法都是阻塞的 一个一个字符的一直读一直读,否则一直等
//直到客户端输入回车,此时读到数据作为一行 bufferReader.readLine()否则readLine()一直等
InputStream is = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(is,"utf-8");
BufferedReader bufferReader = new BufferedReader(reader);
//向客户端发送数据的几个对象
OutputStream outStream = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outStream,"utf-8");
PrintWriter pw = new PrintWriter(writer,true);
Scanner input = new Scanner(System.in);
String s = "";
while((s = bufferReader.readLine()) != null){ //读取客户端消息每次读一行
System.out.println(s);
//向客户端发消息
System.out.print("我说:");
pw.println("服务器说:"+input.nextLine());
}
socket.shutdownInput();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
socket.close();
server.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
客户端:
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8899);
System.out.println("客户端连接上服务器.......");
try {
//向服务器发消息几个对象。客户端先发
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream, "utf-8");
PrintWriter pw = new PrintWriter(writer, true);
Scanner input = new Scanner(System.in);
//接受服务器消息对象
InputStream inputStream = socket.getInputStream(); //阻塞
InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferReader = new BufferedReader(reader);
while (true) {
System.out.print("我说:");
String message = input.nextLine();
pw.println("客户端说:" + message);
String s = bufferReader.readLine();//有阻塞
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
1、服务器端
public class UdpServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(8888);
while(true){
byte[] bytereceive = new byte[1024];
// 创建接受类型的数据报,数据将存储在bytes中
DatagramPacket packet = new DatagramPacket(bytereceive, 0, bytereceive.length);
//通过套接字接收数据, 此方法是阻塞的,会一直等待消息
socket.receive(packet);
// 解析发送方传递的消息,并打印
String str = new String(packet.getData(),0,packet.getLength());
System.out.println("他说:"+str);
//向客户端发消息
InetAddress clientAddress = packet.getAddress();//获得客户端的IP地址
int port = packet.getPort();//获得对方的端口号
SocketAddress socketAddress = packet.getSocketAddress();//通过数据报得到发送方的套接字地址
Scanner input = new Scanner(System.in);
System.out.print("我说:");
String sendStr = input.next();
sendStr = sendStr +"\t\t"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
byte[] bytesend = sendStr.getBytes();
DatagramPacket sendPacket = new DatagramPacket(bytesend,bytesend.length, socketAddress);
socket.send(sendPacket);
}
}
}
2、客户端
public class UdpClient {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
DatagramSocket socket = new DatagramSocket();
while(true){
System.out.print("我说:");
String str = input.next();
str = str +"\t"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
byte[] byteSend = str.getBytes();
DatagramPacket packet = new DatagramPacket(byteSend,0, byteSend.length,
InetAddress.getByName("127.0.0.1"),8888);//对方的ip
socket.send(packet);
//接受服务器发送的消息
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, bytes.length);
socket.receive(receivePacket);
String backMsg = new String(bytes, 0, receivePacket.getLength());
System.out.println("他说:" + backMsg);
}
}
}