˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客系列专栏:xiaoxie的网络>计算机网络学习系列专栏——CSDN博客●'ᴗ'σσணღ
我的目标:"团团等我💪( ◡̀_◡́ ҂)"( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝+关注(互三必回)!
目录
一.网络编程
1.什么是网络编程
2.网络编程中的基本概念
1.发送端 vs 接收端
编辑 2.客户端vs服务端
3.请求 vs 响应
二.Socket套接字
1.概念
2.分类
1.流套接字:基于传输层TCP协议
2.数据报套接字:基于传输层UDP协议
其中UDP的特点为:
3.简单的对比一下UDP协议和TCP协议
3.Java数据报套接字通信模型
1.对于⼀次发送及接收UDP数据报的流程如下:
编辑
2.提供多个客⼾端的请求处理及响应,流程如下:
3.TCP的简单模型
编辑
4..UDP数据报套接字编程
1.UdpEchoClient 类(客户端)
2.UdpEchoServe 类(服务器端)
3.主函数
4.交互过程
5.TCP数据报套接字编程
1.TcpEchoServe 类(服务器端)
2.TcpEchoClient 类(客户端)
3.主函数
4.交互过程
5.解释一下这里为什么要用scanner以及为什么要加换行符 \n
1.使用 Scanner 的原因:
2.加入换行符 \n 的原因:
3.注意事项:
一.网络编程
1.什么是网络编程
是指网络上的主机,通过不同的进程以编程的⽅式实现⽹络通信.
当然,我们也可以是在一台主机,只要是在不同的进程中,就可以通过网络通信来传输资源,不一定需要在多台主机上实现.同时并不代表只能是从进程B到进程A,而是从发送方,到接收方,进程A可以是发送方,也可以是接收方,进程B同理.
2.网络编程中的基本概念
1.发送端 vs 接收端
2.客户端vs服务端
服务端:服务端通常是一个长期的、稳定的进程,它等待客户端的请求并提供相应的服务或数据。
客户端:获取服务的一方进程,称为客⼾端。
- 请求-响应模式:客户端向服务端发送请求,服务端处理请求后返回响应。这是最常见的交互模式。
- 推送模式:服务端也可以主动向客户端推送信息,但这种情况较少见,通常用于实时通信或通知。例如我们使用手机时,像一些什么新闻的APP的后台(服务端),就会时不时的给我们(客户端)发送一些热点新闻的推送,或者是我们微信接收信息也可以说是,服务端给我们推送消息.
网络编程中的角色
- 在网络编程中,开发者需要同时考虑客户端和服务端的实现。服务端的实现更注重稳定性、性能和安全性,而客户端的实现则更注重用户体验和界面设计。
分布式系统:
在分布式系统中,客户端和服务端的概念可以更加复杂。一个系统可能包含多个服务端,它们之间通过内部通信协同工作,共同为客户端提供服务
总的来说,服务端和服务端在网络通信中扮演着不同的角色,它们通过网络协议相互协作,共同为用户提供服务。理解客户端和服务端的区别和联系,对于设计和实现网络应用非常重要。
3.请求 vs 响应
请求:请求是客户端(如浏览器、服务器或其他客户端程序)发送给服务端的一条消息,请求服务端执行某项操作或提供某些数据。
响应:响应是服务端对客户端请求的答复。服务端处理完请求后,会返回一个响应消息,告知客户端操作的结果或提供请求的数据。
二.Socket套接字
1.概念
2.分类
Socket套接字主要针对传输层协议划分为如下三类:
1.流套接字:基于传输层TCP协议
其中TCP的特点为:
1.有连接
2.可靠传输
3.面向字节流
4.全双工
2.数据报套接字:基于传输层UDP协议
其中UDP的特点为:
1. 无连接
2.不可靠传输
3.面向数据报
4.全双工
3.简单的对比一下UDP协议和TCP协议
这只是简单对比,后续的详细的解释,在后面文章在展示
1.有连接 vs 无连接
- 建立连接:在数据传输之前,通信双方必须建立一个连接。这通常涉及到一个建立连接的过程,如TCP协议中的三次握手。
- 无连接:在数据传输之前,通信双方不需要建立连接。每个数据包(或称为数据报)独立发送。
2.不可靠传输 vs 可靠传输
- 不可靠传输:指的是在数据传输过程中,不保证数据包的完整性、顺序性或及时性。发送方发送数据后,无法确保数据包是否能够到达接收方,或者数据包是否按发送顺序到达。
- 可靠传输:指的是在数据传输过程中,尽可能的确保数据包正确、完整、按顺序地到达接收方。如果数据在传输过程中出现丢失、错误或乱序,可靠传输机制会采取措施进行恢复
3.面向字节流 vs面向数据报
-
面向字节流(Stream-oriented):
- 数据被视为一个连续的字节序列,无边界。
- 通常用于需要可靠传输的应用,如文件传输和网页浏览。
- 例子:TCP(传输控制协议)。
-
面向数据报(Datagram-oriented):
- 数据被分割成独立的数据报,每个数据报有明确边界。
- 适用于对实时性要求高的应用,如在线游戏和VoIP。
- 例子:UDP(用户数据报协议)。
选择面向字节流还是面向数据报的通信方式,取决于应用程序的具体需求。如果数据的可靠性和顺序性至关重要,面向字节流的通信方式(如TCP)是更好的选择。如果实时性和性能更为关键,面向数据报的通信方式(如UDP)可能更合适。在实际应用中,开发者需要根据应用的特点和需求,权衡可靠性、实时性、资源消耗等因素,选择合适的通信方式。
4. 全双工
全双工是指在通信中,发送和接收可以同时进行,即两端的通信实体可以在同一时刻进行发送和接收操作,无需轮流占用通信线路。这种通信方式允许数据在两个方向上独立、连续地流动,从而提高了通信效率。
3.Java数据报套接字通信模型
1.对于⼀次发送及接收UDP数据报的流程如下:
-
发送端 (客户端):
- 构建
DatagramPacket
:在发送UDP数据报之前,客户端需要构建一个DatagramPacket
对象。这个对象将包含要发送的数据,以及接收方的IP地址和端口号。
- 构建
-
接收端 (服务端):
- 构建
DatagramSocket
:服务端需要构建一个DatagramSocket
,这个套接字会绑定到一个端口上,监听到来的数据报。 - 接收UDP数据报:服务端使用
DatagramSocket
来接收客户端发送的UDP数据报。在接收数据报时,DatagramSocket
会阻塞,直到有数据报到达。
- 构建
-
DatagramPacket
对象:- 发送端的
DatagramPacket
包含要发送的数据(以byte数组的形式),以及接收方的IP地址和端口号。 - 接收端的
DatagramPacket
在接收数据前是一个空对象,它将被用来填充接收到的数据,以及发送方的IP和端口号。
- 发送端的
-
发送和接收过程:
- 发送端使用
socket.send(packet)
方法发送填充好的DatagramPacket
。 - 接收端调用
socket.receive(packet)
方法,传入一个空的DatagramPacket
对象,该对象在接收到数据后会被更新。
- 发送端使用
2.提供多个客⼾端的请求处理及响应,流程如下:
-
发送端 (客户端):
- 构建
DatagramSocket
:用于发送UDP数据报。 - 构建
DatagramPacket
:包含要发送的数据(byte数组)、目标IP地址和端口号。 socket.send(packet)
:通过DatagramSocket
发送DatagramPacket
。
- 构建
-
接收端 (服务端):
- 构建
DatagramSocket
:用于接收UDP数据报,并绑定到一个端口上。 socket.receive(packet)
:使用DatagramSocket
接收数据报,该方法会阻塞直到数据报到达。服务端需要提供一个空的DatagramPacket
对象来接收数据。- 更新
DatagramPacket
:一旦接收到数据,DatagramPacket
对象会被更新,包含接收到的数据以及发送方的IP地址和端口号。
- 构建
-
通信过程:
- 客户端发送一个UDP数据报(请求)给服务端。
- 服务端接收到数据报后,根据接收的数据执行业务逻辑。
- 服务端根据业务逻辑的需要,可能需要发送一个UDP数据报(响应)回客户端。
- 客户端接收到响应后,根据响应数据执行下一步的业务。
-
注意点:
socket.receive(packet)
方法会阻塞,直到从网络中收到一个数据报或者发生超时。- UDP是无连接的协议,不保证数据的可靠传输,不保证数据包的顺序,也不提供拥塞控制
3.TCP的简单模型
-
服务端(Server):
- 创建
ServerSocket
:服务端首先创建一个ServerSocket
对象,该对象绑定到一个特定的端口上并监听客户端的连接请求。 accept()
方法:服务端调用ServerSocket
的accept()
方法,该方法会阻塞,直到有客户端发起连接请求。
- 创建
-
客户端(Client):
- 创建
Socket
:客户端创建一个Socket
对象,指定服务端的IP地址和服务端监听的端口号。 - 向服务端发起请求:客户端通过
Socket
对象向服务端发起连接请求。
- 创建
-
建立连接:
- 一旦服务端的
ServerSocket
通过accept()
方法接受到连接请求,就会创建一个新的Socket
对象来与客户端建立通信。 - 此时,服务端和客户端都拥有一个
Socket
对象,它们可以通过这个Socket
对象进行通信。
- 一旦服务端的
-
通信过程:
InputStream
和OutputStream
:服务端和客户端的Socket
对象都提供了InputStream
和OutputStream
,用于双向通信。- 客户端和服务端可以通过
OutputStream
写入数据到对方,并通过InputStream
读取来自对方的数据。
-
结束通信:
- 通信完成后,双方需要关闭它们的
Socket
对象以及相关的InputStream
和OutputStream
,以释放网络资源。
- 通信完成后,双方需要关闭它们的
-
关闭资源:
- 服务端和客户端都应确保在通信结束时调用
close()
方法来关闭Socket
、InputStream
和OutputStream
,以避免资源泄露。
- 服务端和客户端都应确保在通信结束时调用
7.注意:
这里的建立连接和断开连接只是简单介绍,在TCP协议中,一个连接的建立通常遵循三次握 手过程,而在通信结束后,双方可以通过四次握手来优雅地关闭连接.这里就不展开说明了,
后续会对这个进行详细的介绍和解释
4..UDP数据报套接字编程
结合上图我们就可以自己写一个简单的只是回显字符串的服务器和客户端了
服务端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServe {private DatagramSocket socket = null;public UdpEchoServe(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while(true){//1.接收请求并解析DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);//从客服端接收请求,如果没有请求,即阻塞等待//将请求解析成字符串String request = new String(requestPacket.getData(),0, requestPacket.getLength());//经过process方法计算请求得出响应String response = this.process(request);//将响应传给客服端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getAddress(), requestPacket.getPort());socket.send(responsePacket);System.out.printf("[%s:%d],req = %s,resp = %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);}}//这个请求的响应为回显public String process(String request) {return request;}public static void main(String[] args) throws IOException{UdpEchoServe udpEchoServe = new UdpEchoServe(8023);//这个端口号可以自己自定义,建议写1024以上的发展被占用udpEchoServe.start();}
}
客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serveIp;private int servePort;public UdpEchoClient(String serveIp,int servePort) throws SocketException {socket = new DatagramSocket(0);this.serveIp = serveIp;this.servePort = servePort;}public void start() throws IOException {System.out.println("客服端启动");Scanner scanner = new Scanner(System.in);while (true) {//输入请求System.out.println("请输入你要发送的请求:");String request = scanner.next();//将请求封装成数据报DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,InetAddress.getByName(serveIp),servePort);//发送请求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 udpEchoClient = new UdpEchoClient("127.0.0.1",8023);udpEchoClient.start();}}
结果如下图
以下是对这两个类的功能和它们之间交互的总结:
1.UdpEchoClient 类(客户端)
- 构造函数:创建一个
DatagramSocket
并在构造时绑定到一个随机端口(0)。 - start 方法:
- 客户端启动并进入一个无限循环。
- 使用
Scanner
从标准输入读取用户输入的请求字符串。 - 将请求字符串封装进
DatagramPacket
对象,并指定服务器的 IP 地址和端口号。 - 使用
socket.send()
方法发送请求数据报到服务器。 - 创建一个空的
DatagramPacket
对象用于接收响应,并通过socket.receive()
方法阻塞等待服务器的响应。 - 将接收到的响应数据报转换为字符串并打印出来。
2.UdpEchoServe 类(服务器端)
- 构造函数:创建一个
DatagramSocket
并绑定到指定的端口(例如 8023)。 - start 方法:
- 服务器启动并进入一个无限循环。
- 创建一个空的
DatagramPacket
对象用于接收客户端的请求,并通过socket.receive()
方法阻塞等待客户端的请求。 - 将接收到的请求数据报转换为字符串。
- 调用
process()
方法处理请求,由于是回显服务器,它简单地将请求字符串作为响应返回。 - 将响应字符串封装进一个新的
DatagramPacket
对象,并发送回客户端。 - 打印出请求和响应的信息,包括客户端的 IP 地址、端口号、请求内容和响应内容。
3.主函数
UdpEchoClient
的main
方法:创建UdpEchoClient
实例并启动客户端。UdpEchoServe
的main
方法:创建UdpEchoServe
实例并启动服务器。
4.交互过程
- 服务器启动并监听指定端口,等待客户端请求。
- 客户端启动并提示用户输入请求,然后将请求发送给服务器。
- 服务器接收到请求后,将其作为响应发送回客户端。
- 客户端接收到响应并打印出来,然后等待下一次请求。
这个简单的 UDP 回显客户端和服务器示例展示了 UDP 协议的基本使用,包括数据报的发送和接收。UDP 协议的特点是无连接、低延迟,但不保证数据的可靠传输。
5.TCP数据报套接字编程
服务端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServe {private ServerSocket socket = null;public TcpEchoServe(int port) throws IOException{socket = new ServerSocket(port);}public void start() throws IOException{System.out.println("服务器启动");//接收客户端的请求ExecutorService poll = Executors.newCachedThreadPool();while(true) {Socket clintSocket = socket.accept();
//不使用线程池而是就使用多线程
// Thread t = new Thread(()-> {
// //建立连接
// try {
// connectClint(clintSocket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// t.start();poll.submit(new Runnable() {@Overridepublic void run() {try {connectClint(clintSocket);} catch (IOException e) {throw new RuntimeException(e);}}});}}public void connectClint(Socket clintSocket) throws IOException{//打印一个日志告知服务器客户端连接上了System.out.printf("[%s:%d,客服端上线\n",clintSocket.getInetAddress(),clintSocket.getPort());//使用字节流的方式来传输数据try(InputStream clintInput = clintSocket.getInputStream();OutputStream clintOutput = clintSocket.getOutputStream()) {Scanner scanner = new Scanner(clintInput);//客服端每次和服务端连接有可能有多个请求while(true) {//如果后续没有数据了,就说明断开连接了if(!scanner.hasNext()) {System.out.printf("[%s:%d,客服端下线\n",clintSocket.getInetAddress(),clintSocket.getPort());break;}String result = scanner.next();//把等到的请求,进行分析计算返回给响应String response = process(result);//把响应输出给客服端clintOutput.write(response.getBytes(),0,response.getBytes().length);//服务器打印结果日志System.out.printf("%s:%d] req=%s, resp=%s\n",clintSocket.getInetAddress(),clintSocket.getPort(),result,response);}}catch (IOException e) {e.printStackTrace();}finally {clintSocket.close();}}public String process(String result) {//给响应这里后面加上一个换行符, 使客户端读取响应的时候, 也有明确的分隔符return result + "\n";}public static void main(String[] args) throws IOException{TcpEchoServe tcpEchoServe = new TcpEchoServe(8023);tcpEchoServe.start();}
}
客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serveIp,int servePort)throws IOException {//和服务端连接socket = new Socket(serveIp,servePort);}public void start(){System.out.println("服务器启动!");Scanner scanner = new Scanner(System.in);//从服务端读取数据和输入数据try(InputStream serveInput = socket.getInputStream();OutputStream serveOutput = socket.getOutputStream()) {//从服务端接收数据Scanner serveScanner = new Scanner(serveInput);while (true){System.out.println("输入你要传输的数据");String result = scanner.next();//加换行result += "\n";//将数据传输给客服端serveOutput.write(result.getBytes(), 0, result.getBytes().length);if (!serveScanner.hasNext()) {break;}String response = serveScanner.next();System.out.println(response);}}catch (IOException e) {e.printStackTrace();}}public static void main(String[] args)throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",8023);tcpEchoClient.start();}
}
结果:
1.TcpEchoServe 类(服务器端)
-
构造函数:创建并绑定一个
ServerSocket
到指定端口,监听客户端的连接请求。 -
start 方法:
- 服务器启动并进入一个无限循环,等待客户端的连接请求。
- 使用
socket.accept()
方法接受客户端的连接请求,该方法会阻塞直到有客户端连接。 - 使用
Executors.newCachedThreadPool()
创建线程池,为每个客户端连接创建新线程处理。 - 调用
connectClint()
方法处理与客户端的连接。
-
connectClint 方法:
- 打印客户端上线日志。
- 创建
InputStream
和OutputStream
对客户端进行读写操作。 - 使用
Scanner
从输入流中读取客户端发送的数据。(阻塞等待) - 如果读取到数据,通过
process()
方法处理数据(在这里是简单的回显)。 - 将处理后的数据写入输出流,发送回客户端。
- 打印请求和响应日志。
- 当客户端断开连接或发生异常时,关闭
Socket
并打印客户端下线日志。
2.TcpEchoClient 类(客户端)
- 构造函数:创建一个
Socket
对象,尝试连接到指定IP地址和端口的服务端。 - start 方法:
- 客户端启动并进入一个无限循环。
- 使用
Scanner
从标准输入读取用户输入的数据。 - 将用户输入的数据发送到服务端,数据后添加了换行符
\n
作为分隔符。 - 创建
InputStream
和OutputStream
对服务端进行读写操作。 - 从服务端的输入流中读取响应数据,并打印出来。
- 如果服务端关闭连接,循环结束。
3.主函数
TcpEchoServe
的main
方法:创建TcpEchoServe
实例并启动服务器。TcpEchoClient
的main
方法:创建TcpEchoClient
实例并启动客户端。
4.交互过程
- 服务器启动并监听指定端口,等待客户端连接。
- 客户端启动并尝试连接到服务器。
- 服务器接受客户端的连接请求,并创建新线程处理该连接。
- 客户端向服务器发送数据,服务器通过
process()
方法处理数据(回显),并将数据发送回客户端。 - 客户端接收到服务器的响应并打印出来。
- 当客户端输入结束后,关闭连接。服务器在客户端关闭连接后释放资源。
这个简单的TCP回显客户端和服务器示例展示了TCP协议的基本使用,包括连接建立、数据传输和连接关闭。TCP协议的特点是面向连接、保证数据的可靠传输,适用于需要稳定数据传输的应用场景。
5.解释一下这里为什么要用scanner以及为什么要加换行符 \n
1.使用 Scanner
的原因:
-
简单性:
Scanner
是 Java 中一个非常基础且广泛使用的类,用于解析基本类型和字符串的简单文本输入。它提供了方便的方法来获取不同类型的输入,如nextInt()
,nextLine()
等。 -
读取文本数据:在 TCP 通信中,通常会传输文本数据。
Scanner
能够很好地处理文本数据的读取,尤其是当数据以行的形式发送时。 -
按行读取:
Scanner
的nextLine()
方法可以读取整行的输入,这在处理来自网络的文本消息时非常有用,尤其是当您不知道消息的具体长度时。
2.加入换行符 \n
的原因:
-
消息边界:TCP 是一个面向流的协议,这意味着数据在传输时是连续的字节流,没有明确的边界。通过在每条消息的末尾添加一个换行符
\n
,您可以在读取端明确地知道一条消息的结束,从而方便地解析消息。 -
简化客户端处理:当服务器接收到带有换行符的消息时,
Scanner
的nextLine()
方法将会在换行符处停止读取,这使得服务器能够按行读取客户端发送的消息,而不需要额外的逻辑来确定消息的结束。 -
协议简单化:使用换行符作为消息边界是一种简单的协议设计,它简化了消息的发送和接收逻辑。当然,这要求发送的消息中不能包含换行符本身,除非进行了适当的转义处理。
-
通用性:换行符
\n
是一种通用的文本消息分隔符,在许多网络协议和文本处理场景中广泛使用,因此它易于理解和实现。
3.注意事项:
- 消息终止符:在使用换行符作为消息边界时,确保发送的消息中不包含换行符,或者在处理时能够正确地识别和处理它。
- 性能考虑:对于大量数据的传输,使用
Scanner
可能不是最高效的选择,因为它是为简单文本处理设计的。对于二进制数据或大量文本数据的处理,可能需要更高效的 I/O 操作,如使用BufferedReader
和BufferedWriter
,或者InputStream
和OutputStream
的合适缓冲。 - 安全性:在设计协议时,仅依赖换行符作为消息边界可能不够安全,因为它容易被篡改。在实际应用中,可能需要更健壮的协议来确保数据的完整性和安全性。
以上就是所有对网络编程和套接字的简单介绍了,感谢你的阅读