目录
一、网络编程基本知识
前言:
端口号与IP地址的组合得出一个网络套接字:Socket,网络编程通常称为socket编程。
1、网络模型:
a.通讯方式:TCP、UDP
i. TCP : 可靠连接,使命必达,速度慢
ii. UDP : 不可靠,速度快
2、 TCP的编程模型
a. BIO / OIO
i. Blocking IO / Old IO
b. NIO(linux支持)
i. New IO : Non-Blocking IO
ii. Selector
iii. ByteBuffer (single pointer)
c. AIO(仅仅windows支持)
i. Asynchronous IO
2、Netty
a. 封装了NIO ByteBuf(read pointer、writer pointer)
第一种方式:BIO/OIO
写一个简单的例子:客户端连接服务器,只写一句话
第一步:创建客户端和服务端,并让客户端连接上服务端
server:
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8899));
System.out.println("hello");
Socket accept = serverSocket.accept();
System.out.println("world");
client:
Socket socket = new Socket("localhost",8899);
解析:
在以上的server端的代码中,运行以后”hello“会输出出来,但是,accept()方法是阻塞的,也就是说,如果客户端没有启动,或者说没有客户端连接到服务端,此时”world不会输出出来“。客户端启动,则”world“就会输出出来。
第二步:
客户端连接上服务端传给服务端一些信息
server:
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8899));
System.out.println("hello");
Socket accept = serverSocket.accept();
System.out.println("world");
// 获取输入流
InputStream is = accept.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println(str);
// 关闭资源
is.close();
accept.close();
serverSocket.close();
client:
Socket socket = new Socket("localhost",8899);
OutputStream oos = socket.getOutputStream();
// 获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
bw.write("你好啊,我是Michael");
bw.newLine();
bw.flush();
// 关闭资源
bw.close();
解析:此时服务端不仅仅会输出”hello“、”world“还会输出客户端传过来的信息:”你好啊,我是Michael“.
此时server中的br.readLine()是一个阻塞方法,即:如果客户端只是连接上,但是没有传过来数据,服务端会在br.readLine()处阻塞。如第三步所示
第三步:
server端的代码不变
client代码如下:
Socket socket = new Socket("localhost",8899);
OutputStream oos = socket.getOutputStream();
// 获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
// 下面的代码是等待键盘输入内容,如果不输入内容,下面的代码会进入阻塞状态
System.in.read();
解析:此时启动客户端,服务端仅仅会输出"hello"、"world"。并进入阻塞状态。
这种情况其实是一种网络攻击,叫拒绝服务攻击,也就是说我连接上你的服务器,但就是什么事也不干,其他正常的连接也连接不上来,这就是一种攻击。
这种模式类似于:一个餐厅开门后只能服务于一个客户,客户吃完饭,餐厅会关门,如果需要对下一个客户提供服务,需要重新开张。
以上模式只能接受一个客户端。如何改进呢:
第一种改进方式:
server:
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8899));
boolean started = true;
while (started){
Socket accept = serverSocket.accept();
// 获取输入流
InputStream is = accept.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println(str);
// 关闭资源
is.close();
accept.close();
}
serverSocket.close();
client
Socket socket = new Socket("localhost",8899);
OutputStream oos = socket.getOutputStream();
// 获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
bw.write("你好啊,我是Michael");
bw.newLine();
bw.flush();
bw.close();
解析:启动服务端,重复启动客户端,没启动一起客户端,服务端会收到"你好啊,我是Michael"。这种模式类似于,饭店每次只能允许一个人在里面吃饭,此时其他想要吃饭的客户必须得等上一个客户吃完饭走出去餐厅,才能进去。也就是其他客户端会进入阻塞模式。
第二种改进方式:
这种方式为每一个客户端生成一个线程,当大量客户端同时访问此服务时,由于起的线程太多,服务基本上就挂了
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket socket = new ServerSocket();
socket.bind(new InetSocketAddress("localhost", 8888));
boolean started = true;
while (started) {
new Thread(()->{
try {
Socket s = socket.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = br.readLine();
System.out.println(str);
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
socket.close();
}
}
public class Client {
public static void main(String[] args) throws IOException {
Socket s = new Socket("localhost", 8888);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write("mashibing");
bw.newLine();
bw.flush();
bw.close();
}
}
这种方式类似于:每个顾客去餐厅吃饭时,餐厅都找一个属于这个顾客的服务员(也就是线程)去为他服务,不妨碍其他顾客吃饭。而主线程的作用就是为其分配服务员(线程)。
第二种方式:NIO
NIO中使用的是ServerSocketChannel模式,channel就是通道的意思
public class Server2 {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ServerSocket socket = ssc.socket();
socket.bind(new InetSocketAddress("localhost",8899));
// 将此通道设置为非阻塞式
ssc.configureBlocking(false);
System.out.println("Server started, listening on:" + ssc.getLocalAddress());
// 启动大管家
Selector selector = Selector.open();
// 我让大管家管什么事呢?让大管家负责有连接需要连接过来的服务
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true){
// 开始轮询,有事情就会处理事情,在轮询时是阻塞状态
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()){
SelectionKey key = it.next();
it.remove();
handle(key);
}
}
}
private static void handle(SelectionKey key) {
// 客户端连接的情况
if(key.isAcceptable()){
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 有客户端进来时单独建立一个新的通道,同时也将此通道设置为非阻塞式
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 此通道注册一个读数据的通道还是什么不清楚
sc.register(key.selector(),SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}else if(key.isReadable()){
SocketChannel sc = null;
try {
sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.clear();
int len = sc.read(buffer);
if(len!=-1){
System.out.println(new String(buffer.array(),0,len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(bufferToWrite);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(sc!=null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
以上代码是NIO代码,只用了一个线程。Nertty就是对其的封装。NIO使用了ByteBuffer,其底层封装了一个字节数组,和一个指针,记录了读写数据的位置,但是它不好用,也容易出错。而Netty底层使用了ByteBuf,也是一个字节数组,但是其有两个指针分别记录读写数据的位置,并且,Netty可以操作直接内存即零拷贝,不需要经过JDK的内存,直接使用操作系统的内存,少了拷贝数据的过程,增加效率。
此种模型因为是单线程的,当大管家在处理某一张桌子(客户端)的需求时,其他需求都需要排队等待,改进方式如下:
NIO另一种模型:一个大管家+多个服务员(线程),线程数固定,大管家只负责接客,服务员负责每个客人的业务需求。服务员忙完后回到线程池进入等待状态,有了新的客户再去服务。但是ByteBuffer的问题依然没有解决
Netty:
以下只有服务端代码,没有客户端代码,客户端代码可以使用BIO中的客户端
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 负责接客
NioEventLoopGroup bossGroups = new NioEventLoopGroup(2);
// 负责服务
NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);
// Server启动辅助类
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroups,workerGroup);
// 异步全双工
b.channel(NioServerSocketChannel.class);
// netty 帮我们内部处理accept的过程
b.childHandler(new MyChildInitializer());
b.bind(8888).sync();
}
}
class MyChildInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("a client connected !");
}
}
一个完整的Netty例子
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 负责接客
NioEventLoopGroup boosGroup = new NioEventLoopGroup(2);
// 负责服务
NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);
// server启动辅助类
ServerBootstrap b = new ServerBootstrap();
b.group(boosGroup,workerGroup);
// 指定使用的channel类型:异步全双工
b.channel(NioServerSocketChannel.class);
b.childHandler(new MyChildInitializer2());
ChannelFuture future = b.bind(8899).sync();
// 优雅的关闭方式
future.channel().closeFuture().sync();
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
class MyChildInitializer2 extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MyChildHandler2());
}
}
class MyChildHandler2 extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf.toString());
ctx.writeAndFlush(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
public class NettyClient {
public static void main(String[] args) throws InterruptedException, IOException {
NioEventLoopGroup worker = new NioEventLoopGroup(1);
Bootstrap bs = new Bootstrap();
bs.group(worker);
bs.channel(NioSocketChannel.class);
bs.handler(new MyChannelInit());
ChannelFuture future = bs.connect("localhost", 8899).sync();
// 等待关闭
future.channel().closeFuture().sync();
System.out.println("go on");
worker.shutdownGracefully();
}
}
class MyChannelInit extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MyHandler());
}
}
class MyHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("mashbing".getBytes());
ctx.writeAndFlush(buf);
}
}
详情请看马士兵关于线程与高并发的视频
二、网络编程基本实例
1、客户端发一句话给服务端
public class client {
public static void main(String[] args) {
Socket socket = null;
OutputStream oos = null;
try {
// 1、创建socket对象,指明服务器端的ip和端口号
InetAddress address = InetAddress.getByName("localhost");
socket = new Socket(address, 8899);
// 2、获取一个输出流,用于输出数据
oos = socket.getOutputStream();
// 3、写出数据的操作
oos.write("你好啊,我是client".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4、资源关闭
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class SeverTest {
public static void main(String[] args) throws IOException {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 1、创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
// 2、调用accept方法,表示接受客户端的socket
socket = ss.accept();
// 3、获取输入流
is = socket.getInputStream();
// // 不建议这个方式:
// byte[] bytes = new byte[10];
// int len;
// while ((len = is.read(bytes)) != -1) {
// String str = new String(bytes,0,len);
// System.out.println(str);
// }
// 4、读取输入流中的数据
// ByteArrayOutputStream内部自己封装了一个字节数组,也会自动扩容,使用这个时,会先将数据写入自己的字节数组中。
baos = new ByteArrayOutputStream();
byte[] bytes = new byte[10];
int len;
while ((len = is.read(bytes)) != -1) {
baos.write(bytes, 0, len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
is.close();
ss.close();
baos.close();
socket.close();
}
}
}
2、客户端给服务端发一个文本
public class client1 {
public static void main(String[] args) throws Exception {
Socket socket = null;
OutputStream oos = null;
FileInputStream fis = null;
try {
socket = new Socket(InetAddress.getByName("localhost"), 8899);
oos = socket.getOutputStream();
fis = new FileInputStream(new File("hello.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
oos.write(bytes,0,len);
}
System.out.println("文件写入成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
socket.close();
oos.close();
fis.close();
}
}
}
public class server1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
try {
serverSocket = new ServerSocket(8899);
socket = serverSocket.accept();
is = socket.getInputStream();
fos = new FileOutputStream(new File("b.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
serverSocket.close();
socket.close();
is.close();
fos.close();
}
}
}
3、客户端给服务端发送完消息后,服务端再给客户端一个回馈
public class client2 {
public static void main(String[] args) throws Exception {
Socket socket = null;
OutputStream oos = null;
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
socket = new Socket(InetAddress.getByName("localhost"), 8899);
oos = socket.getOutputStream();
fis = new FileInputStream(new File("hello.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
oos.write(bytes,0,len);
}
// 此语句的作用是告诉客户端我的消息发送停止了,如果没有此语句,
// 因为server的read方法是阻塞的,所以会erver会一直在读数据
socket.shutdownOutput();
System.out.println("文件写入成功");
InputStream is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] bytes1 = new byte[1024];
int len2;
while ((len2=is.read(bytes1))!=-1){
baos.write(bytes1,0,len2);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
fis.close();
oos.close();
socket.close();
baos.close();
}
}
}
public class server2 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
OutputStream oos = null;
try {
serverSocket = new ServerSocket(8899);
socket = serverSocket.accept();
is = socket.getInputStream();
fos = new FileOutputStream(new File("c.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
System.out.println("图片传输完成");
oos = socket.getOutputStream();
oos.write("你好,信的内容我已经收到了,很好,不错!".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
fos.close();
serverSocket.close();
socket.close();
is.close();
oos.close();
}
}
}
三、URL编程
public class URLTest {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://www.bilibili.com/video/BV1Kb411W75N?p=629");
// 获取该URL的协议名
System.out.println(url.getProtocol());
// 获取该URL的主机名
System.out.println(url.getHost());
// 获取该URL的端口号
System.out.println(url.getPort());
// 获取该URL的文件路径
System.out.println(url.getPath());
// 获取该URL的文件名
System.out.println(url.getFile());
// 获取该URL的查询名
System.out.println(url.getQuery());
}
}
public class URLTest02 {
public static void main(String[] args) throws IOException {
URL url = new URL("\"https://www.bilibili.com/video/BV1Kb411W75N?p=629\"");
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("d.txt");
byte[] bytes = new byte[1024];
int len;
while ((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
is.close();
fos.close();
urlConnection.getClass();
}
}
}