网络编程:指网络上的主机,通过不同的进程,以编程的方式实现网络通信(数据通信本质是网络数据传输),其目的就是为了获取网络资源。
如何来理解呢?我们只需要记住是不同进程就可以。即便是同一台主机,只要是不同进程之间进行传输数据就属于网络编程。但是需要注意的是,一个进程获取资源,另外一个进程提供资源。
发送端和接收端(发送端和接收端只是相对的,只是一次网络数据传输产生数据流之后的概念)
发送端:源主机
接收端:目的主机
收发端:既可以进行发送也可以接收数据
请求和响应
一般来讲,获取一个网络资源,涉及到两次网络数据传输:①请求数据发送 ②响应数据发送
比如,主机A给主机B发送消息之前,会请求主机B与它建立连接,之后主机B返回给它响应(同意建立连接)。
客户端和服务端
服务端:提供服务的一方
客户端:获取服务资源,并且可以保存资源在服务端。
举个栗子:在银行办业务
银行提供存款服务:用户(客户端)保存资源(现金)在银行(服务端)
银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管的现金)
常见的客户端服务端模型
核心流程:
①Client发送请求到Server
②Server根据请求,执行响应的业务处理
③Server返回响应,发送业务处理结果到Client
④Client根据响应数据,展示处理结果(展示获取到的资源/保存资源)
Socket套接字:用于网络通信的技术,一般我们使用Socket进行网络程序开发,即网络编程。
其主要针对传输层协议划分为三类:流套接字、数据报套接字、原始套接字。
使用传输层TCP协议;
TCP:即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点:
使用传输层UDP协议;
UDP:即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点:
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。(这不是我们的重点)
面试题:TCP协议和UDP协议的区别
参考:TCP和UDP的区别
①报头不同 ②协议不同 ③特点不同
操作系统把网络编程的一些相关操作(访问网络核心的硬件设备/网卡驱动等)封装起来了,提供了一组API供开发者使用。
对于UDP协议来讲,具有无连接、面向数据报的特征,即每次都是没有建立连接,一次发送全部数据报一次接受全部数据报。java中使用UDP协议通讯,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramPacket描述一个发送或接收数据报。(DatagramSocket类描述一个socket对象,本质是一个文件描述符,表示网卡设备的文件,通过读写socket文件的方式操作网卡)
对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,具体流程如下:
DatagramSocket是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket构造方法:
方法 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字Socket,绑定到主机任意一个随机端口(客户端不需要绑定端口号) |
DatagramSocket(int port) | 创建一个UDP数据报套接字Socket,绑定到本机指定的端口(服务器) |
DatagramSocket方法:
方法 | 说明 |
---|---|
void receive(DatagramPacket p) | 套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待,如果接收到数据,会返回一个DatagramPacket对象) |
void send(DatagramPacketp) | 套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字(UDP中的Socket生命周期跟随整个程序,所以close()方法不太需要关闭,如果进程结束就会释放相应资源) |
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:
方法 | 说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号 |
DatagramPacket 方法:
方法 | 说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress ( SocketAddress 的子类 )构造方法:
方法 | 说明 |
---|---|
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
以下两个小栗子,体现了UDP通信的业务逻辑,快来试试吧❤
回显服务器:虽然是网络通信流程,但实际上仍是一台主机之间进行通信
回显服务器没有第③部分,相当于客户端请求是啥,返回给客户端的响应就是啥!
服务器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package UDPEcho; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchoServer { //创建socket对象(数据报套接字) private DatagramSocket socket = null; //服务器启动时需绑定一个端口号,收到数据时用于发送响应到某一个进程 //端口号实际上是两个字节的无符号整型数据,0-65535 public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); } //启动服务器 public void start() throws IOException { System.out.println("启动程序!"); //服务器一般是持续运行(24h*7) while(true){ //1.读取请求,服务器一般不知道客户端啥时候发来请求 //receive()参数DatagramPacket是一个输出型参数,socket中读到的数据会设置到这个参数的对象中 //DatagramPacket在构造的时候需要一个缓冲区(实际上是一段内存空间, 通常使用byte[]) DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); socket.receive(requestPacket); //收到请求之前,receive()操作在阻塞等待! //把requestPacket中的内容取出来,作为一个字符串 String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //2.根据请求计算响应 String response = process(request); //3.构造responsePacket响应 //此处设置的参数长度 必须是 字节的长度个数!response.getBytes().length //如果直接取response.length,则是字符串的长度,也就是字符串的个数 //当前的responsePacket在构造时,需要指定这个包要发给谁;发送给的目标即发来请求的一方 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress()); //4.发送响应到客户端 socket.send(responsePacket); //5.打印日志(格式化字符串--后面的参数和类型都必须一致) String log = String.format("[%s:%d] request:%s,response:%s", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); System.out.println(log); } } //此处的process()方法负责的功能是根据请求来计算响应 //由于当前是一个回显服务器,就是把客户端发来的请求,服务器发回即可! private String process(String request) { return request; } public static void main(String[] args) throws IOException { UdpEchoServer server = new UdpEchoServer(9090); server.start(); } } |
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package UDPEcho; import java.io.IOException; import java.net.*; import java.util.Scanner; public class UdpEchoClient { //创建一个socket对象 private DatagramSocket socket = null; private String serverIp; private int serverPort; //此处的参数ip和port是要连接的服务器的ip和port //客户端一启动的时候就需要知道服务器的IP和port,然而服务器启动时是无法知道客户端的ip和port的, //直到客户的请求到了,服务器才知道对应客户端的ip和port public UdpEchoClient(String serverIp, int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; this.socket = new DatagramSocket(); //客户端在构造方法中,不需要指定端口号(自己的端口号) //如果当前的DatagramSocket构造方法中没有指定端口的话,操作系统会自动分配一个空闲的端口号给客户端使用(随机分配) } public void start() throws IOException { //1.从标准输入读入一个数据 Scanner scanner = new Scanner(System.in); while(true){ String request = scanner.nextLine(); if(request.equals("goodbye")){ System.out.println("goodbye!"); return; } //2.把字符串构造成一个UDP请求,并发送数据! //这个DatagramPacket当中既要包含具体的数据,又要包含这个数据发送给谁 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort); socket.send(requestPacket); //3.尝试从服务器这里读取响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096); socket.receive(responsePacket); String response = new String(responsePacket.getData(),0,responsePacket.getLength()); //4.显示结果 String log = String.format("request:%s,response:%s", request, response); System.out.println(log); } } public static void main(String[] args) throws IOException { //127.0.0.1 环回IP(loopback),客户端和服务器在同一台主机上 //如果在不同主机上,此时就要写成对应服务器的IP UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090); client.start(); } } |
服务器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package UDPDict; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.util.HashMap; /*实现汉译英功能,客户端输入的请求是英文单词,返回的响应是对应的中文解释。 */ public class UdpDictServer { //创建一个socket对象 private DatagramSocket socket = null; private HashMap<String, String> dict = new HashMap<>(); //带一个参数的构造函数(服务器需手动指定一个port,用于描述客户端向服务器指定端口发送请求) public UdpDictServer(int port) throws SocketException { //socket绑定端口 socket = new DatagramSocket(port); //初始化hash表 dict.put("hello", "你好"); dict.put("cat", "小猫"); dict.put("dog", "小狗"); } //启动服务端程序 public void start() throws IOException { System.out.println("服务器启动!"); while(true){ //接收请求receive(),如果没有数据请求过来就一直阻塞等待,直到接收到数据请求 DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024); socket.receive(requestPacket); //解析请求(取出请求的内容) String request = new String(requestPacket.getData(),0,requestPacket.getLength()); //处理回响 String response = process(request); //构造响应,并发送至客户端(需要指定responsePacket包要发送给谁) DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); socket.send(responsePacket); //打印日志 String log = String.format("[%s:%d],request:%s,response:%s", requestPacket.getAddress().toString(), requestPacket.getPort(), request,response); System.out.println(log); } } //根据请求计算响应(核心操作) private String process(String request) { //TODO:英汉互译,具体实现就是 查表(类似于数据库) //以 查内存的哈希表 为例 //如果使用hashMap.get()方法,遇到表中没有的单词,即返回null return dict.getOrDefault(request, "[单词在字典中不存在!]"); } public static void main(String[] args) throws IOException { UdpDictServer udpDictServer = new UdpDictServer(9090); udpDictServer.start(); } } |
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package UDPDict; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner; public class UdpDictClient { private DatagramSocket socket = null; private int serverPort; private String serverIp; public UdpDictClient(String serverIp, int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; socket = new DatagramSocket();//端口随机分配,IP本机地址 } public void start() throws IOException { //标准输入一个数据 Scanner scanner = new Scanner(System.in); while(true){ String request = scanner.nextLine(); if(request.equals("goodbye")){ System.out.println("goodbye!退出程序!"); return; } //封装为一个请求packet,并发送!(需要指定要发送的服务器的IP和port) DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp), serverPort); socket.send(requestPacket); //解析响应并显示 DatagramPacket responsePacket = new DatagramPacket(new byte[2048],2048); socket.receive(responsePacket); String response = new String(responsePacket.getData(),0,responsePacket.getLength()); String log = String.format("request:%s,response:%s",request,response); System.out.println(log); } } public static void main(String[] args) throws IOException { UdpDictClient udpDictClient = new UdpDictClient("127.0.0.1", 9090); udpDictClient.start(); } } |
ღ( ´・ᴗ・` )比心~
from:https://blog.csdn.net/fxt579810/article/details/123567128