第一章 网络编程
通信一定是基于软件结构实现的
- C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷,IDEA等软件
- B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等、软件:博学谷、京东、淘宝。(开发中的重点,基于网页设计界面,界面效果可以更丰富: Java Web开发)
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的技术
网络通信的三要素
-
协议:计算机网络客户端与服务端通信必须事先约定和彼此遵守的通信规则。 HTTP , FTP , TCP , UDP , SSH , SMTP。 -
IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。 IP地址用来给一个网络中的计算机设备做唯一的编号 IPv4: 4个字节,32位组成。 192.168.70.70 局域网:公司内部用 城域网 广域网(公网):可以在任何地方访问 IPv6: 可以实现为所有设备分配IP 128位 ipconfig:查看本机的IP ping 检查本机与某个IP指定的机器是否联通,或者说是检测对方是否在线。 ping 空格 IP地址 ping 220.181.57.216 ping www.baidu.com 注意:特殊的IP地址: 本机IP地址.(不受环境的影响,任何时候都存在这两个ip,可以直接找本机!) 127.0.0.1 == localhost。 -
端口:端口号就可以唯一标识设备中的进程(应用程序)了 端口号:用两个字节表示的整数,它的取值范围是0~65535。
- 0~1023之间的端口号用于一些知名的网络服务和应用。
- 普通的应用程序需要使用1024以上的端口号。
- 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。报出端口被占用异常!!
利用协议 +IP地址 +端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
网络通信的分层和协议
网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信
-------------------------------------------------------------------------------
应用层 :应用程序(QQ,微信,浏览器),可能用到的协议(HTTP,FTP,SMTP) 通常程序员只需要关心这一层
------------------------------------------------------------------------------
传输层 :TCP/IP协议 - UDP协议 计算机网络工程师需要精通的协议,有些技术我们也需要精通这一层协议,
-----------------------------------------------------------------
网络层 :IP协议 封装自己的IP和对方的IP和端口
-----------------------------------------------------------------
数据链路层 : 进入到硬件(网)
-----------------------------------------------------------------
InetAddress类概述
InetAddress 类的对象就代表一个IP地址对象。
InetAddress 类成员方法:
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostName());
System.out.println(ip.getHostAddress());
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
InetAddress ip3 = InetAddress.getByName("182.61.200.6");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
System.out.println(ip2.isReachable(5000));
第二章 UDP通信
UDP协议的特点
- 面向无连接的协议
- 发送端只管发送,不确认对方是否能收到
- 基于数据包进行数据传输
- 发送数据的包的大小限制64KB以内
- 因为面向无连接,速度快,但是不可靠。会丢失数据!
UDP协议的使用场景
UDP协议相关的两个类
DatagramPacket
- 数据包对象
- 作用:用来封装要发送或要接收的数据,比如:集装箱
DatagramSocket
DatagramPacket 类构造器
发送端用:new DatagramPacket(byte[] buf, int length, InetAddress address, int port) 创建发送端数据包对象
buf :要发送的内容,字节数组length :要发送内容的长度,单位是字节address :接收端的IP地址对象port :接收端的端口号
接收端用:new DatagramPacket(byte[] buf, int length)
- 创建接收端的数据包对象
buf :用来存储接收到内容length :能够接收内容的长度
DatagramPacket 类常用方法
* `int getLength()` 获得实际接收到的字节个数
DatagramSocket 类构造方法
DatagramSocket() 创建发送端的Socket对象,系统会随机分配一个端口号。DatagramSocket(int port) 创建接收端的Socket对象并指定端口号
DatagramSocket 类成员方法
void send(DatagramPacket dp) 发送数据包void receive(DatagramPacket p) 接收数据包
需求:使用UDP实现客户端发,服务端收。(了解)
客户端
System.out.println("===启动客户端===");
byte[] buffer = "今晚,约吗?".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 6666);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();
服务端
System.out.println("==启动服务端程序==");
byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
DatagramSocket socket = new DatagramSocket(6666);
socket.receive(packet);
int len = packet.getLength();
String rs = new String(buffer , 0 , len);
System.out.println(rs);
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方:"+ip+":"+port);
socket.close();
第三章 TCP通信
TCP/IP协议 ==> Transfer Control Protocol ==> 传输控制协议 TCP/IP协议的特点
- 面向连接的协议
- 只能由客户端主动发送数据给服务器端,服务器端接收到数据之后,可以给客户端响应数据。
- 通过三次握手建立连接,连接成功形成数据传输通道。
- 通过四次挥手断开连接
- 基于IO流进行数据传输
- 传输数据大小没有限制
- 因为面向连接的协议,速度慢,但是是可靠的协议。
TCP协议的使用场景
TCP协议相关的类
-
Socket 一个该类的对象就代表一个客户端程序 -
ServerSocket 一个该类的对象就代表一个服务器端程序
TCP通信也叫Socket 网络编程,只要代码基于Socket 开发,底层就是基于了可靠传输的TCP通信。
Socket 类构造方法
* `Socket(String host, int port)`
根据ip地址字符串和端口号创建客户端Socket对象
> 只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。如果连接成功,则表示三次握手通过。
Socket 类常用方法
OutputStream getOutputStream() ; 获得字节输出流对象InputStream getInputStream() ;获得字节输入流对象
客户端的开发流程
- 客户端要请求于服务端的
socket 管道连接。 - 从
socket 通信管道中得到一个字节输出流 - 通过字节输出流给服务端写出数据。
服务端的开发流程
- 注册端口。
- 接收客户端的
Socket 管道连接。 - 从
socket 通信管道中得到一个字节输入流。 - 从字节输入流中读取客户端发来的数据。
需求:客户端发送一行数据,服务端接收一行数据!!
- 客户端用
Socket 连接服务端。 - 服务端用
ServerSocket 注册端口,接收客户端的Socket 连接。 - 通信是很严格的,对方怎么发,你就应该怎么收,对方发多少你就只能收多少。
- 实现的面向连接的
socket 端到端的通信管道,一方如果出现对象,另一方会出现异常!
TCP通信的第一个入门案例
简单的发送一条信息
客户端
Socket socket = new Socket("127.0.0.1" , 9999);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("我是客户端,喜欢你很久了,第一次给你发消息,只想说:约吗?");
ps.flush();
System.out.println("客户端发送完毕~~~~");
服务端
System.out.println("----服务端启动----");
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
Reader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line ;
if((line = br.readLine())!=null){
System.out.println(line);
}
TCP通信的第二个案例-循环发送
客户端可以反复发送数据,服务端可以反复接受数据
客户端
Socket socket = new Socket("127.0.0.1" , 9999);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
while(true){
Scanner sc = new Scanner(System.in);
System.out.print("请说:");
ps.println(sc.nextLine());
ps.flush();
}
服务端
System.out.println("----服务端启动----");
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
Reader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line ;
while((line = br.readLine())!=null){
System.out.println(line);
}
TCP通信的第三个案例-一个服务端同时接受多个客户端消息
需要在服务端引入多线程。每接收一个客户端的Socket通道,就为它分配一个独立的线程来处理它的消息。如此便可实现:一个服务端可以同时接收多个客户端的消息。
服务端
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
ServerSocket serverSocket = new ServerSocket(9999);
while(true){
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket ;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
InputStream is = socket.getInputStream();
Reader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line ;
while((line = br.readLine())!=null){
System.out.println(socket.getRemoteSocketAddress()+"说:"+line);
}
}catch (Exception e){
System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
}
}
}
客户端
Socket socket = new Socket("127.0.0.1" , 9999);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
while(true){
Scanner sc = new Scanner(System.in);
System.out.print("请说:");
ps.println(sc.nextLine());
ps.flush();
}
TCP通信的第四个案例-线程池
我们之前引入的线程解决一个服务端可以接收多个客户端消息。客户端与服务端的线程模型是: N-N的关系。 一个客户端要一个线程。这种模型是不行的,并发越高,系统瘫痪的越快
我们可以在服务端引入线程池,使用线程池来处理与客户端的消息通信,线程池不会引起出现过多的线程而导致系统死机
客户端
try {
Socket socket = new Socket("127.0.0.1" , 9999);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.print("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
服务端主程序
try {
System.out.println("----------服务端启动成功------------");
ServerSocket ss = new ServerSocket(9999);
HandlerSocketThreadPool handlerSocketThreadPool =
new HandlerSocketThreadPool(3, 100);
while(true){
Socket socket = ss.accept() ;
System.out.println("有人上线了!!");
handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
HandlerSocketThreadPool
public class HandlerSocketThreadPool {
private ExecutorService executor;
public HandlerSocketThreadPool(int maxPoolSize, int queueSize){
executor = new ThreadPoolExecutor(
maxPoolSize,
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize) );
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
ReaderClientRunnable
class ReaderClientRunnable implements Runnable {
private Socket socket ;
public ReaderClientRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream() ;
Reader fr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(fr);
String line = null ;
while((line = br.readLine())!=null){
System.out.println("服务端收到了数据:"+line);
}
} catch (Exception e) {
System.out.println("有人下线了");
}
}
}
即时通信
ClientChat
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class ClientChat implements ActionListener {
private JFrame win = new JFrame();
public JTextArea smsContent =new JTextArea(23 , 50);
private JTextArea smsSend = new JTextArea(4,40);
public JList<String> onLineUsers = new JList<>();
private JCheckBox isPrivateBn = new JCheckBox("私聊");
private JButton sendBn = new JButton("发送");
private JFrame loginView;
private JTextField ipEt , nameEt , idEt;
private Socket socket ;
public static void main(String[] args) {
new ClientChat().initView();
}
private void initView() {
win.setSize(650, 600);
displayLoginView();
}
private void displayChatView() {
JPanel bottomPanel = new JPanel(new BorderLayout());
win.add(bottomPanel, BorderLayout.SOUTH);
bottomPanel.add(smsSend);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
btns.add(sendBn);
btns.add(isPrivateBn);
bottomPanel.add(btns, BorderLayout.EAST);
smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
smsContent.setEditable(false);
Box rightBox = new Box(BoxLayout.Y_AXIS);
onLineUsers.setFixedCellWidth(120);
onLineUsers.setVisibleRowCount(13);
rightBox.add(new JScrollPane(onLineUsers));
win.add(rightBox, BorderLayout.EAST);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.pack();
setWindowCenter(win,650,600,true);
sendBn.addActionListener(this);
}
private void displayLoginView(){
loginView = new JFrame("登录");
loginView.setLayout(new GridLayout(3, 1));
loginView.setSize(400, 230);
JPanel ip = new JPanel();
JLabel label = new JLabel(" IP:");
ip.add(label);
ipEt = new JTextField(20);
ip.add(ipEt);
loginView.add(ip);
JPanel name = new JPanel();
JLabel label1 = new JLabel("姓名:");
name.add(label1);
nameEt = new JTextField(20);
name.add(nameEt);
loginView.add(name);
JPanel btnView = new JPanel();
JButton login = new JButton("登陆");
btnView.add(login);
JButton cancle = new JButton("取消");
btnView.add(cancle);
loginView.add(btnView);
loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setWindowCenter(loginView,400,260,true);
login.addActionListener(this);
cancle.addActionListener(this);
}
private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
Dimension ds = frame.getToolkit().getScreenSize();
int width1 = ds.width;
int height1 = ds.height ;
System.out.println(width1 +"*" + height1);
frame.setLocation(width1/2 - width/2, height1/2 -height/2);
frame.setVisible(flag);
}
@Override
public void actionPerformed(ActionEvent e) {
JButton btn = (JButton) e.getSource();
switch(btn.getText()){
case "登陆":
String ip = ipEt.getText().toString();
String name = nameEt.getText().toString();
String msg = "" ;
if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
msg = "请输入合法的服务端ip地址";
}else if(name==null || !name.matches("\\S{1,}")){
msg = "姓名必须1个字符以上";
}
if(!msg.equals("")){
JOptionPane.showMessageDialog(loginView, msg);
}else{
try {
win.setTitle(name);
socket = new Socket(ip, Constants.PORT);
new ClientReader(this,socket).start();
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);
dos.writeUTF(name.trim());
dos.flush();
loginView.dispose();
displayChatView();
} catch (Exception e1) {
e1.printStackTrace();
}
}
break;
case "取消":
System.exit(0);
break;
case "发送":
String msgSend = smsSend.getText().toString();
if(!msgSend.trim().equals("")){
try {
String selectName = onLineUsers.getSelectedValue();
int flag = 2 ;
if(selectName!=null&&!selectName.equals("")){
msgSend =("@"+selectName+","+msgSend);
if(isPrivateBn.isSelected()){
flag = 3 ;
}
}
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(flag);
dos.writeUTF(msgSend);
if(flag == 3){
dos.writeUTF(selectName.trim());
}
dos.flush();
} catch (Exception e1) {
e1.printStackTrace();
}
}
smsSend.setText(null);
break;
}
}
}
class ClientReader extends Thread {
private Socket socket;
private ClientChat clientChat ;
public ClientReader(ClientChat clientChat, Socket socket) {
this.clientChat = clientChat;
this.socket = socket;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
while(true){
int flag = dis.readInt();
if(flag == 1){
String nameDatas = dis.readUTF();
String[] names = nameDatas.split(Constants.SPILIT);
clientChat.onLineUsers.setListData(names);
}else if(flag == 2){
String msg = dis.readUTF() ;
clientChat.smsContent.append(msg);
clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
User
public class User {
private Integer id ;
private String name ;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
ServerChat
public class ServerChat {
public static Map<Socket, String> onLineSockets = new HashMap<>();
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(Constants.PORT);
while(true){
Socket socket = serverSocket.accept();
new ServerReader(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ServerReader extends Thread {
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
DataInputStream dis = null;
try {
dis = new DataInputStream(socket.getInputStream());
while(true){
int flag = dis.readInt();
if(flag == 1){
String name = dis.readUTF() ;
System.out.println(name+"---->"+socket.getRemoteSocketAddress());
ServerChat.onLineSockets.put(socket, name);
}
writeMsg(flag,dis);
}
} catch (Exception e) {
System.out.println("--有人下线了--");
ServerChat.onLineSockets.remove(socket);
try {
writeMsg(1,dis);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
private void writeMsg(int flag, DataInputStream dis) throws Exception {
String msg = null ;
if(flag == 1){
StringBuilder rs = new StringBuilder();
Collection<String> onlineNames = ServerChat.onLineSockets.values();
if(onlineNames != null && onlineNames.size() > 0){
for(String name : onlineNames){
rs.append(name+ Constants.SPILIT);
}
msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));
sendMsgToAll(flag,msg);
}
}else if(flag == 2 || flag == 3){
String newMsg = dis.readUTF() ;
String sendName = ServerChat.onLineSockets.get(socket);
StringBuilder msgFinal = new StringBuilder();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
if(flag == 2){
msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
msgFinal.append(" ").append(newMsg).append("\r\n");
sendMsgToAll(flag,msgFinal.toString());
}else if(flag == 3){
msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n");
msgFinal.append(" ").append(newMsg).append("\r\n");
String destName = dis.readUTF();
sendMsgToOne(destName,msgFinal.toString());
}
}
}
private void sendMsgToOne(String destName, String msg) throws Exception {
Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
for(Socket sk : allOnLineSockets){
if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
dos.writeInt(2);
dos.writeUTF(msg);
dos.flush();
}
}
}
private void sendMsgToAll(int flag, String msg) throws Exception {
Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
for(Socket sk : allOnLineSockets){
DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
dos.writeInt(flag);
dos.writeUTF(msg);
dos.flush();
}
}
}
Constants
public class Constants {
public static final int PORT = 7778 ;
public static final String SPILIT = "003197????④④?";
}
文件上传
实现客户端上传图片给服务端保存起来
服务端实现:
- 接受多个客户端传输来的图片数据存储到服务器路径
- 响应一个成功的消息给当前客户端
ClientDemo
public class ClientDemo {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(Constants.SERVER_IP , Constants.SERVER_PORT);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(Constants.SRC_IMAGE));
byte[] buffer = new byte[1024];
int len ;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0 ,len);
}
bos.flush();
socket.shutdownOutput();
bis.close();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("收到服务端响应:"+br.readLine());
}
}
Constants
public class Constants {
public static final String SRC_IMAGE = "D:\\itcast\\图片资源\\beautiful.jpg";
public static final String SERVER_DIR = "D:\\itcast\\约吧图片服务器\\";
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8888;
}
ServerDemo
public class ServerDemo {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
ServerSocket serverSocket = new ServerSocket(Constants.SERVER_PORT);
while(true){
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket ;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream(Constants.SERVER_DIR+ UUID.randomUUID().toString()+".jpg"));
byte[] buffer = new byte[1024];
int len ;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0 ,len);
}
bos.close();
System.out.println("服务端接收完毕了!");
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("您好,已成功接收您上传的图片!");
ps.flush();
Thread.sleep(100000);
}catch (Exception e){
System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
}
}
}
第四章 BS架构
之前客户端和服务端都需要自己开发。也就是CS架构。接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示。
class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<span style='color:green;font-size:100px;'>Hello, world<span>");
Thread.sleep(4000);
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
第五章 基本通信模型
-
BIO通信模式:同步阻塞式通信。(Socket网络编程也就是上面的通信架构) 同步:当前线程要自己进行数据的读写操作。(自己去银行取钱) 异步: 当前线程可以去做其他事情,(委托一小弟拿银行卡到银行取钱,然后给你) 阻塞: 在数据没有的情况下,还是要继续等待着读。(排队等待) 非阻塞:在数据没有的情况下,会去做其他事情,一旦有了数据再来获取。(柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理)
- BIO表示同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- 同步阻塞式性能极差:大量线程,大量阻塞。
-
伪异步通信:引入了线程池。 不需要一个客户端一个线程,可以实现1个线程复用来处理很多个客户端! 这种架构,可以避免系统的死机,因为不会出现很多线程,线程可控。 但是高并发下性能还是很差:a.线程数量少,数据依然是阻塞的。数据没有来线程还是要等待! -
NIO表示同步非阻塞IO,服务器实现模式为请求对应一个线程, 即客户端发送的连接请求都会注册到多路复用器上, 多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
1个主线程专门负责接收客户端:
1个线程[c1 ,s2 ,c3,c4, ,s2 ,c3,c4,c3,c4, ,s2 ,c3,c4]轮询所有的客户端,发来了数据才会开启线程处理
这种架构性能还可以!!
同步:线程还是要不断的接收客户端连接,以及处理数据。
非阻塞:如果一个管道没有数据,不需要等待,可以轮询下一个管道是否有数据!
-
AIO表示异步非阻塞IO,服务器实现模式为一个有效请求一个线程, 客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。 异步:服务端线程接收到了客户端管道以后就交给底层处理它的io通信。 自己可以做其他事情。 非阻塞:底层也是客户端有数据才会处理,有了数据以后处理好通知服务器应用来启动线程进行处理。
各种模型应用场景:
BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,
JDK 1.4开始支持。
AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。
|