网络通信的基本概念
/**
目标:基本软件结构。
通信一定是基于软件结构实现的:
1.C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。
常见程序有QQ、迅雷,IDEA等软件。
2.B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。
常见浏览器有谷歌、火狐等、软件:博学谷、京东、淘宝。
(开发中的重点,基于网页设计界面,界面效果可以更丰富: Java Web开发)
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,
就是在一定的协议下,实现两台计算机的通信的技术。
*/
public class NetDemo01 {
}
网络通信的三要素
/**
目标:网络通信的三要素。
1.协议
协议:计算机网络客户端与服务端通信必须事先约定和彼此遵守的通信规则。
HTTP , FTP , TCP , UDP , SSH , SMTP。
2.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。
3.端口:
端口号就可以唯一标识设备中的进程(应用程序)了
端口号:
用两个字节表示的整数,它的取值范围是0~65535。
0~1023之间的端口号用于一些知名的网络服务和应用。
普通的应用程序需要使用1024以上的端口号。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。报出端口被占用异常!!
利用`协议`+`IP地址`+`端口号` 三元组合,
就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
*/
public class NetDemo02 {
}
网络通信的分层和协议
/**
目标:网络通信的分层和协议。
网络通信协议:通信协议是对计算机必须遵守的规则,
只有遵守这些规则,计算机之间才能进行通信。
-------------------------------------------------------------------------------
应用层 :应用程序(QQ,微信,浏览器),可能用到的协议(HTTP,FTP,SMTP) 通常程序员只需要关心这一层
------------------------------------------------------------------------------
传输层 :TCP/IP协议 - UDP协议 计算机网络工程师需要精通的协议,有些技术我们也需要精通这一层协议,
-----------------------------------------------------------------
网络层 :IP协议 封装自己的IP和对方的IP和端口
-----------------------------------------------------------------
数据链路层 : 进入到硬件(网)
-----------------------------------------------------------------
TCP/IP协议:传输控制协议 (Transmission Control Protocol)。
TCP协议是面向连接的安全的可靠的传输通信协议。
1.在通信之前必须确定对方在线并且连接成功才可以通信。
2.例如下载文件、浏览网页等(要求可靠传输)
UDP:用户数据报协议(User Datagram Protocol)。
UDP协议是一个面向无连接的不可靠传输的协议。
1.直接发消息给对方,不管对方是否在线,发消息后也不需要确认。
2.无线(视频会议,通话),性能好,可能丢失一些数据!!
*/
public class NetDemo03 {
}
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
目标:InetAddress类概述
一个该类的对象就代表一个IP地址对象。
InetAddress类成员方法:
static InetAddress getLocalHost()
* 获得本地主机IP地址对象。
static InetAddress getByName(String host)
* 根据IP地址字符串或主机名获得对应的IP地址对象。
String getHostName()
* 获得主机名。
String getHostAddress()
* 获得IP地址字符串。
*/
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象。
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostName());
System.out.println(ip.getHostAddress());
// 2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 3.获取公网IP对象。
InetAddress ip3 = InetAddress.getByName("182.61.200.6");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
// 4.判断是否能通: ping 5s之前测试是否可通
System.out.println(ip2.isReachable(5000)); // ping
}
}
UDP通信的使用介绍(了解即可)
客户端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
目标: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实现客户端发,服务端收。(了解)
*/
public class UDPClientDemo01 {
public static void main(String[] args) throws Exception {
System.out.println("===启动客户端===");
// 1.创建一个集装箱对象,用于封装需要发送的数据包!
/**
new DatagramPacket(byte[] buf, int length, InetAddress address, int port)
参数一:封装数据的字节数组。
参数二:发送数据的长度!
参数三:服务端的IP地址
参数四:服务端程序的端口号码。
*/
byte[] buffer = "今晚,约吗?".getBytes();
DatagramPacket packet = new DatagramPacket(buffer , buffer.length
, InetAddress.getLocalHost(),6666);
// 2.创建一个码头对象
// 参数可以申明客户端端口,可以有可以没有,默认会给一个端口。
DatagramSocket socket = new DatagramSocket();
// 3.开始发送数据包对象
socket.send(packet);
socket.close();
}
}
服务端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 目标:UDP服务端开发。接收客户端的消息。
*/
public class UDPServerDemo02 {
public static void main(String[] args) throws Exception {
System.out.println("==启动服务端程序==");
// 1.创建一个接收客户都端的数据包对象(集装箱)
/**
* new DatagramPacket(byte[] buffer ,int lenght):
* 参数一:接收数据的数组。
* 参数二:接收数据的数组的长度!
*/
byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 2.创建一个接收端的码头对象
DatagramSocket socket = new DatagramSocket(6666);
// 3.开始接收
socket.receive(packet);
// 4.从集装箱中获取本次读取的数据量
int len = packet.getLength();
// 5.输出数据
String rs = new String(buffer , 0 , len);
System.out.println(rs);
// 6.服务端还可以获取发来信息的客户端的IP和端口。
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方:"+ip+":"+port);
socket.close();
}
}
TCP通信
TCP可靠传输通信入门案例(非常重要)
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
目标: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();获得字节输入流对象
客户端的开发流程:
1.客户端要请求于服务端的socket管道连接。
2.从socket通信管道中得到一个字节输出流
3.通过字节输出流给服务端写出数据。
服务端的开发流程:
1.注册端口。
2.接收客户端的Socket管道连接。
3.从socket通信管道中得到一个字节输入流。
4.从字节输入流中读取客户端发来的数据。
需求:客户端发送一行数据,服务端接收一行数据!!
小结:
1.客户端用Socket连接服务端。
2.服务端用ServerSocket注册端口,接收客户端的Socket连接。
3.通信是很严格的,对方怎么发,你就应该怎么收,对方发多少你就只能收多少。
4.实现的面向连接的socket端到端的通信管道,一方如果出现对象,另一方会出现异常!
*/
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
// 1.客户端要请求于服务端的socket管道连接。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1" , 9999);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
// 4.开始发消息出去
ps.println("我是客户端,喜欢你很久了,第一次给你发消息,只想说:约吗?");
ps.flush();
System.out.println("客户端发送完毕~~~~");
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发服务器。
1.注册端口。
2.接收客户端的Socket管道连接。
3.从socket通信管道中得到一个字节输入流。
4.从字节输入流中读取客户端发来的数据。
ServerSocket类:
构造器:public ServerSocket(int port)
方法:public Socket accept():
-- 等待接收一个客户端的Socket管道连接请求,连接成功返回一个Socket对象
*/
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口: public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(9999);
// 2.开始等待接收客户端的Socket管道连接。
Socket socket = serverSocket.accept();
// 3.从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
Reader isr = new InputStreamReader(is);
// 5.把字符输入流包装成缓冲字符输入流。
BufferedReader br = new BufferedReader(isr);
// 6.按照行读取消息 。
String line ;
if((line = br.readLine())!=null){
System.out.println(line);
}
}
}
客户端可以反复发送数据,服务端可以反复数据
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:客户端可以反复发送数据,服务端可以反复数据!!
分析:
只需要让客户端通过循环进行数据的发送。
产生的问题:
目前服务端只能接收一个客户端请求。
*/
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
// 1.客户端要请求于服务端的socket管道连接。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1" , 9999);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
// 4.开始发消息出去
while(true){
Scanner sc = new Scanner(System.in);
System.out.print("请说:");
ps.println(sc.nextLine());
ps.flush();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发服务器。
1.注册端口。
2.接收客户端的Socket管道连接。
3.从socket通信管道中得到一个字节输入流。
4.从字节输入流中读取客户端发来的数据。
ServerSocket类:
构造器:public ServerSocket(int port)
方法:public Socket accept():
-- 等待接收一个客户端的Socket管道连接请求,连接成功返回一个Socket对象
*/
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口: public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(9999);
// 2.开始等待接收客户端的Socket管道连接。
Socket socket = serverSocket.accept();
// 3.从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
Reader isr = new InputStreamReader(is);
// 5.把字符输入流包装成缓冲字符输入流。
BufferedReader br = new BufferedReader(isr);
// 6.按照行读取消息 。
String line ;
while((line = br.readLine())!=null){
System.out.println(line);
}
}
}
实现一个服务端可以同时接收多个客户端的消息
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:实现一个服务端可以同时接收多个客户端的消息。
总结:
需要在服务端引入多线程。
每接收一个客户端的Socket通道,就为它分配一个独立的线程来处理它的消息。
如此便可实现:一个服务端可以同时接收多个客户端的消息。
*/
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
// 1.客户端要请求于服务端的socket管道连接。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1" , 9999);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
// 4.开始发消息出去
while(true){
Scanner sc = new Scanner(System.in);
System.out.print("请说:");
ps.println(sc.nextLine());
ps.flush();
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发服务器。
1.注册端口。
2.接收客户端的Socket管道连接。
3.从socket通信管道中得到一个字节输入流。
4.从字节输入流中读取客户端发来的数据。
*/
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口: public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(9999);
// 2.定义一个循环不断的接收客户端的连接请求
while(true){
// 3.开始等待接收客户端的Socket管道连接。
Socket socket = serverSocket.accept();
// 4.每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信。
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket ;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
// 3.从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
Reader isr = new InputStreamReader(is);
// 5.把字符输入流包装成缓冲字符输入流。
BufferedReader br = new BufferedReader(isr);
// 6.按照行读取消息 。
String line ;
while((line = br.readLine())!=null){
System.out.println(socket.getRemoteSocketAddress()+"说:"+line);
}
}catch (Exception e){
System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
}
}
}
使用线程池
import java.net.ServerSocket;
import java.net.Socket;
// 放弃了1 客户端 一个线程的模型了
// 提供了线程池:
public class Server {
public static void main(String[] args) {
try {
System.out.println("----------服务端启动成功------------");
ServerSocket ss = new ServerSocket(9999);
// 一个服务端只需要对应一个线程池
HandlerSocketThreadPool handlerSocketThreadPool =
new HandlerSocketThreadPool(3, 100);
// 客户端可能有很多个
while(true){
Socket socket = ss.accept() ;
System.out.println("有人上线了!!");
// 每次收到一个客户端的socket请求,都需要为这个客户端分配一个
// 独立的线程 专门负责对这个客户端的通信!!
handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
拓展:(了解)
引入:
我们之前引入的线程解决一个服务端可以接收多个客户端消息
客户端与服务端的线程模型是: N-N的关系。 一个客户端要一个线程。
这种模型是不行的,并发越高,系统瘫痪的越快!!
解决:
我们可以在服务端引入线程池,使用线程池来处理与客户端的消息通信!!
线程池不会引起出现过多的线程而导致系统死机!!
这种方案的优劣势:
优势:不会引起系统的死机,可以控制并发线程的数量。
劣势:同时可以并发的线程将受到限制。
*/
public class Client {
public static void main(String[] args) {
try {
// 1.客户端要请求于服务端的socket管道连接。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1" , 9999);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
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();
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// 线程池处理类
public class HandlerSocketThreadPool {
// 线程池
private ExecutorService executor;
// 线程池:3个线程 100个
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);
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.Socket;
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("有人下线了");
}
}
}
文件上传
import java.io.*;
import java.net.Socket;
/**
目标:实现客户端上传图片给服务端保存起来。
开发客户端:本地图片: D:\itcast\图片资源\beautiful.jpg
开发服务端:服务器路径: D:\itcast\约吧图片服务器
*/
public class ClientDemo {
// 本地图片路径、
public static void main(String[] args) throws Exception {
// 1.请求于服务端的Socket管道连接。
Socket socket = new Socket(Constants.SERVER_IP , Constants.SERVER_PORT);
// 2.从socket管道中得到一个字节输出流包装成缓冲字节输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
// 3.提取本机的图片上传给服务端
// 4.得到一个缓冲字节输入流与本地图片接通
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(Constants.SRC_IMAGE));
// 5.定义一个字节数组
byte[] buffer = new byte[1024];
int len ;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0 ,len);
}
bos.flush(); // 刷新图片数据到服务端!!
socket.shutdownOutput(); // 告诉服务端我的数据已经发送完毕,请不要在等我了!
bis.close(); // 可以关闭
// 6.等待着服务端的响应数据!!
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("收到服务端响应:"+br.readLine());
}
}
/**
* 客户端常量包
*/
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;
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
/**
功能点:
1.接收多个客户端传输来的图片数据存储到服务器路径:
2.响应一个成功的消息给当前客户端。
*/
public class ServerDemo {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口: public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(Constants.SERVER_PORT);
// 2.定义一个循环不断的接收客户端的连接请求
while(true){
// 3.开始等待接收客户端的Socket管道连接。
Socket socket = serverSocket.accept();
// 4.每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信。
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket ;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
// 1.从socket通信管道中得到一个字节输入流读取客户端发来的图片数据!
InputStream is = socket.getInputStream();
// 2.包装成高级的缓冲字节输入流
BufferedInputStream bis = new BufferedInputStream(is);
// 3.定义一个缓冲字节输出流通向目标路径(服务端路径)
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("服务端接收完毕了!");
// 4.响应数据给客户端
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("您好,已成功接收您上传的图片!");
ps.flush();
Thread.sleep(100000); // 等消失发送完毕被客户端接收后死亡!
}catch (Exception e){
System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
}
}
}
B/S架构模拟
import com.itheima._07TCP通信四.Server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:BS-浏览器-服务器基本了解。
引入:
之前客户端和服务端都需要自己开发。也就是CS架构。
接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示。
*/
public class BSserverDemo {
public static void main(String[] args) {
try {
// 1.注册端口
ServerSocket ss = new ServerSocket(8080);
// 2.创建一个循环接收多个客户端的请求。
while(true){
Socket socket = ss.accept();
// 3.交给一个独立的线程来处理!
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 响应消息数据给浏览器显示。
// 浏览器是基于HTTP协议通信!响应格式必须满足HTTP协议数据格式的要求,浏览器
// 才能够识别,否则响应消息浏览器根本不认识。
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;'>牛逼的128期<span>");
Thread.sleep(4000);
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
基本通信模型的概念介绍
/**
拓展:基本通信模型的概念介绍。
1.BIO通信模式:同步阻塞式通信。(Socket网络编程也就是上面的通信架构)
-- 同步:当前线程要自己进行数据的读写操作。(自己去银行取钱)
-- 异步: 当前线程可以去做其他事情,(委托一小弟拿银行卡到银行取钱,然后给你)
-- 阻塞: 在数据没有的情况下,还是要继续等待着读。(排队等待)
-- 非阻塞:在数据没有的情况下,会去做其他事情,一旦有了数据再来获取。(柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理)
BIO表示同步阻塞式IO,服务器实现模式为一个连接一个线程,
即客户端有连接请求时服务器端就需要启动一个线程进行处理,
如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步阻塞式性能极差:大量线程,大量阻塞。
2.伪异步通信:引入了线程池。
不需要一个客户端一个线程,可以实现1个线程复用来处理很多个客户端!
这种架构,可以避免系统的死机,因为不会出现很多线程,线程可控。
但是高并发下性能还是很差:a.线程数量少,数据依然是阻塞的。数据没有来线程还是要等待!
3.NIO表示同步非阻塞IO,服务器实现模式为请求对应一个线程,
即客户端发送的连接请求都会注册到多路复用器上,
多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
1个主线程专门负责接收客户端:
1个线程[c1 ,s2 ,c3,c4, ,s2 ,c3,c4,,c3,c4, ,s2 ,c3,c4]轮询所有的客户端,发来了数据才会开启线程处理
这种架构性能还可以!!
同步:线程还是要不断的接收客户端连接,以及处理数据。
非阻塞:如果一个管道没有数据,不需要等待,可以轮询下一个管道是否有数据!
4.AIO表示异步非阻塞IO,服务器实现模式为一个有效请求一个线程,
客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。
异步:服务端线程接收到了客户端管道以后就交给底层处理它的io通信。
自己可以做其他事情。
非阻塞:底层也是客户端有数据才会处理,有了数据以后处理好通知服务器应用来启动线程进行处理。
小结:
各种模型应用场景:
BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,
JDK 1.4开始支持。
AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,
充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。
*/
public class NioDemo {
}