UDP通信
Java提供了DatagramSocket类作为基于UDP协议的Socket
构造方法
方法 | 说明 |
---|---|
DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
相关方法
方法名 | 说明 |
---|---|
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭数据报套接字 |
void receive(DatagramPacket p) | 从此套接字接受数据报包 |
代码示例
发送端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
//创建socket
DatagramSocket datagramSocket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true){
String line = scanner.nextLine();
if("886".equals(line)){
break;
}
//打包数据包
DatagramPacket datagramPacket = new DatagramPacket(line.getBytes(), line.length(), InetAddress.getByName("127.0.0.1"), 10000);
//发送数据
datagramSocket.send(datagramPacket);
}
//关闭发送端
datagramSocket.close();
}
}
接收端
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;
public class Server {
public static void main(String[] args) throws IOException {
//创建接收端socket
DatagramSocket datagramSocket = new DatagramSocket(10000);
//接收缓存
byte[] bytes=new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
while (true){
//清空数组
Arrays.fill(bytes, (byte) 0);
//接收数据
datagramSocket.receive(datagramPacket);
System.out.println("数据:"+new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
}
//关闭
// datagramSocket.close();
}
}
TCP
java为客户端提供了Socket类,为服务器端提供了ServerSocket类
-
构造方法
方法名 说明 Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号 Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号 -
相关方法
方法名 说明 InputStream getInputStream() 返回此套接字的输入流 OutputStream getOutputStream() 返回此套接字的输出流 -
构造方法
方法名 说明 ServletSocket(int port) 创建绑定到指定端口的服务器套接字 -
相关方法
方法名 说明 Socket accept() 监听要连接到此的套接字并接受它
代码示例
client
注意缓冲流要刷新或关闭对方才能获取到数据
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//创建客户端socke
Socket socket = new Socket("127.0.0.1",10000);
//获取输出流发送数据
OutputStream outputStream = socket.getOutputStream();
//使用转换流解决中文乱码
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
bufferedWriter.write("hello,在干嘛?");
bufferedWriter.flush();
//处理业务时如果没有发送结束标志,服务端会在read时读不到结束标志而一直等待接收数据,可以使用shutdownOutput来写结束标志
socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
//获取输入流读取数据
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
int b=0;
while ((b=bufferedReader.read())!=-1){
System.out.println((char)b);
}
System.out.println("client接收数据");
//关流的的时候会发送一个结束标志
bufferedWriter.close();
bufferedReader.close();
socket.close();
}
}
server
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//创建服务器端socket
ServerSocket serverSocket = new ServerSocket(10000);
//等待连接并返回连接socket
Socket accept = serverSocket.accept();
//获取输入流读取数据
InputStream inputStream = accept.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
int b=0;
while ((b=bufferedReader.read())!=-1){
System.out.println((char)b);
}
//获取输出流回写数据
OutputStream outputStream = accept.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
bufferedWriter.write("hello,你是哪位?");
bufferedWriter.newLine();
bufferedWriter.flush();
System.out.println("server发送数据");
//关闭
bufferedReader.close();
bufferedWriter.close();
accept.close();
serverSocket.close();
}
}
使用线程池实现文件上传功能
client
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
//是本地的流,用来读取本地文件的
BufferedInputStream bisFile = new BufferedInputStream(new FileInputStream("test\\ba1.jpg"));
//网络流,写到服务器
OutputStream os = socket.getOutputStream();
BufferedOutputStream bosSocket = new BufferedOutputStream(os);
int b;
while((b = bisFile.read())!=-1){
bosSocket.write(b);//通过网络写到服务器中
}
bosSocket.flush();
//给服务器一个结束标记,告诉服务器文件已经传输完毕
socket.shutdownOutput();
//接收服务器的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
String line;
while((line = br.readLine()) !=null){
System.out.println(line);
}
//关闭
bisFile.close();
socket.close();
}
}
thread
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class ThreadSocket implements Runnable {
private Socket acceptSocket;
public ThreadSocket(Socket accept) {
this.acceptSocket = accept;
}
@Override
public void run() {
BufferedOutputStream bos = null;
try {
//网络中的流,从客户端读取数据的
BufferedInputStream bisSocket = new BufferedInputStream(acceptSocket.getInputStream());
//本地的IO流,把数据写到本地中,实现永久化存储
bos = new BufferedOutputStream(new FileOutputStream("test\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));
int b;
while((b = bisSocket.read()) !=-1){
bos.write(b);
}
//接收文件成功发送消息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (acceptSocket != null){
try {
acceptSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
server
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
10, //线程池的总数量
60, //临时线程空闲时间
TimeUnit.SECONDS, //临时线程空闲时间的单位
new ArrayBlockingQueue<>(5),//阻塞队列
Executors.defaultThreadFactory(),//创建线程的方式
new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
pool.submit(ts);
}
}
}
NIO
非阻塞IO实现
client
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
//1.打开通道
SocketChannel socketChannel = SocketChannel.open();
//2.指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
//3.写出数据
ByteBuffer byteBuffer = ByteBuffer.wrap("死亡如风,常伴吾身".getBytes("utf-8"));
socketChannel.write(byteBuffer);
// 手动写入结束标记,防止服务端读取不到结束标志而阻塞,从而不能循环处理新连接
// socketChannel.shutdownOutput();
System.out.println("数据已经写给服务器");
// 4.读取服务器写回的数据
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
//因为要循环从buff读数据,所以要切换读写模式,切换为读模式
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len,"utf-8"));
//数据读写完毕,切换为写模式,因为循环要读数据到buff中,所以要切换为写
byteBuffer2.clear();
}
//4.释放资源
socketChannel.close();
}
}
server
import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) throws IOException {
// 1.打开一个服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2.绑定对应的端口号
serverSocketChannel.bind(new InetSocketAddress(10000));
// 3.通道默认是阻塞的,需要设置为非阻塞
//如果传递true 表示通道设置为阻塞通道...默认值
//如果传递false 表示通道设置为非阻塞通道
serverSocketChannel.configureBlocking(false);
while (true) {
// 5.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
//此时已经设置了通道为非阻塞
//所以在调用方法的时候,如果有客户端来连接,那么会创建一个SocketChannel对象.
//如果在调用方法的时候,没有客户端来连接,那么他会返回一个null
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
System.out.println("此时有客户端来连接了");
//设置客户端连接通道为非阻塞的,从而read就不会因为没有结束标记而阻塞,设置非阻塞后read读完数据len返回0
socketChannel.configureBlocking(false);
// 6.客户端将缓冲区通过通道传递给服务端,就到了这个延伸通道socketChannel里面
// 7.服务端创建一个空的缓冲区装数据并输出
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//获取传递过来的数据,并把他们放到byteBuffer缓冲区中.
//返回值:
//正数: 表示本次读到的有效字节个数.
//0 : 表示本次没有读到有效字节.
//-1 : 表示读到了末尾
// int len = socketChannel.read(byteBuffer);
// System.out.println(new String(byteBuffer.array(),0,len,"utf-8"));
int len;
//针对于缓冲区来讲
//如果 从添加数据 ----> 获取数据 flip
//如果 从获取数据 ----> 添加数据 clear
while((len = socketChannel.read(byteBuffer))>0){
//切换为读
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(),0,len,"utf-8"));
//切换为写
byteBuffer.clear();
}
//会写数据到客户端
System.out.println("接收数据完毕,准备开始往客户端回写数据");
ByteBuffer byteBuffer2 = ByteBuffer.wrap("我蹭踏足山巅也曾跌落谷底!!!".getBytes("utf-8"));
socketChannel.write(byteBuffer2);
//8.释放资源
socketChannel.close();
}
}
}
}
阻塞IO:相当于你点了外卖后都是一直在门口等外卖,直到外卖送过来
非阻塞IO:相当于你点了外卖后,不在一直傻等,而是干一下别的事情,就跑到门口看看外卖到了没有。一直这样循环,直到我取到外卖,才从这个循环中跳出来
多路IO
多路IO:相当于你点外卖,请了一个门卫看着,外卖没来之前可以自己干自己的事情,如果外卖到了门卫会通知你去拿,
多路IO步骤
- 打开一个服务端通道(open)
- 绑定对应的端口号
- 通道默认是阻塞的,需要设置为非阻塞
- 打开一个选择器(门卫大爷)
- 将选择器绑定服务端通道,并监视服务端是否准备好
- 如果有客户端来连接了,大爷会遍历所有的服务端通道,谁准备好了,就让谁来连接
连接后,在服务端通道内部,再创建一个客户端延伸通道 - 如果客户端把数据传递过来了,大爷会遍历所有的延伸通道,谁准备好了,谁去接收数据
client
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
//1.打开通道
SocketChannel socketChannel = SocketChannel.open();
//2.指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
//3.写出数据
ByteBuffer byteBuffer = ByteBuffer.wrap("死亡如风,常伴吾身".getBytes("utf-8"));
socketChannel.write(byteBuffer);
// 手动写入结束标记,防止服务端读取不到结束标志而阻塞,从而不能循环处理新连接
// socketChannel.shutdownOutput();
System.out.println("数据已经写给服务器");
// 4.读取服务器写回的数据
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
//因为要循环从buff读数据,所以要切换读写模式,切换为读模式
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len,"utf-8"));
//数据读写完毕,切换为写模式,因为循环要读数据到buff中,所以要切换为写
byteBuffer2.clear();
}
//4.释放资源
socketChannel.close();
}
}
server
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;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
//1.打开服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.让这个通道绑定一个端口
serverSocketChannel.bind(new InetSocketAddress(10000));
//3.设置通道为非阻塞
serverSocketChannel.configureBlocking(false);
//4.打开一个选择器
//Selector --- 选择器
// SelectionKey --- 绑定通道后返回那个令牌
// SelectableChannel --- 可以使用选择器的通道
Selector selector = Selector.open();
//5.绑定选择器和服务端通道
//将Channel注册到给定的Selector sel上,并指定注册的事件类型(SelectionKey.OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT等)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
//选择器会监视客户端通道的状态.
//6.返回值就表示此时有多少个客户端来连接
//阻塞直到注册在Selector中的Channel 发送可读写事件(或其他注册事件)
int count = selector.select();
if(count != 0){
System.out.println("有事件发生了");
//7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
//获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
//selectionKey 依次表示每一个服务端通道的令牌
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
//可以通过令牌来获取到了一个已经就绪的服务端通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
//客户端的延伸通道
SocketChannel socketChannel = ssc.accept();
//将客户端延伸通道设置为非阻塞的
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
//当客户端来连接的时候,所有的步骤已经全部执行完毕.
}else if(selectionKey.isReadable()){
//当前通道已经做好了读取的准备(延伸通道)
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer1)) > 0){
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
//给客户端的回写数据
socketChannel.write(ByteBuffer.wrap("你装啥呢!!!".getBytes("utf-8")));
socketChannel.close();
}
iterator.remove();
}
}
}
}
}