前言
网络编程就是计算机和计算机之间通过网络进行数据传输,下面介绍一些概念和如何实现UDP和TCP两种模式的传输。
一、常见的软件架构C/S和B/S
C/S架构需要一个客户端软件程序+服务器
B/S只需要打开网页+服务器
C/S架构的优缺点和应用场景
优点:画面可以做的非常精美,用户体验好
缺点:需要开发客户端,也需要开发服务端
用户需要下载和更新的时候太麻烦
应用场景:适合定制专业化的办公类软件如:IDEA、网游
B/S架构的优缺点和应用场景
优点:不需要开发客户端,只需要开发服务端
用户不需要下载,打开浏览器就能使用
缺点:如果应用过大,用户体验受到影响
应用场景:适合移动互联网应用,可以在任何地方随时访问的系统。
二、网络编程三要素(IP、端口号、协议)
1.概述
IP 设备在网络中的地址,是唯一的标识。
端口号 应用程序在设备中唯一的标识。
协议 数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp。
2.详说
IP
全称:Internet Protocol,是互联网协议地址,也称IP地址。
是分配给上网设备的数字标签。
通俗理解就是,上网设备在网络中的地址,是唯一的
常见的IP分类为 IPv4、IPv6
为解决IPv4的IP不够用的问题,出现了IPv6
IPv4的细节
分类:公网地址(万维网使用)和私有地址(局域网使用)。
192.168.开头的就是私有址址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用,以此节省IP
三个问
1.IPv6还未普及,现在如何解决IPv4不够的问题?
利用局域网IP解决IP不够的问题
2.特殊的IP是什么?
127.0.0.1(永远表示本机)
3.常见的两个CMD命令
ipconfig:查看本机IP地址
ping:检查网络是否连通
InetAddress类
InetAddress类可以获取IP对象,主机名,IP
public class InetAddressTest { public static void main(String[] args) throws UnknownHostException { //传递电脑的名字或网址都可以获得对应的IP地址 InetAddress address = InetAddress.getByName("LAPTOP-8DV5B4U6"); System.out.println(address); //获得本机的主机名 String hostName = address.getHostName(); System.out.println(hostName); //获得本机的IP地址 String hostAddress = address.getHostAddress(); System.out.println(hostAddress); } }
端口号
就是,应用程序在设备中唯一的标识。
端口号:由两个字节表示的整数,取值范围:0~65535
其中0~1023之间的端口号用于一些知名的网络服务或者应用。
我们自己使用1024以上的端口号就可以了。
注意:一个端口号只能被一个应用程序使用。
传输的数据只能由电脑绑定的端口号的端口发出和接收
协议
计算机网络中,连接和通信的规则被称为网络通信协议。
下面是传输层的两个协议UDP和TCP
三、UDP
1.发送数据
步骤
(1)创建发送端的DatagramSocket对象
(2)数据打包(DatagramPacket)
(3)发送数据
(4)释放资源
示例代码
public class SendDataTest { public static void main(String[] args) throws IOException { //1.创建UDP socket 即new 一个 DatagramSocket对象 //细节: //绑定端口,以后我们就是通过这个端口往外发送 //空参:所有可用的端口中随机一个进行使用 //带参:指定端口进行绑定 DatagramSocket socket = new DatagramSocket(); //2.打包数据 byte[] data = "你好厉害u".getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 10086; DatagramPacket packet = new DatagramPacket(data, data.length, address, port); //3.发送数据 socket.send(packet); //关闭资源 socket.close(); } }
2.接收数据
步骤
(1)创建接收端的DatagramSocket对象
(2)接收打包好的数据(DatagramPacket)
(3)解析数据包
(4)释放资源
示例代码
public class ReceiveDataType { public static void main(String[] args) throws IOException { // 1.创建一个DatagramSocket对象 //细节: //在接收的时候,一定要绑定端口 //而且绑定的端口一定要跟发送的端口保持一致 DatagramSocket socket = new DatagramSocket(10086); // 2.创建一个DatagramPacket对象接收数据 byte[] bytes = new byte[1024]; DatagramPacket packet = new DatagramPacket(bytes,bytes.length); //该方法是阻塞的 //程序执行到这一步的时候,会在这里死等 //等发送端发送消息 socket.receive(packet); //3.解析数据 byte[] data = packet.getData(); int len = packet.getLength(); String str = new String(data,0,len); InetAddress address = packet.getAddress(); int port = packet.getPort(); System.out.println("接收到的数据:"+str); System.out.println("这数据从"+address+"这台电脑中的"+port+"端口发出"); //关闭资源 socket.close(); } }
3.聊天室
按照下面的要求实现程序
UDP发送数据:娄数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
发送端代码
public class SendSide { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(); Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入要发送的信息:"); String s = scanner.nextLine(); if ("886".equals(s)){ break; } byte[] data = s.getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 10086; DatagramPacket packet = new DatagramPacket(data, data.length, address, port); socket.send(packet); } socket.close(); } }
idea里面修改发送代码的运行配置,改为允许多个实例跑
接收端代码
public class ReceiveSide { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(10086); byte[] data = new byte[1024]; DatagramPacket packet = new DatagramPacket(data, data.length); while (true) { socket.receive(packet); byte[] data1 = packet.getData(); int length = packet.getLength(); String str = new String(data1, 0, length); SocketAddress socketAddress = packet.getSocketAddress(); System.out.println("来自:" + socketAddress+"发出的一条信息:" + str ); } } }
4.三种通信方式(单播、组播、广播)
单播 上面的代码就是单薄
组播 组播地址:224.0.0.0~239.255.255.255 发送到的数据一组的主机都能收到
其中224.0.0.0~224.0.0.255为预留的组播地址广播 广播地址:255.255.255.255 发送到的数据所有的主机都能收到
(1)组播
发送数据步骤
1.创建MulticastSocket对象
2.创建DatagramPacket对象(这里的目的ip对象要指定组播地址比如224.0.0.1)
3.调用MulticastSocket发送数据方法发送数据
4.释放资源
接收数据步骤
1.创建MulticastSocket对象
2.将当前本机,添加到224.0.0.1的这一组当中
3.创建DatagramPacket对象
4.接收数据
5.解析数据
6.释放资源
实例代码
1.发送端代码
public class SendSide { public static void main(String[] args) throws IOException { //1.创建UDP socket 即new 一个 MulticastSocket对象 MulticastSocket socket = new MulticastSocket(); //2.打包数据 byte[] data = "你好厉害u".getBytes(); //指定接收端的组播ip InetAddress address = InetAddress.getByName("224.0.0.1"); //指定接收端的端口 int port = 10086; DatagramPacket packet = new DatagramPacket(data, data.length, address, port); //3.发送数据 socket.send(packet); //关闭资源 socket.close(); } }
2.接收端代码
public class ReceiveSide { public static void main(String[] args) throws IOException { // 1.创建一个MulticastSocket对象 //细节: //在接收的时候,一定要绑定端口 //而且绑定的端口一定要跟发送的端口保持一致 MulticastSocket socket = new MulticastSocket(10086); //2..将当前本机,添加到224.0.0.2的这一组当中 InetAddress address = InetAddress.getByName("224.0.0.2"); socket.joinGroup(address); // 3.创建一个DatagramPacket对象接收数据 byte[] bytes = new byte[1024]; DatagramPacket packet = new DatagramPacket(bytes,bytes.length); //4.接收数据 //该方法是阻塞的 //程序执行到这一步的时候,会在这里死等 //等发送端发送消息 socket.receive(packet); //5.解析数据 byte[] data = packet.getData(); int len = packet.getLength(); String str = new String(data,0,len); InetAddress src = packet.getAddress(); int port = packet.getPort(); System.out.println("接收到的数据:"+str); System.out.println("这数据从"+src+"这台电脑中的"+port+"端口发出"); //6.关闭资源 socket.close(); } }
设置接收端的代码的运行设置为允许多个实例运行。
多次运行接收端的代码,控制台多几个接收端的实例,几个接收实例创建就加入224.0.0.2的一个组播地址的组。只要发送端一发送消息,几个接收端都能收到。
(2)广播
这个广播只需要在单播的基础上,修改发送端发送包参数指定的目的ip地址。
修改后的发送端代码如下
发送后,无论是多个单播或组播的接收端都是能接收到的。但是端口号还是要对应。
四、TCP
1.客户端和服务端的步骤
2.实例代码
服务端代码
public class ServerType { public static void main(String[] args) throws IOException { //1.创建一个服务器ServerSocket ServerSocket serverSocket = new ServerSocket(10086); //2.监听客户端的连接 //没有客户端连接,会阻塞在这里 Socket socket = serverSocket.accept(); //3.获取输入流 InputStream inputStream = socket.getInputStream(); //4.读数据 int b; while ((b = inputStream.read()) != -1) { System.out.print((char) b); } //5.关闭流 inputStream.close(); socket.close(); } }
客户端代码
public class CilentType { public static void main(String[] args) throws IOException { //1.创建客户端socket Socket socket = new Socket("127.0.0.1", 10086); //2.获取输出流 OutputStream outputStream = socket.getOutputStream(); //3.写数据 outputStream.write("我是客户端".getBytes()); //4.关闭资源 outputStream.close(); socket.close(); } }
注意:上面的代码是使用字节流传输数据的,中文的话会乱码
因此需要将字节输入流转换为字符输入流,要修改服务端的代码
修改后的服务端的代码
public class ServerType { public static void main(String[] args) throws IOException { //1.创建一个服务器ServerSocket ServerSocket serverSocket = new ServerSocket(10086); //2.监听客户端的连接 //没有客户端连接,会阻塞在这里 Socket socket = serverSocket.accept(); //3.获取输入流 InputStream inputStream = socket.getInputStream(); //转换为字符流,避免出现中文乱码 InputStreamReader isr = new InputStreamReader(inputStream); //4.读数据 int b; while ((b = isr.read()) != -1) { System.out.print((char) b); } //5.关闭流 inputStream.close(); socket.close(); } }
运行结果正常
3.三次握手和四次挥手
(1)三次握手
(2)四次挥手
五、Demo
1.Demo1 多发多收
客户端代码
public class Client { public static void main(String[] args) throws IOException { //1.创建Socket对象并连接服务端 Socket socket = new Socket("127.0.0.1", 10086); Scanner sr = new Scanner(System.in); //2.获取输出流,发送数据 OutputStream outputStream = socket.getOutputStream(); while (true) { System.out.println("请输入要发送的数据:"); String str = sr.nextLine()+"\r\n"; if ("886".equals(str)){ break; } outputStream.write(str.getBytes()); } //3.释放资源 outputStream.close(); socket.close(); } }
服务端代码
public class Server { public static void main(String[] args) throws IOException { //1.创建一个服务器ServerSocket ServerSocket serverSocket = new ServerSocket(10086); //2.调用accept()方法,获取到请求的客户端Socket Socket socket = serverSocket.accept(); //3.获取输入流,读取客户端发送的数据 InputStreamReader isr = new InputStreamReader(socket.getInputStream()); int b; while((b = isr.read()) != -1) { System.out.print((char)b); } //4.关闭流和socket isr.close(); socket.close(); } }
2.Demo2 接收和反馈
客户端代码
public class CilentType { public static void main(String[] args) throws IOException { //1.创建客户端socket Socket socket = new Socket("127.0.0.1", 10086); //2.获取输出流 OutputStream outputStream = socket.getOutputStream(); //3.写数据 outputStream.write("我是客户端".getBytes()); //在此写一个结束标记 socket.shutdownOutput(); //4.接收服务端的回复 InputStreamReader isr = new InputStreamReader(socket.getInputStream()); int b; while ((b = isr.read()) != -1) { System.out.print((char) b); } //关闭资源 outputStream.close(); isr.close(); socket.close(); } }
服务端代码:
public class ServerType { public static void main(String[] args) throws IOException { //1.创建一个服务器ServerSocket ServerSocket serverSocket = new ServerSocket(10086); //2.监听客户端的连接 //没有客户端连接,会阻塞在这里 Socket socket = serverSocket.accept(); //3.获取输入流 InputStream inputStream = socket.getInputStream(); //转换为字符流,避免出现中文乱码 InputStreamReader isr = new InputStreamReader(inputStream); //4.读数据 int b; //细节: //read方法会从连接通道中读取数据 //如果客户端没有发送数据,会阻塞在这里 //需要一个结束标记,此处循环才会停止。 while ((b = isr.read()) != -1) { System.out.print((char) b); } //5.写回数据 String str = "我是服务端"; socket.getOutputStream().write(str.getBytes()); //关闭流 inputStream.close(); socket.close(); } }
注意:
read方法会从连接通道中读取数据
如果客户端没有发送数据,会阻塞在循环那里
需要一个结束标记,循环才会停止。
3.Demo3 上传文件
客户端代码
public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 10086); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("files\\xjj.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] buffer = new byte[1024]; int b; while ((b = bis.read(buffer)) != -1) { bos.write(buffer, 0, b); } bos.flush(); socket.shutdownOutput(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = br.readLine(); System.out.println(line); socket.close(); } }
服务端代码
public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(10086); Socket socket = serverSocket.accept(); BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy\\a.jpg")); int b; byte[] buf = new byte[1024]; while ((b = bis.read(buf)) != -1) { bos.write(buf, 0, b); } bos.flush(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("服务端接收到了数据!"); bw.newLine(); bw.flush(); socket.close(); serverSocket.close(); } }
注意注意注意:1.缓冲字节流是需要刷新的 2.流不要随意的关闭,不然就导致socket关闭了
4.Demo4 文件名重复问题
这道代码只需要在上一题的基础上,修改服务端的代码,保存文件的名字用下面的UUID类生成唯一的标识码。
5.Demo5 上传文件多线程版本
使用循环+多线程
服务端代码
public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(10086); while (true) { Socket socket = serverSocket.accept(); MyRunnable mr = new MyRunnable(socket); new Thread(mr).start(); } } }
线程类代码
public class MyRunnable implements Runnable{ Socket socket; public MyRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); String name = UUID.randomUUID().toString().replace("-", "")+".jpg"; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy\\"+name)); int b; byte[] buf = new byte[1024]; while ((b = bis.read(buf)) != -1) { bos.write(buf, 0, b); } bos.flush(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("服务端接收到了数据!"); bw.newLine(); bw.flush(); } catch (IOException e) { throw new RuntimeException(e); } finally { try { socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
客户端代码(和上面的一样)
public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 10086); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("files\\xjj.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] buffer = new byte[1024]; int b; while ((b = bis.read(buffer)) != -1) { bos.write(buffer, 0, b); } bos.flush(); socket.shutdownOutput(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = br.readLine(); System.out.println(line); socket.close(); } }
6.Demo6 线程池优化版本
在上一道题的代码的基础上,修改服务端的代码,增加一个线程池管理线程
修改后的服务端的代码
public class Server { public static void main(String[] args) throws IOException { //创建自定义线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3,//核心线程数 10,//最大线程数 60,//空闲时间 TimeUnit.SECONDS,//时间单位 new ArrayBlockingQueue<>(2),//阻塞队列 Executors.defaultThreadFactory(),//线程工厂 new ThreadPoolExecutor.AbortPolicy()//拒绝策略 ); ServerSocket serverSocket = new ServerSocket(10086); while (true) { Socket socket = serverSocket.accept(); MyRunnable mr = new MyRunnable(socket); threadPoolExecutor.submit(mr); } } }
7.Demo7 接收浏览器的信息并打印
这道题只需要服务端的代码,然后用浏览器去访问服务端
给个Demo1的服务端代码
public class Server { public static void main(String[] args) throws IOException { //1.创建一个服务器ServerSocket ServerSocket serverSocket = new ServerSocket(10086); //2.调用accept()方法,获取到请求的客户端Socket Socket socket = serverSocket.accept(); //3.获取输入流,读取客户端发送的数据 InputStreamReader isr = new InputStreamReader(socket.getInputStream()); int b; while((b = isr.read()) != -1) { System.out.print((char)b); } //4.关闭流和socket isr.close(); socket.close(); } }
先运行服务端的代码,再打开浏览器,输入127.0.0.1:10086访问服务端
服务端在控制台输出