1. 网络编程概述
1.1 C/S和B/S
C/S
客户端 服务器软件结构
服务提供商给予用户服务需要准备的内容1. 各大平台的客户端Android iOS PC Windows Linux macOSQQ 微信 淘宝 JD 剑与远征2. 服务器提供服务软件更新:LOL服务器版本更新,同时本地软件也要进行更新操作。这个操作非常耗时。热更新
B/S
浏览器 服务器软件结构
服务提供商只要提供数据服务就OK,以及前端数据展示方式1. 浏览器提供商非常非常多谷歌,火狐,欧朋,Safari,Edge2. 服务器提供服务软件更新:服务器更新数据,浏览器刷新就ok了
1.2 网络通信协议
协议:
protocol协议
网络通信协议是要求双方传递数据的计算机必须遵守的,按照对应的网络传输协议,才可以进入数据的交互和传递。
目前网络段数据传输比较常见的协议:
UDPTCP/IP
1.3 UDP和TCP/IP区别
UDP
1. 面向无连接,数据传递不算特别安全
2. 因为面向无连接,传输速度快
3. 因为面向无连接,数据传递存在丢包问题
4.UDP没有客户端和服务器区别,都可以作为发送端和接收端,相互的
UDP协议使用场景
直播,网络游戏
实时的大部分都是UDP
TCP/IP
1. 面向连接,数据传递较为安全
2. 因为面向连接,所有传递速度较慢
3. 面向连接,数据传递有保障
4.TCP/IP协议是有明确的服务器和客户端概念
TCP/IP协议使用场景
客户端登陆,数据下载,文件传输
一个软件肯定是混合协议的,不是单独的。
1.4 网络编程的三要素
1. 协议
两个在于网络情况下的计算机数据传递,都需要对应的协议来完成。
2. IP地址
Internet Protocol Address
当前计算机在网络中的一个地址编号,类似于手机号号码
IP地址有IPv4协议和IPv6协议
IPv4是一个32位的二进制数,通常展示效果是a.b.c.d 例如 192.168.1.1
a.b.c.d 各代表0 ~ 255的数字,目前已经消耗殆尽 42亿个
IPv6
IPv6是能够保证地球上的每一粒沙子都有一个IP地址。
128位地址长度,16字节一组
8组 0x0 ~ 0xFFFF
3. 端口号
端口号是当前应用程序在计算机中的一个编号。可以让计算机明确知道,当前的数据是给予哪一个程序使用,或者数据从哪一个程序出现的。
端口号是一个short类型 0 ~ 65535
0~1024不能用于自定义端口号使用,特定的系统端口号
2.IP类
SUN公司提供给开发使用的IP地址类
InetAddress
常用方法:
InetAddress getLocalhost();
获取本机IP地址类对象
InetAddress getByName(String str);
根据指定的主机名获取对应的IP地址对象
InetAddress[] getAllByName(String str);
获取指定主机名,或者域名对应的所有IP地址类对象
代码演示:
package com.qfedu.a_ip;import .InetAddress;import .UnknownHostException;/** IP类演示*/public class Demo1 {public static void main(String[] args) throws UnknownHostException {InetAddress localHost = InetAddress.getLocalHost();System.out.println(localHost);InetAddress byName = InetAddress.getByName("DESKTOP-M89SDP7");System.out.println(byName);InetAddress byName2 = InetAddress.getByName("");System.out.println(byName2);System.out.println("----------------------------------");InetAddress[] allByName = InetAddress.getAllByName("");for (InetAddress inetAddress : allByName) {System.out.println(inetAddress);}System.out.println("----------------------------------");InetAddress[] allByName1 = InetAddress.getAllByName("");for (InetAddress inetAddress : allByName1) {System.out.println(inetAddress);}System.out.println("----------------------------------");InetAddress[] allByName2 = InetAddress.getAllByName("");for (InetAddress inetAddress : allByName2) {System.out.println(inetAddress);}}}
3.UDP协议数据传输
3.1 UDP数据传输方式
User Datagram Protocol
数据传递采用数据包方式传递,所有的数据要进行打包操作,并且没有对应的客户端服务器概念,有且只有发送端和接收端
Socket 套接字
数据需要进行传递操作,在数据传递的两台计算机当中必须有对应的Socket。这里采用UDP协议,那么必须有一个UDP协议的Socket
DatagramSocket();创建一个发送端UDP协议Socket对象DatagramSocket(int port);创建一个接收端UDP协议的Socket对象,这里需要【监听】指定端口
发送端数据包的打包方法:
DatagramPacket DatagramPacket(byte[] buf, int length, InetAddress address, int port);
buf: 需要传递数据的字节数组
length:是当前字节数组中数据容量字节数
address:接收端IP地址对象
port: 接收端对应的端口号
接收端数据包接收方式
这里需要准备一个空的数据包
DatagramPacket DatagramPacket(byte[] buf, int length);
buf: 字节缓冲数组,通常是1024整数倍
length: 当前字节缓冲数组的容量
3.2 发送端
流程:
1. 创建UDP服务器对应的发送端Socket
2. 准备对应数据包,需要带有指定数据
3. 发送数据 send
4. 关闭UDP发送端
代码如下:
package udp;import java.io.IOException;import .DatagramPacket;import .DatagramSocket;import .InetAddress;/*流程:1. 创建UDP服务器对应的发送端Socket2. 准备对应数据包,需要带有指定数据DatagramPacket DatagramPacket(byte[] buf, int length, InetAddress address, int port);buf: 需要传递数据的字节数组length:是当前字节数组中数据容量字节数address:接收端IP地址对象port: 接收端对应的端口号3. 发送数据 send4. 关闭UDP发送端*/public class SenderDemo1 {public static void main(String[] args) throws IOException {//标记一下System.out.println("发送端启动");//1. 创建UDP服务器对应的发送端SocketDatagramSocket datagramSocket = new DatagramSocket();//准备对应数据包,需要带有指定数据byte[] bytes= "发送端:我来朵蜜你了!!!".getBytes();DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8848);datagramSocket.send(packet);datagramSocket.close();}}
3.3 接收端
流程:
1. 打开UDP服务,并且监听指定端口
2. 创建新的空数据包
3. 通过Socket接收数据
4. 关闭UDP服务接收端
package udp;import java.io.IOException;import .DatagramPacket;import .DatagramSocket;/*流程:1. 打开UDP服务,并且监听指定端口2. 创建新的空数据包3. 通过Socket接收数据 4. 关闭UDP服务接收端*/public class ReciveDemo1 {public static void main(String[] args) throws IOException {//标记System.out.println("接收端打开");//1. 打开UDP服务,并且监听指定端口DatagramSocket datagramSocket = new DatagramSocket(8848);//2. 创建新的空数据包byte[] bytes = new byte[1024];DatagramPacket packet = new DatagramPacket(bytes, bytes.length);//3. 通过Socket接收数据datagramSocket.receive(packet);//4.确定接收到的字节长度int length = packet.getLength();System.out.println(new String(bytes, 0, length));//5. 关闭UDP服务接收端datagramSocket.close();}}
3.4 UDP数据传递丢失问题
这是udp的缺点之一,因为面向无连接,所以可能会丢数据,类似于玩电脑游戏丢包,瞬移,卡顿。
网络不够好,稳定性不行,带宽不够电脑性能不好
3.5 FeiQ
网络传输都有自己的传输规格,如果软件接受到的数据是自己的规格,那么可以读取数据
如果不是,丢弃!!!
FeiQ
version:time:sender:ip:flag:content
版本:时间:发送者名字:发送人IP:标记:内容
数据是一个String类型
而且使用的协议是UDP协议
import java.io.IOException;import .DatagramPacket;import .DatagramSocket;import .InetAddress;public class FeiQ {public static void main(String[] args) throws IOException {DatagramSocket socket = new DatagramSocket();String data = getData("Welcome to Java World!!!");DatagramPacket datagramPacket = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getByName("192.168.31.255"), 2425);socket.send(datagramPacket);socket.close();}/*** 传入数据,转换成FeiQ可以识别的数据* version:time:sender:ip:flag:content* * @param message 字符串类型内容* @return 符合FeiQ格式要求的字符串*/public static String getData(String message) {StringBuilder stb = new StringBuilder();stb.append("1.0:");stb.append(System.currentTimeMillis() + ":");stb.append("Anonymous:");stb.append("10.1.1.1:");stb.append("32:");stb.append(message);return stb.toString();} }
4. TCP
4.1 TCP概述
TCP相对于UDP比较稳定的传输协议,这里存在三次握手,保证连接状态,同时有明确的客户端和服务端之分
TCP服务中需要服务器端先启动,需要监听指定端口,等待客户端连接。
客户端主动连接服务器,和服务器连接之后,才可以进行数据交互,服务器不能主动连接客户端的。
TCP操作而言,Java中提供了两个Socket
服务端Socket
.ServerSocket;
创建对应的ServerScoket开启服务器,等待客户端连接
客户端Socket(这个比较重要)
.Socket
创建客户端Scoket,并且连接服务器,同时将Socket发送给服务器绑定注册。
4.2 Socket 客户端Socket
给客户端提供数据传输的符合TCP/IP要求的Socket对象
构造方法 Constructor
Socket(String host, int port);
host是服务器IP地址,port对应服务器程序的端口号
通过指定的服务器IP地址和端口号,获取TCP连接对象
成员方法 Method
InputStream getInputStream(); 获取Socket对象输入字节流,可以从服务器获取对应的数据 InputStream是一个资源,需要在程序退出是关闭 Read
OutputStream getOutputStream(); 获取Sokcet对象输出字节流,可以发送数据到服务器 OutputStream是一个资源,需要在程序退出是关闭 Write
void close();
关闭客户端Socket
void shutdownOutput();
禁止当前Socket发送数据
TCP/IP协议对应的Socket是给予IO流实现的。
4.3 ServerSocket服务端Socket
在服务端开启Socket服务器
构造方法 Constructor:
ServerSocket(int port); 开启ServerSocket服务器,并且明确当前服务端口是谁
成员方法 Method:
Socket accept(); 监听并且连接,得到一个Socket对象,同时该方法是一个阻塞方法,会处于一个始终的监听状态 返回的是Socket,也就是客户端Socket对象,获取到当前Socket对象,相对于获取到客户端连接,同时使用的Socket和客户端一致。
4.6 TCP协议代码演示
4.6.1 服务器代码
流程:
1. 创建ServerSocket服务器,同时监听指定端口
2. 通过accept方法获取Socket连接,得到客户端Socket对象
3. 通过Socket对象,获取InputStream,读取客户端发送数据
4. 通过Socket对象,获取OutputStream,发送数据给客户端
5. 关闭服务
代码如下:
package com.qfedu.c_tcp;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import .ServerSocket;import .Socket;/*流程:1. 创建ServerSocket服务器,同时监听指定端口2. 通过accept方法获取Socket连接,得到客户端Socket对象3. 通过Socket对象,获取InputStream,读取客户端发送数据4. 通过Socket对象,获取OutputStream,发送数据给客户端5. 关闭服务 */public class TcpServer1 {public static void main(String[] args) throws IOException {System.out.println("服务器启动");System.out.println("-----------------------");// 1. 创建ServerSocket服务器,同时监听指定端口ServerSocket serverSocket = new ServerSocket(8848);// 2. 通过accept方法获取Socket连接,得到客户端Socket对象Socket socket = serverSocket.accept();// 3. 通过Socket对象,获取InputStream,读取客户端发送数据InputStream inputStream = socket.getInputStream();// IO流操作byte[] buf = new byte[1024];int length = inputStream.read(buf);System.out.println(new String(buf, 0, length));// 4. 通过Socket对象,获取OutputStream,发送数据给客户端OutputStream outputStream = socket.getOutputStream();String str = "欢迎来到德莱联盟";outputStream.write(str.getBytes());// 5. 关闭Socket服务 同时关闭当前Socket使用的输入字节流和输出字节流// Closing this socket will also close the socket's InputStream and OutputStream. socket.close();}}
4.6.2 客户端代码
流程:
1. 创建Socket服务,同时明确连接服务器的IP地址和对应端口号
2. 通过Socket对象,获取对应的OutputStream对象,发送数据给服务器
3. 通过Socket对象,获取对应的InputStream对象,接收服务器发送数据
4. 关闭服务
代码如下:
package com.qfedu.c_tcp;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import .Socket;import .UnknownHostException;/*流程:1. 创建Socket服务,同时明确连接服务器的IP地址和对应端口号2. 通过Socket对象,获取对应的OutputStream对象,发送数据给服务器3. 通过Socket对象,获取对应的InputStream对象,接收服务器发送数据4. 关闭服务*/public class TcpClient1 {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端启动");System.out.println("------------------------");// 1. 创建Socket服务,同时明确连接服务器的IP地址和对应端口号Socket socket = new Socket("192.168.31.154", 8848);// 2. 通过Socket对象,获取对应的OutputStream对象,发送数据给服务器OutputStream outputStream = socket.getOutputStream();outputStream.write("你好服务器!!!".getBytes());// 3. 通过Socket对象,获取对应的InputStream对象,接收服务器发送数据InputStream inputStream = socket.getInputStream();byte[] buf = new byte[1024];int length = inputStream.read(buf);System.out.println(new String(buf, 0, length));// 4. 关闭服务socket.close();}}
4.6.3 代码总结
在这里只是一个小的演示,传递的只有一句话,比较小,所以用一个1024的字节数组就可以接收信息了。
下边来传输比较大的文件,会用到之前的IO流操作。
4.7 文件上传操作
4.7.1 分析过程
4.7.2 客户端程序
流程:
1. 创建对应文件的输入字节流操作,这里可以使用缓冲
2. 启动Socket
3. 获取Socket输出OutputStream对象,发送数据给服务器
4. 边读边发
5. 当文件读取结束,发送完毕,关闭客户端
代码如下:
package fileupload;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.OutputStream;import .InetAddress;import .Socket;public class tcpClient {public static void main(String[] args) throws IOException {// 1. 创建对应文件的输入字节流操作,这里可以使用缓冲BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("d:/aaa/1.mp4")));//2. 启动Socket,Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),8848);//3. 获取Socket输出OutputStream对象,发送数据给服务器OutputStream outputStream = socket.getOutputStream();int length = -1;byte buf[] = new byte[1024 * 8];// 4. 读取数据,发送数据while ((length = bis.read(buf)) != -1) {outputStream.write(buf,0,length);}// 5.关闭资源socket.close();bis.close();}}
4.7.3 服务端程序
流程:
1. 开启服务端服务,创建ServerSocket对象
2. 明确保存文件的位置,创建对应文件夹的输出缓冲字节流
3. 读取数据,写入文件
4. 关闭服务器
代码如下:
package fileupload;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import .ServerSocket;import .Socket;/*流程:1. 开启服务端服务,创建ServerSocket对象2. 明确保存文件的位置,创建对应文件夹的输出缓冲字节流3. 读取数据,写入文件4. 关闭服务器*/public class tcpServer {public static void main(String[] args) throws IOException {//1. 开启服务端服务,创建ServerSocket对象ServerSocket serverSocket = new ServerSocket(8848);//获取客户端socket,合体Socket accept = serverSocket.accept();// 2. 明确保存文件的位置,创建对应文件夹的输出缓冲字节流BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File("d:/aaa/temp.mp4")));// 3. 获取Socket对应的输入流InputStream inputStream = accept.getInputStream();int length = -1;byte[] bytes = new byte[1024*8];// 4. 边读边写while((length = inputStream.read(bytes)) != -1) {bufferedOutputStream.write(bytes,0,length);}//关闭资源bufferedOutputStream.close();serverSocket.close();accept.close();}}
这里用到了IO流操作,用于写入读取文件。如果有看不懂的可以回顾一下IO博客。IO流
4.7.4 目前服务端代码问题
在上边的代码中,我们存在一些逻辑问题
保存的文件名都是一致的,无法保存多个文件。
这里可以考虑使用UUID作为文件名
服务端没有这么low,代码肯定不能执行完一个上传功能就结束
同理,服务端代码不可能只有一个上传文件功能
在这里多线程可以很好地解决问题
解决问题如下:
服务端代码优化如下:
package fileupload;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import .ServerSocket;import .Socket;import java.util.UUID;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class BetterTcpServer {public static void main(String[] args) throws IOException {System.out.println("服务端代码启动");// 1. 启动TCP服务端服务ServerSocket serverSocket = new ServerSocket(8848);// 2. 使用线程池ExecutorService pool = Executors.newFixedThreadPool(5);while (true) {Socket accept = serverSocket.accept();pool.submit(() -> {System.out.println(Thread.currentThread().getName());try {//设置文件名,用UUID来设置随机不重复文件名并且把下划线取消掉String filename = UUID.randomUUID().toString().replaceAll("-", "");//新建缓冲输出字节流,并且确定文件存放位置BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File("d:/aaa/temp/",filename + ".mp4")));//获取客户端传来的inputstreamInputStream inputStream = accept.getInputStream();//边读边写int length = -1;byte[] bus = new byte[1024*8];while((length = inputStream.read(bus)) != -1) {bufferedOutputStream.write(bus);}// 5. 关闭资源bufferedOutputStream.close();accept.close();//serverSocket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}});}}}
手速还可以,截了张图
可以看到,实现了多线程操作,而且可以储存多分一样的文件,文件名使用UUID随机。也可以看到,代码运行的时候,我的网速跑到了十几M每秒,代码优化成功。
我的博客即将同步至腾讯云+社区,邀请大家一同入驻
链接