个人主页:Hello Code.
本文专栏:Java基础知识
更多相关内容请点击前往Java基础知识总结【目录】查看
如有问题,欢迎指正,一起学习~~
文章目录
- 网络编程入门
- IP
- InetAddress的使用
- 端口
- 协议
- UDP通讯程序
- UDP发送端
- UDP接收端
- UDP练习
- 三种通信方式
- TCP通讯程序
- 发送数据
- 接收数据
- 原理分析
- 三次握手
- 四次挥手
- 练习
- 服务端优化
网络编程入门
网络编程:在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
网络编程三要素
- IP地址:设备在网络中的地址,是唯一的标识
- 端口:应用程序在设备中唯一的标识
- 协议:数据在网络中传输的规则,常见的协议有UDP和TCP协议
IP:全称“互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
通过域名访问服务器->域名通过DNS服务器解析为IP地址传递回去->计算机再通过解析好的IP地址访问相应的服务器->服务器返回数据展示在浏览器上
IPv4
- 32bit(4字节):1 100000000 10101000 00000001 01000010
点分十进制表示法:192.168.1.66
IPv6
- 由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。
采用128位地址长度,分成8组,每16位分为一组
- 冒分十六进制表示法:2001:0DB8:0000:0023:0008:0800:200C:417A
省略前面的0:2001:DB8:0:23:8:800:200C:417A
- 特殊情况:如果计算出的16进制表示形式中间有多个连续的0(FF01:0:0:0:0:0:0:1101)
采用0位压缩表示法:FF01::1101
常用命令
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
为了方便我们对IP地址的获取和 *** 作,Java提供了一个类InetAddress供我们使用
InetAddress:此类表示Internet协议(IP)地址
常用方法
- static InetAddress getByName(String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
- String getHostName():获取此IP地址的主机名
- String getHostAddress():返回文本显示中的IP地址字符串
端口:应用程序在设备中唯一的标识
端口号:用两个字节表示的整数,它的取值范围是0~65535
其中0~1023之间的端口号用于一些知名的网络服务或者应用
在自己使用时使用1024以上的端口号就可以了
注意:一个端口号只能被一个应用程序使用
协议在计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议
用户数据报协议(User Datagram Protocol)
- UDP是面向无连接通信协议
速度快,有大小限制,一次最多发送64K,数据不安全,容易丢失数据
TCP协议
- 传输控制协议(Transmission Control Protocol)
- TCP协议是面向连接的通信协议
速度慢,没有大小限制,数据安全
UDP通讯程序
- 发送端:发送数据
- 接收端:接收数据
发送数据步骤
- 创建发送端的DatagramSocket对象
- 创建数据,并把数据打包(DatagramPacket)
- 调用DatagramSocket对象的方法发送数据
- 释放资源
代码实现
public class Demo{ public static void main(String[] args) throws IOException{ // 创建发送端对象 DatagramSocket ds = new DatagramSocket(); // 数据打包 DatagramPacket(byte[] buf, int length, InetAddress, int port) String s = "需要发送的数据"; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); // 发送数据 ds.send(dp); // 释放资源 ds.close(); } }UDP接收端
步骤
- 创建接收端的DatagramSocket对象
- 创建一个箱子,用于接收数据
- 调用DatagramSocket的方法接收数据并将数据放入箱子中
- 解析数据包,并把数据在控制台显示
- 释放资源
代码
public class Demo{ public static void main(String[] args) throws{ // 创建接收端对象------参数表示从10000端口接收数据 DatagramSocket ds = new DatagramSocket(10000); // 创建箱子 byte[] bytes = new bytes[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); // 接收数据 ds.receive(dp); // 获取数据 byte[] data = dp.getData(); System.out.println(new String(data)); // 释放资源 ds.close(); } }
UDP练习在这里运行时必须先运行接收端再运行发送端(否则都发送完了才运行接收端就接收不到了)
如果接收端在启动之后没有接收到数据,会阻塞
在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节
- UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
- UDP接收端:因为不知道发送端什么时候停止发送,就死循环接收
// 发送端 package UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Scanner; public class ClientDemo { public static void main(String[] args) throws IOException{ Scanner sc = new Scanner(System.in); DatagramSocket ds = new DatagramSocket(); while(true){ String s = sc.nextLine(); if("886".equals(s)) break; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); ds.send(dp); } ds.close(); } }
// 接收端 package UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class ServerDemo { public static void main(String[] args) throws IOException { DatagramSocket ds = new DatagramSocket(10000); while(true){ byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); ds.receive(dp); byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data, 0, length)); } // ds.close(); } }三种通信方式
单播:一个发送端通过一个路由器只发送给一个接收端(一对一)
组播:一个发送端通过一个路由器发送给一组接收端(一对多)
- 组播地址:224.0.0.0~239.255.255.255
其中224.0.0.0~224.0.0.255为预留的组播地址,不可用
- 组播的发送端在调用DatagramSocket对象的方法发送数据时,单播是发送给指定IP的电脑,组播是发送给组播地址
- 组播的接收端创建的是MulticastSocket对象
在创建箱子后,还要把当前电脑添加到这一组中
// 组播的发送端 public static void main(String[] args) throws IOException{ DatagramSocket ds = new DatagramSocket(); String s = "Hello Word"; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("224.0.1.0"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); ds.send(dp); ds.close(); }
// 接收端 public static void main(String[] args) throws IOException{ MulticastSocket ms = new MulticastSocket(10000); DatagramPacket dp = new DatagramPacket(new byte[1024], 1024); // 把当前计算机绑定一个组播地址 ms.joinGroup(InetAddress.getByName("224.0.1.0")); // 接收数据 ms.receive(dp); byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data, 0, length)); ms.close(); }
广播:一个发送端通过一个路由器发送给该局域网下所有接收端(一对所有)
- 广播地址:255.255.255.255
// 发送端 public static void main(String[] args) throws IOException{ DatagramSocket ds = new DatagramSocket(); String s = "广播发送端"; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("255.255.255.255"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); ds.sent(dp); ds.close(); }
// 接收端 public class ServerDemo { public static void main(String[] args) throws IOException { DatagramSocket ds = new DatagramSocket(10000); while(true){ byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); ds.receive(dp); byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data, 0, length)); } ds.close(); } }
TCP通讯程序
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
发送端:Socket 接收端:ServerSocket
发送数据通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
- 创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host, int port) - 获取输出流,写数据
OutputStream getOutputStream() - 释放资源
void close()
代码实现
// 客户端 public static void main(String[] args) throws IOException{ Socket socket = new Socket("127.0.0.1", 10000); OutputStream os = socket.getOutputStream(); os.write("hello".getBytes()); os.close(); socket.close(); }接收数据
- 创建服务端的Socket对象(ServerSocket)
ServerSocket(int port) - 监听客户端连接,返回一个Socket对象
Socket accept() - 获得输入流,读数据,并把数据显示在控制台
InputStream getInputStream() - 释放资源
void close()
代码实现
// 服务端 public static void main(String[] args){ ServerSocket ss = new ServerSocket(10000); Socket accept = ss.accept(); // 没有客户端连接的话,就会死等,不执行后续代码,即阻塞 InputStream is = accept.getInputStream(); int b; while((b = is.read()) != -1){ System.out.print((char) b); } is.close(); ss.close(); }原理分析
- 不能先运行客户端(先运行客户端会没有服务端可连,就会报错)
- 先运行服务端,代码会依次执行到accept方法,然后就阻塞,等待客户端连接
- 运行客户端,在对象创建完毕之后,客户端与服务端的连接通道就已经建立
客户端写数据,服务端读数据
- 客户端释放资源,服务端释放资源
三次握手客户端创建对象并连接服务器,此时是通过三次握手协议保证服务器之间的连接
针对客户端,是往外写的,所以是输出流;而服务端是往进读的,所以是输入流
read方法也是阻塞的
在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,会通过四次挥手协议保证连接终止
保证客户端与服务器之间建立连接
- 第一次:客户端向服务器发出连接请求(等待服务器确认)
- 第二次:服务器向客户端返回一个响应(告诉客户端收到了请求)
- 第三次:客户端向服务端再次发出确认信息(连接建立)
保证客户端与服务端取消连接,成功终止连接
- 第一次:客户端向服务器发出取消连接请求
- 第二次:服务器向客户端返回一个响应,表示收到客户端取消请求
服务器将最后的数据处理完毕 - 第三次:服务器向客户端发出确认取消信息
- 第四次:客户端再次发送确认消息(连接取消)
练习一
- 客户端:发送数据,接收服务器反馈
- 服务器:接收数据,给出反馈
- 代码
// 客户端 package TCP; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.net.UnknownHostException; import java.io.OutputStream; public class ClientDemo { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 10000); OutputStream os = socket.getOutputStream(); os.write("hello".getBytes()); socket.shutdownOutput(); // 仅仅关闭输出流,并写一个结束标记,对socket没有任何影响 InputStream is = socket.getInputStream(); int b; while ((b = is.read()) != -1) { System.out.print((char) b); } is.close(); os.close(); socket.close(); } }
// 服务端 package TCP; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.io.InputStream; public class ServerDemo { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10000); Socket accept = ss.accept(); InputStream is = (InputStream) accept.getInputStream(); int b; while ((b = is.read()) != -1) { System.out.print((char) b); } OutputStream os = accept.getOutputStream(); os.write("who?".getBytes()); os.close(); is.close(); accept.close(); ss.close(); } }
练习二
- 客户端:将本地文件上传到服务器,接收服务器的反馈
- 服务器:接收客户上传的文件,上传完毕之后给出反馈
- 代码
// 客户端 package TCP; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; public class ClientDemo2 { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 10000); // 本地的流,读取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png")); OutputStream os = socket.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(os); int b; while ((b = bis.read()) != -1) { bos.write(b); // 通过网络写到服务器中 } // 给服务器一个结束标记 socket.shutdownOutput(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line; while ((line = br.readLine()) != null) { System.out.println(line); } socket.close(); bis.close(); } }
// 服务器 package TCP; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo2 { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10000); Socket accept = ss.accept(); // 网络中的流,从客户端读取数据的 BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); // 本地的IO流,把数据写到本地中,实现永久化存储 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\copy.png")); int b; while ((b = bis.read()) != -1) { bos.write(b); } BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); bw.write("上传成功!"); bw.newline(); bw.flush(); accept.close(); ss.close(); bos.close(); } }
服务端优化
- 第一个弊端:服务器只能处理一个客户端请求,接受完一个文件之后,服务器就关闭了
改进方式:循环 - UUID
- UUID uuid = UUID.randomUUID(); 生成一个随机且唯一的uid
- uuid.toString(); 转换为字符串
- 防止多次上传文件时,后上传的文件覆盖掉先上传的文件
- 仅仅使用while循环,无法同时处理多个客户端的请求
采用多线程改进 - 使用多线程虽然可以让服务器同时处理多个客户端请求,但是资源消耗太大
采用线程池改进
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)