操作系统会提供一组API(socket.api)接口进行网络通讯
传输层的两个核心协议
TCP:有连接,可靠传输,面向字节流,全双工
UDP:无连接,不可靠传输,面向数据报,全双工
有连接vs无连接
这个连接不是物流意义上的连接,是抽象,虚拟概念的连接。
在TCP中,A和B通信,A和B先建立连接,然后A会保存B的信息,B也会保存A的信息,让他们知道彼此之间是和谁在进行通信。
UDP中,则不会保存这样的信息,不管对方是谁,这个就叫做无连接
举例子
结婚,两个人结婚就之后,会有结婚证,一式两份,上面就会写着对方的信息,比如A是B的老公,B是A的老婆。这个就叫做有连接
可靠传输vs不可靠传输
在网络传输中,丢包是很常见的事情,受到外界的各种干扰就会导致丢包,当我们发出一个数据包的时候,并不指望他能够100%到达对方
可靠传输:并不能100%的送达,只是说尽可能的送达
不可靠传输:管你那么多,我送了,到不到就不关我事了
可靠传输虽然送达的完整度高,但是要付出效率低的代价,不可靠传输虽然送达完整度低,但是效率高。
面向字节流vs面向数据报
面向字节流:以字节为基本单位读写数据,支持任意长度会出现粘包的问题(读一半)
面向数据报:以一个数据报为基本单位读写数据,每次读取必须1个完整的数据报,不支持任意长度但是不会出现粘包
全双工vs半双工
全双工:一个通信链路中,支持双向通信,能读也能写
半双工:一个通信链路中,支持单向通信,只能写或者只能读
Socket.api进行网络编程
在计算机中,文件广义上来说,是操作系统操作管理的一种形式,同时,硬件也可以抽象成一个文件,进行统一的管理。
网卡==socket文件
操作过程就和操作普通的文件差不多,打开,读写,关闭,之所以不直接操作网卡,是因为不好操作,所以转化成文件,相当于遥控器
DategramSocket
表示的是通过操作系统调用socket.api来操作网卡

构造方法:相当于打开文件
第一个构造方法是随机绑定一个端口号
第二个是指定端口号

一个是接收,一个是发送,注意传入的是一个完整的数据报,因为你接收和发送的肯定是一个完整的数据报
DatagramPacket
表示的是一个完整的UDP数据报

UDP的数据包的载荷数据,可以通过构造方法来指定传入和传出
使用UdpEchoClient/Server模拟(回显服务器)
UdpEchoServer
思路
首先声明一个变量,类型是datagramsocket,然后在构造方法中传入一个端口号,同时初始化之前的变量,这个变量是datagramsocket类型的,这个类就相当于一个代理人,因为我们无法直接调用网卡,这个类就相当于一个代理人向操作系统申请一个窗口和网卡交流,,之后在start方法中要写入一个死循环,因为我们要让服务器进行不间断的工作,服务器的处理请求的过程一般分为三步,第一步,创建一个UDP数据包,读取请求,最好使用一个数组类型这样读取的快一些,然后使用socket.receive去读取,注意这个是一个阻塞的方法,会等待客户端输入,假如客户端输入了,因为网络传输的只能说二进制,所以下一步就是需要把这些二进制转成string类型,第二步就是响应请求,这里我用的是回显式,所以暂时先不管,第三步就是返回数据给客户端,我们要把之前转成字符串的转成二进制,这样才能在网络上传播,传播的时候要注意指定ip和端口号,最后返回即可。
具体代码实现:

这段代码中,首先是声明一个datagramsocket类型的变量,然后在构造方法中,我会传入一个端口号,因为我们在java中是无法直接操作网卡的,所以要借助datagramsocket这个类,可以理解为代理人,去和操作系统申请,让我们可以和网卡进行沟通,进而可以进行读写操作。

这里更是核心中的核心,首先,因为服务器需要不间断的进行工作,所以在这个start方法中,我们需要加入一个死循环来确保他能够24小时不间断工作(黑奴)。
服务器处理请求一般是分为三步
第一步,读取并解析请求

首先,我们需要提前准备好一个空的请求包,这个是为了之后在客户端输入的时候,我们能够读取到信息,然后使用socket.receive去读取这个数据包,接着需要把这些去读到的数据转化成字符串,方便我们能看懂
第二步,根据请求,计算响应(核心)
由于我这里演示的是回显式所以就比较简单

