一、网络编程入门
1、软件架构
- C/S:QQ、迅雷
- B/S
- 共同点:都离不开网络的支持
- 网络编程:在一定的协议下,实现两台计算机通信
2、网络通信协议
- 通信协议:需遵守的规则,只有遵守才能通信
- 主要包括:传输格式、传输速率、传输步骤
- TCP/IP协议:
- 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol)
- 是Internet最基本、最广泛的协议
- 内部包含了一系列处理数据通信的协议,并采用四层分层模型(下层为上层提供服务)
- 核心层:网络层,将数据分组,并将分组后的传输到计算机网络中
3、协议分类
- UDP:用户数据报协议(User Datagram Protocol)
- 无需连接,数据直接发送,传输快,不可靠,数据被限制在64kb内
- 例子:QQ聊天、在线视频会议
- TCP:传输控制协议 (Transmission Control Protocol)
- 使用三次握手建立双端连接,可靠无差错的数据传输,数据安全
- 例子:文件传输,网页浏览、看视频
4、网络编程三要素
- 组成:协议、IP地址、端口号(标识唯一设备)
- IP地址:
- IPV4:32位二进制,地址资源有限
- IPV6:128位,8组16进制
- 常用命令:ipconfig,特殊ip
- 端口号(标识设备的多个进程)
- 利用协议 + IP地址 + 端口号三元组,与其他进程交互
- 0-65536,普通程序使用1024以上的
- 常用端口号:
- http/Nginx:80
- https:443
- Tomcat:8080
- Oracle:1521
- Redis:6379
二、TCP应用程序
1、概述
- TCP通信实现两端(客户端(Client)与服务端(Server))的数据交互
- 两个套接字类:Socket发请求/ServerSocket(构造传递端口)响应请求
- 步骤:
- 先启动服务器端,经过三次握手
- 客户端请求服务器端
- 建立连接,通过IO对象(字节流对象)通信
- 服务器使用客户端的流与客户端发送数据和回写数据
- 说明:
- 与多个服务器端交互,可以使用accept获取指定对象:Socket s1=server.accept()
2、代码实现
- 服务器端代码
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建服务器ServerSocket对象和系统要指定的端口号
ServerSocket server=new ServerSocket(8888);
//2.使用SeverSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket=server.accept();
//3.使用Socket对象中的方法getInputStream读取网络字节输入流InputStream对象
InputStream is=socket.getInputStream();
//4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
byte[] bytes=new byte[1024];
//读取一次就够了
int len=is.read(bytes);
System.out.println(new String(bytes,0,len));
//5.使用Socket对象中的方法getOutputStream()读取网络字节输出流OutputStream对象
OutputStream os=socket.getOutputStream();
//6.使用网络字节输出流OutputStream对象中的方法write,往客户端回写数据
os.write("收到,谢谢".getBytes());
//6.释放资源(socket)
socket.close();
server.close();
}
}
- 客户端代码
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建客户端对象,构造方法绑定服务器端
Socket socket=new Socket("127.0.0.1",8888);
//只有客户端,抛出异常java.net.ConnectException
//2.使用Socket对象中的方法getOutputStream()读取网络字节输出流OutputStream对象
OutputStream os=socket.getOutputStream();
//3.使用网络字节输出流OutputStream对象中的方法write,往服务器发送数据
os.write("你好,服务器".getBytes());
//4.使用Socket对象中的方法getInputStream读取网络字节输入流InputStream对象
InputStream is=socket.getInputStream();
//5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
byte[] bytes=new byte[1024];
//读取一次就够了
int len=is.read(bytes);
System.out.println(new String(bytes,0,len));
//6.释放资源(socket)
socket.close();
}
}
三、综合案例:文件上传下载
1、原理
- 注意:
- 与硬盘读写,自己创建字节流对象
- C/S读写,使用socket创建字节流对象
- IO流对象使用Socket套接字
2、客户端
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
FileInputStream fis=new FileInputStream("c:\\1.jpg");
//2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
Socket socket=new Socket("127.0.0.1",8888);
//3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
OutputStream os=socket.getOutputStream();
//4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
int len=0;
byte[] bytes=new byte[1024];
//循环读取文件
while((len=fis.read(bytes))!=-1){
//5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
//6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
InputStream is=socket.getInputStream();
//7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
//8、释放资源(FileInputstream,Scoket)
fis.close();
socket.close();
}
}
3、服务器端(先运行)
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server=new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket socket=server.accept();
//3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is=socket.getInputStream();
//4、判断d:\\upload文件夹是否存在,不存在则创建
File file=new File("d:\\upload");
if (!file.exists()){
file.mkdirs();
}
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos=new FileOutputStream(file+"\\1.jpg");//不加\\文件会上传至D盘根目录,作为upload1.jpg文件
//6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len=0;
byte[] bytes=new byte[1024];
while((len=is.read(bytes))!=-1){
//7、使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
//只调用一次,不创建对象
//9、使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10、释放资源(FileOutputStream,Socket,ServerSocket)
socket.close();
server.close();
}
}
4、文件上传案例阻塞问题
从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。
结束标志:读取到-1结束,不会读取到-1,也不会把结束标志写入服务器
服务器读取不到,就会进入阻塞状态,一直死循环等待结束标记
- 解决:上传完文件,给服务器一个结束标记
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
FileInputStream fis=new FileInputStream("c:\\1.jpg");
//2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
Socket socket=new Socket("127.0.0.1",8888);
//3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
OutputStream os=socket.getOutputStream();
//4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
int len=0;
byte[] bytes=new byte[1024];
//循环读取文件
while((len=fis.read(bytes))!=-1){
//5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
//6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
/*
写结束标记
net中的方法 java.net.Socket
void shutdownOutput() 禁用此套接字的输出流。
禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。
* */
socket.shutdownOutput();//告诉服务器,输出流已经结束
InputStream is=socket.getInputStream();
System.out.println("33333333333333333333333");
//7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
System.out.println("44444444444444444444444");
//8、释放资源(FileInputstream,Scoket)
fis.close();
socket.close();
}
}
5、文件上传案例优化
- 文件命名:新建命名规则
- 循环接收:accept方法放到死循环中,同时不关闭服务器端ServerSocket对象
- 多线程提高效率:上传文件的操作放入run中,每上传一个文件,开启一个线程
- 服务器端程序
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server=new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
/*
让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传文件,就保存一个文件
* */
while (true) {
Socket socket=server.accept();
/*
使用多线程提高程序的效率
只要有一个客户端上传文件,就开启一个线程,完成文件的上传
* */
new Thread(new Runnable() {
@Override
public void run() {
//开启一个线程,完成文件的上传
try {
//3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4、判断d:\\upload文件夹是否存在,不存在则创建
File file = new File("d:\\upload");
if (!file.exists()) {
file.mkdirs();
}
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
/*
自定义一个文件的命名规则,防止同名的文件覆盖
规则:域名+毫秒值+随机数
* */
String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt() + ".jpg";
FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
//6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1) {
//7、使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes, 0, len);
}
//8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
//9、使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10、释放资源(FileOutputStream,Socket,ServerSocket)
socket.close();
}
catch (IOException e){
e.printStackTrace();
System.out.println(e);
}
}
}).start();
}
//服务器不再关闭
//server.close();
}
}
- 客户端程序
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
FileInputStream fis=new FileInputStream("c:\\1.jpg");
//2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
Socket socket=new Socket("127.0.0.1",8888);
//3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
OutputStream os=socket.getOutputStream();
//4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
int len=0;
byte[] bytes=new byte[1024];
//循环读取文件
while((len=fis.read(bytes))!=-1){
//5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
//6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
/*
写结束标记
net中的方法 java.net.Socket
void shutdownOutput() 禁用此套接字的输出流。
禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。
* */
socket.shutdownOutput();//告诉服务器,输出流已经结束
InputStream is=socket.getInputStream();
//7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
//8、释放资源(FileInputstream,Scoket)
fis.close();
socket.close();
}
}
四、模拟B/S服务器
1、分析
只要访问该端口的指定地址,就访问页面头的路径,将html内容写回到客户端
- 使用浏览器作为客户端
- 目的:服务器给客户端回写一个信息,回写一个html文件,即需要读取该文件
2、服务器端
package com.liujinhui.Day1206Net.BSTCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
创建BS版本TCP服务器
* */
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建一个服务器ServerScoket,和系统要指定的端口号
ServerSocket server =new ServerSocket(8080);
//使用accept方法,获取到请求的客户端对象(浏览器)
Socket socket=server.accept();
//使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is=socket.getInputStream();
//使用网络字节输入流InputStream对象中的方法read读取客户端的请求信息
/* byte[] bytes=new byte[1024];
int len=0;
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}*/
//把is网络字节输入流对象,转换为字符缓冲输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//读取客户端请求信息的第一行GET /MyJavaProject/web/index.html HTTP/1.1
String line=br.readLine();
System.out.println(line);
//把读取的信息进行切割,只要中间的部分
String[] arr = line.split(" ");
//把路径前面的/去掉,进行截取
String htmlPath = arr[1].substring(1);
System.out.println(htmlPath);
//根据此路径读取此文件
//创建一个本地字节输入流,绑定读取的html路径
//项目根目录不一样!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
FileInputStream fis=new FileInputStream("web/index.html");
//使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os=socket.getOutputStream();
//写入HTTP协议响应头,固定写法【html中会讲】
os.write("HTTP/1.1 200 0K\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
//必须要写入空行,否则浏览器不解析
os.write("\r\n".getBytes());
//一读一写复制文件,把服务器读取的html文件写到客户端
int len=0;
byte[] bytes=new byte[1024];
while((len=fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
//释放资源
fis.close();
socket.close();
server.close();
//浏览器访问http://127.0.0.1:8080/MyJavaProject/web/index.html
//打印一堆信息,客户端是IE的浏览器
}
}
- 优化:多线程一直保持监听,保证浏览器可以正常读取图片
package com.liujinhui.Day1206Net.BSTCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
创建BS版本TCP服务器
* */
public class TCPServerThread {
public static void main(String[] args) throws IOException {
//创建一个服务器ServerScoket,和系统要指定的端口号
ServerSocket server =new ServerSocket(8080);
/*
浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片
我们需要让服务器一直处于监听状态,客户端请求一次,服务器就回写一次
* */
while(true){
//一直处于监听状态
Socket socket=server.accept();
new Thread(new Runnable() {
@Override
public void run() {
try{
//使用accept方法,获取到请求的客户端对象(浏览器)
//使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is=socket.getInputStream();
//使用网络字节输入流InputStream对象中的方法read读取客户端的请求信息
/* byte[] bytes=new byte[1024];
int len=0;
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}*/
//把is网络字节输入流对象,转换为字符缓冲输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//读取客户端请求信息的第一行GET /MyJavaProject/web/index.html HTTP/1.1
String line=br.readLine();
//打印请求的路径
System.out.println(line);
//把读取的信息进行切割,只要中间的部分
String[] arr = line.split(" ");
//把路径前面的/去掉,进行截取
String htmlPath = arr[1].substring(1);
//根据此路径读取此文件
//创建一个本地字节输入流,绑定读取的html路径
FileInputStream fis=new FileInputStream(htmlPath);
//使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os=socket.getOutputStream();
//写入HTTP协议响应头,固定写法【html中会讲】
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
//必须要写入空行,否则浏览器不解析
os.write("\r\n".getBytes());
//一读一写复制文件,把服务器读取的html文件写到客户端
int len=0;
byte[] bytes=new byte[1024];
while((len=fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
//释放资源
fis.close();
socket.close();
}
catch(IOException e){
System.out.println(e);
}
}
}).start();
}
//server.close();
//浏览器访问http://127.0.0.1:8080/web/index.html
//打印一堆信息,客户端是IE的浏览器
}
}