【1】TCP/IP协议概述
TCP协议:
网络传输控制协议
IP协议:
网络地址协议,(作用定位在网络上计算机)
TCP/IP采用的是如下【3】的四层结构,每一层都依靠它的下一层所提供的协议来完成自己的需求。我们大部分时间工作在应用层。
【2】TCP/IP协议栈概述
TCP/IP协议栈是一系列协议的总和,即构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。
【3】四层结构
来源:四层结构是由七层结构简化而来。
(1)应用层:
A:发送邮件(smtp pop3 imap之类的邮件协议)
B:使用文件服务(FTP协议)
C:访问互联网上的网页(HTTP协议(超文本协议))
D:远程登录(Telnet协议(是明文传输的(不安全)),如果要保证安全传输要对数据 进行加密处理(使用SSH协议))
(2)传输层:
A:把应用层的数据打包发出去
B:接收从互联网发来的数据
C:规定了打包数据和数据发送的目的应用程序(每个应用程序占用一个端口,eg:http:占用80端口(TCP协议),telnet:占用端口22(TCP协议),msql:占用端口3306(TCP协议))
D:TCP是可靠连接,保证数据的安全性和有序性。UDP是非可靠协议,不能保证数据的安全和有序,但是优点是传输快速。
(3)互联网层:
确定了数据要发给哪里,如何寻址,接收的数据是谁发的。
使用的是IP协议
(4)网络访问层:
提供与物理网络的接口,与硬件相关。
【4】发送数据的过程&接收数据的过程
发送数据:
应用层(处理)---------->传输层(打包)--------->互联网层(发给谁)--------->网络访问层(发送)
接收数据:
网络访问层(数据)----------------------->互联网层----------------------->传输层--------------------->应用层
【5】接下来从客户端开始介绍如何接收数据和发送数据
这是客户端的一段接收数据进行控制台显示的代码:
package Socket;
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
//第一步:新建socket对象
Socket socket = new Socket("222.24.34.231", 80);
//第一个参数是主机的IP地址,第二个参数是端口号
//每个程序只能占用一个端口号,避免冲突
//连接的过程有个三次握手的过程
// 客户端 --》 服务器
// 服务器 --》 客户端
// 客户端 --》 服务器
OutputStream outputStream = socket.getOutputStream();
//第二步:获得发送数据的输出流
outputStream.write(3);//发送一个int 3
outputStream.write("Hello World".getBytes());//发送字节数组
outputStream.write("\n".getBytes());//换行符
outputStream.write("\n".getBytes());
InputStream inputStream = socket.getInputStream();
//第三步:获得接收数据的输入流
System.out.println(inputStream.read());
BufferedReader bufferedInputStream = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//字节输入流成字符输入流,再转成高效字符流
while (true){
String string=bufferedInputStream.readLine();//高效字符流的特殊方法,一次读一行
if(string==null){
//判断当读进的数据为空的时候,读取完毕
break;
}
}
socket.close();//关闭连接
}
当然接收数据的时候也可以用字节流,只是我这里用高效字符流是因为它一次能读取一行的方法,快一些。
但是这个时候我们如果想从网页获取数据呢?比如爬取一张图片,该怎么做呢?
package Socket;
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
//第一步:新建socket对象
Socket socket = new Socket("192.168.3.123", 80);
//第一个参数是主机的IP地址,第二个参数是端口号
//每个程序只能占用一个端口号,避免冲突
//连接的过程有个三次握手的过程
// 客户端 --》 服务器
// 服务器 --》 客户端
// 客户端 --》 服务器
OutputStream outputStream = socket.getOutputStream();
//第二步:获得发送数据的输出流
outputStream.write(3);//发送一个int 3
outputStream.write("Hello World".getBytes());//发送字节数组
outputStream.write("\n".getBytes());//换行符
outputStream.write("\n".getBytes());
InputStream inputStream = socket.getInputStream();
//第三步:获得接收数据的输入流
System.out.println(inputStream.read());
BufferedReader bufferedInputStream = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//字节输入流成字符输入流,再转成高效字符流
while (true){
String string=bufferedInputStream.readLine();//高效字符流的特殊方法,一次读一行
if(string==null){
//判断当读进的数据为空的时候,读取完毕
break;
}
}
socket.close();//关闭连接
}
}
但是用这种方法爬下来的,并不能打开图片,这是为什么呢?
原来这样的方式爬出来的数据,不仅仅是有图片的数据还有头文件的数据,而在解析的过程中把头文件也当成图片的一部分当然显示不出之前的完整图片咯。
那怎么办呢?除了自己手动去除头文件,还有一种方法,这里就要引出统一资源定位符URL了
【6】URL
概述:统一资源定位符
package Socket;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class ClientURLDemo {
public static void main(String[] args) throws IOException {
//http://www.baidu.com/img/bd_logo1.png
//http://ip地址:端口/资源地址
HttpURLConnection urlConnection = (HttpURLConnection)
new URL("http://www.baidu.com/img/bd_logo1.png?where=super").openConnection();
//System.out.println(urlConnection);
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("E:/picture.png");
while (true){
byte[] bytes=new byte[1024*8];
int len=inputStream.read(bytes);
if(len==-1){
break;
}
fileOutputStream.write(bytes,0,len);
}
fileOutputStream.close();
urlConnection.disconnect();
}
}
其中的端口号:80是默认的,可以不写。第一次老师报错,问了一个我们班的小哥哥,哈哈哈哈哈哈哈哈嗝,讨论了好久,试了好多,终于发现地址后面加一个“?where=super”才能成功。
【7】服务器端编程模式
(1)阻塞IO方式:BIO
首先是服务器端,开启之后显示“服务器端已开启,等待连接”
accept();执行到这个时候就等待客户端的连接
package ServerClient;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ServerSocketDemo {
public static void main(String[] args) throws IOException {
//第一步:new 一个ServerSocket
ServerSocket serverSocketDemo = new ServerSocket(5000);
System.out.println("服务器端已开启,等待连接...");
//如果此时客户端的请求很多 new 线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
//参数一:corePoolSize:核心线程数
//参数二:maxPoolSize:最大线程数 核心线程数+救极线程数<=最大线程数
//参数三:keepAlivetime :保持时间 如果一个线程闲暇的时间超过了保持时间,那么就把它回收,但是剩余的线程数不会少于核心线程数
//参数四:BlockingQueue:阻塞队列 当任务数超过核心线程数之和,就把任务放入阻塞队列排队运行(有有界和无界之分)
//可以给参数
while (true){
Socket accept = serverSocketDemo.accept();
//调用accept来等待客户端的连接
System.out.println("客户端已连接...");
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
handle(accept);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
private static void handle(Socket accept) throws IOException {
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
while (true){
byte[] bytes=new byte[1024*8];
int len=inputStream.read(bytes);
if(len==-1){
break;
}
String string = new String(bytes,0,len,"utf-8");
System.out.println(string);
outputStream.write(("服务器应答:"+string).getBytes("utf-8"));
}
}
}
接下来开启一个客户端,向服务器发起请求连接
package ServerClient;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 5000);
//输出数据流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello World".getBytes());
//开启线程
new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner=new Scanner(System.in);//键盘输入数据
while (scanner.hasNextLine()){
try {
String string=scanner.nextLine();
outputStream.write(string.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
//接收数据
InputStream inputStream = socket.getInputStream();
while (true){
byte[] bytes=new byte[1024*8];
int len=inputStream.read(bytes);
if(len==-1){
break;
}
String string=new String(bytes,0,len,"utf-8");
System.out.println(string);
}
}
}
阻塞IO的特点:
一个socket执行IO读写操作会组织其他IO的读写,一个线程内IO的读写是串行的,可以用多线程的方法来解决,建议使用线程池而不是自己动手不断创建线程。
为了简化程序,我们还可以使用Lambda表达式
【8】Lambda表达式
表达式:
参数部分 特殊符号 代码体
(形参) -> {执行的代码}
针对单行方法的接口,才能使用Lambda表达式简化:()->{ }
参数部分可以省略类型,如果代码体部分只有一条语句,{ }也可以省略,且不能加;号,如果代码体部分,只有一条语句,那么它还可以充当返回值,从而省略return。
比如上面的代码,简化之后:
while (true){
Socket accept = serverSocketDemo.accept();
//调用accept来等待客户端的连接
System.out.println("客户端已连接...");
// threadPoolExecutor.submit(new Runnable() {
// @Override
// public void run() {
// try {
// handle(accept);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// });
threadPoolExecutor.submit(()->{
try {
handle(accept);
} catch (IOException e) {
e.printStackTrace();
}
});
}
如果还觉得不是很明了的话,再来看一个简单一点的例子:
public class TestDemo {
public static void main(String[] args) {
List<Integer> objects = new ArrayList<>();
objects.add(34);
objects.add(3);
objects.add(44);
// Collections.sort(objects, new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return o2-o1;
// }
// });
Collections.sort(objects, (o1, o2) -> o2 - o1);
}
}
(2)并发量再高,怎么办?用非阻塞IO(NIO)
非阻塞IO的思想:
线程(多路)复用:一个线程可以同时处理多个IO操作,减少了线程的数量,能够应付更高的并发
比如WEB服务器 tomcat 雄猫(BIO,NIO);Netty服务器,封装了NIO技术,并发能力很高;spark分布式计算框架;Redis缓存服务器。