第三步,返回响应值

因为之前我们把他拆成字符串了,所以这个时候我们需要把他变回去成为二进制的数据,然后把他装到一个数据包里去,这个时候要注意给出发送的地址和端口号,最后发送即可。
可能你会有一个疑问,之前不是说UDP是无连接的吗,这个指的是长久的连接,一次的连接他是可以记住的,过了就忘了,所以这个时候可以获取到源IP和端口。
可能你还有一个疑问,既然说网卡可以抽象成文件,那打开不要关闭吗?好问题,不需要关闭,因为我们这个是服务器,关了还咋服务。
还有一个疑问,当客户端没有发消息的时候,这个服务器在干啥?忙等吗?并没有,还记得我之前说receive方法那里吗,带有阻塞的兄弟,会阻塞等待直到客户端有消息传入。
最后一步,打印日志

前面的是格式,后面的是一一对应的东西,分别打印请求的ip和端口号,还有请求是啥,回应是啥。
源码
java
package NetWork;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: zhany
* Date: 2025-12-14
* Time: 13:04
*/
public class UdpEchoServer {
//socket是一个可以和网卡通讯的,读就相当于在网卡上收取信息,写就是通过网卡发送
//首先,先定义一个变量用来存放之后的行为
DatagramSocket socket = null;
public UdpEchoServer (int port) throws SocketException {
//指定一个端口号,让服务器使用
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
//因为服务器需要7*24小时不间断工作,所以套一个while死循环
while (true){
//处理请求的过程分为三步
//1.读取请求并解析
//这一步是创建一个UDP的数据包,传入的字节数组,就是UDP的载荷
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
//把读取到的二进制转成有效的字符串,使用getdata获取到有效的字节数组,getlength获取到有效的长度,再string
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
//2.根据请求,计算响应
//因为写的是回显式服务器,所以不用管先
String response = process(request);
//3.返回响应值
//因为前面把他转成了字符串,所以现在需要把他转成二进制的要注意最后不是response.length,同时要给出这个
//回应的端口号和ip
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//打印日志
System.out.printf("[%s : %d] , req: %s , resp: %s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
UdpEchoClient
思路
客户端其实和服务器端有点类似,他也没有办法直接操作网卡,所以他也需要寻找一个代理人datagramsocket去帮他开个通话窗口去读写网卡的数据,逻辑也是比较简单的,首先我们知道UDP是无连接的,所以我们需要在构造方法中传入一个源地址和端口号,让我们知道去访问哪个服务器,核心,让用户输入要发送的内容,然后我们需要构建一个数据包载荷,里面把用户输入的全部转成二进制的,需要知道这个有啥和有多长,其次需要知道这个包要去到哪里,哪个端口号,然后发送出去就ok,然后需要创建一个新的数据包(载荷),等待服务器返回的值,然后把返回的二进制解析成为字符串,然后展示出来,结束

第一步

首先创建一个datagramsocket类型的变量,因为这个和服务端的一样,是无法直接操作网卡的,所以也是需要借助这个代理人的,因为UDP是无连接,所以他不知道要去到哪里,这个时候我们就需要告诉他去到哪里,在构造方法中,需要传入访问的IP地址和端口号。
核心方法start
首先让用户输入想要发送的,然后接下里就简单了,把用户输入的直接开始分解,分解成二进制的数据,放到一个数据包中去,注意在创建这个数据包的时候,需要指定传输的ip和端口号,然后发送就行,接着需要创建好一个空的数据包,用来读取服务器返回的值,再把返回来的值解析成字符串,最后打印出来
注意这个"127.0.0.1"是环回IP,无论你本机的真实IP是啥,都是可以进行访问的
这两个程序在同一个电脑上确实是可以进行互传消息的,那如果在两个电脑上呢?
- 如果是在同一个局域网的情况下,是可以进行互发消息的
- 如果不在同一个局域网下就无法进行发消息
源码
java
package NetWork;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: zhany
* Date: 2025-12-14
* Time: 18:52
*/
public class UdpEchoClient {
private DatagramSocket socket = null;
//因为UDP并不会记录对方是谁,所以要自己保存
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 {
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入你要发送的内容");
if (!scanner.hasNext()){
break;
}
String request = scanner.nextLine();
//构造数据载荷的同时要注意ip和端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);
//发送
socket.send(requestPacket);
//接收
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
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",9090);
udpEchoClient.start();
}
}