【Java SE进阶】Day11 网络编程、TCP应用程序
一、网络编程入门
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的浏览器 } }
本文来自博客园,作者:哥们要飞,转载请注明原文链接:https://www.cnblogs.com/liujinhui/p/14839241.html