编辑
阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录
前引:本文代码建议反复敲打至少3遍!!!!
一:引入
1:网络编程的基本概念
网络编程就是通过写代码来完成基于网络的跨主机通信
本质上是学习传输层给应用层提供的一系列的API,通过API把数据交给传输层,在经过层层封装之后,通过网卡,把数据发送出去。
这里使用的API是传输层提供的,传输层涉及到的主要协议有两个:TCP和UDP
后面通过代码详细理解这些特点
编辑
①有/无连接:这里的“连接”是虚拟的,抽象的连接
例:打电话——打过去对方接才能通话;微信发消息不管对面在不在线,消息都发出去了
②可/不可靠传输:可以知道数据到没到达对方
③面向字节流:传输数据的基本单位就是字节
④面向数据报:传输的的基本单位是一个“数据报”——由一系列字节构成的特定结构UDP
⑤全双工:可以双向通信
⑥半双工:只能单向通信
客户端:发送请求的一方,发送的数据叫做“请求”
服务器:被动接受请求的一方,给客户端返回的数据叫做“响应”
客户端和服务器之间的交互有这几种模式:“一问一答”,“一问多答”,“多问一答”,“多问多答”,
二:UDP socket API使用
引入:UDP核心类有两个DatagramSocket,DatagramPacket
1:socket文件
操作系统中有一类文件就叫做socket文件,区别于放在硬盘上的普通文件,socket文件是放在“网卡”这样的硬件设备上——通过网卡发送数据,就是写socket文件;接受数据,就是读socket文件
2:DatagramSocket类
(1)构造方法
编辑
(2)方法
编辑
3:DatagramPacket类
UDP面向数据报,每次发送和接收数据的基本单位就是一个UDP数据报
(1)构造方法
编辑
下面这个图建议读完代码后再进行理解
编辑
(2)方法
编辑
三:回显服务器——服务器
1:引入(必看)
以下代码是实现一个“回显服务器”——是网络编程中的“hello world”,但是对新手小白并不友好
大致流程为:客户端发出请求,服务器收到客户端的请求,完成业务并返回响应,客户端接受响应
先附赠上服务器的代码,(按照步骤去边理解边敲)(重在理解,重在理解)
2:服务器响应代码
(1)注释版本
package network; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-10-07 * Time: 17:20 */ public class UdpEchoServer { //1:创建DatagramSocket对象 private DatagramSocket socket = null;//socket:插槽插座 //2:构造方法,服务器一启动,就要关联/绑定上一个操作系统中的端口号(服务器的端口号哦啊通常由程序员指定) //抛出的异常通常为scoket创建失败 public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port);//端口号port } //3:服务器启动逻辑 //5:抛异常,网络编程socket本质上也是IO public void start() throws IOException { System.out.println("服务器启动"); while(true){ //3:创建一个数据报,它是每次发送数据和接收数据的一个基本单位 // 参数为输出型参数,字节数组,对应UDP的载荷部分,一开始是空的 DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); //4;socket读网卡能读到一个UDP数据报(里面会包含数据源IP,源端口),放到requestPacket(数据报)对象中 //如果没有接受到请求那么就会阻塞等待,此处收到的数据是先存到socket文件的内存缓冲区中。非硬盘 socket.receive(requestPacket); //5:基于字节数组构造String对象,数组中的数据可能是文本数据(给String刚好)也可能是二进制数据(Java也能保存) //这里的长度是有效长度 String request = new String(requestPacket.getData(),0,requestPacket.getLength()); //6:根据请求计算响应 String response = process(request); //7:把响应(String)字节给拎出来,构造成一个DatagramPacket数据报,在传入socket对象(因为4中我们说过收到请求时,socket能读到源IP和源端口) //这时我们把这个源IP和源端口,作为响应的目的IP和目的端口(确认客户端的发出请求的位置),返回回去 //注:response.length()单位是字符,此处单位是字节 DatagramPacket responsePacket = new DatagramPacket(response.getBytes() , response.getBytes().length , requestPacket.getSocketAddress()); socket.send(responsePacket); } } //7:构造响应,这里是回显服务器,所以就是单纯的return private String process(String request) { return request; } }
(2)无注释版本
package repeat2; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-10-10 * Time: 18:28 */ public class UdpServer { private DatagramSocket socket = null; public UdpServer(int port) throws SocketException { socket = new DatagramSocket(port); } public void start() throws IOException { System.out.println("服务器启动"); while(true){ DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); socket.receive(requestPacket); //转化为字符串 String request = new String(requestPacket.getData(),0,requestPacket.getLength()); String response = process(request); DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress()); socket.send(responsePacket); System.out.printf("[%s,%d] request: %s response: %s\n ", requestPacket.getAddress().toString(),requestPacket.getPort(),request,response); } } public String process(String request){ return request; } public static void main(String[] args) throws IOException { UdpServer server = new UdpServer(9096); server.start(); } }
3:细节补充
这里在补充一些细节:
①创建完socket对象后,可以通过socket对象来操作网卡(具体:因为socket对象是在内存中的,针对这个内存对象进行操作就可以影响到网卡)
②构造方法(还有无参的上面有写)中的参数,是我们指定的,进程一启动就要绑定上操作系统中的端口号,这个端口号是一个整数,用来区分一个主机上的进行网络通信的不同程序
一个端口号只能被一个进程绑定,但是一个进程可以绑定多个端口号(像极了爱情~~,爱情中宁死不做端口号~~)
编辑
③SocketException,类似端口号被别的进程占用就会报异常
④因为服务器需要不停的运作,所以while一直循环
⑤数据报中的数组就是载荷部分
编辑
⑥receive有阻塞等待功能
⑦ 编辑
⑧数据报的位置在socket对象的内存缓冲区中
编辑
⑨构造响应数据报
4:特点
上述代码可以体现出UDP是——
(1)无连接通信
UDP的DAtagramSocket自身并不需要保存对端的IP和端口,对端IP和端口在数据报中就已经包含,另外代码中也没有“建立连接”和“接受连接”这种操作
(2)不可靠数据——代码没体现(略)
(3)面向数据报
send方法和receive方法都是以DatagramPacket为基本单位的
(4)全双工
一个socket既可以发送数据报又可以接受数据报(属于是自力更生了)
四:回显服务器——客户端
1:代码
(1)注释版本
package network; import java.io.IOException; import java.net.*; import java.util.Scanner; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-10-07 * Time: 17:20 */ public class UdpEchoClient { private DatagramSocket socket = null; private String serverIp; private int serverPort; public UdpEchoClient(String serverIp , int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; socket = new DatagramSocket();//传入参数为请求的目的ip和目的端口,源ip为本机ip源端口为系统分配的端口 } public void start() throws IOException { System.out.println("客户端启动"); Scanner scanner = new Scanner(System.in); while(true){ System.out.print("->"); if (!scanner.hasNext()){//如果没有输入的话就结束循环 break; } String request = scanner.next();//人为输入请求 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort);//指定服务器的ip和端口,分开的 socket.send(requestPacket); DatagramPacket responsePacket = new DatagramPacket(new byte[4096] , 4096); socket.receive(responsePacket); //转化为字符串 String response = new String(responsePacket.getData(),0,responsePacket.getLength());//内置getLength()获取length成员变量 System.out.println(response); } } public static void main(String[] args) throws IOException { UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090); client.start(); } }
(2)无注释版本
package repeat2; import java.io.IOException; import java.net.*; import java.util.Scanner; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-10-10 * Time: 18:29 */ public class UdpEchoClient { private DatagramSocket socket = null; private String serverIp; private int serverPort; public UdpEchoClient(String serverIp , int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; socket = new DatagramSocket(); } public void start() throws IOException { System.out.println("客户端启动"); Scanner scanner = new Scanner(System.in); while(true){ System.out.print("->"); if(!scanner.hasNext()){ break; } String request = scanner.next(); DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp),serverPort); socket.send(requestPacket); //接收响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096] , 4096); socket.receive(responsePacket); String response = new String(responsePacket.getData(),0,responsePacket.getLength()); System.out.println(response);// } } public static void main(String[] args) throws IOException { UdpEchoClient client = new UdpEchoClient("127.0.0.1",9096); client.start(); } }
2:注意点
(1)服务器端口号需要手动指定,是确保客户端可以找到服务器,客户端的端口号是操作系统的内核随机分配的 (确保分配的端口号是空闲可用的)
(2)Scanner从控制台读取字符串最好使用next非nextLine
(如果是从文件读取就无所谓了)
①next读取
编辑
编辑
②nextLine读取
编辑 编辑
(3).length和.length()方法的区别
编辑
编辑
五:回显服务器过程文字梳理
编辑
六:知识补充
1:.length和.length()的区别
引用文章java中length和length()的区别_length变量与length函数 java-CSDN博客
编辑
2:字符串转数组
编辑
编辑
七:易错的地方
构造String用getData(),有0-多少范围,
构造DatagramPacket用getBytes(),范围为getBytes().length
服务器获取客户端地址用:.getSocketAddress()
客户端获取服务器地址用:InetAddress.getByName(),serverPort
编辑
编辑
编辑 编辑 编辑