网络编程及其实现
DatagramSocket是UDP Socket用于发送和接收UDP数据报方法签名方法说明创建一个UDP数据报套接字的Socket,绑定到任意随机端口(一般用于客户端)创建一个UDP数据报套接字的socket,绑定到指定端口(一般用于服务端)注:服务端指定端口号:方便客户端找到它,发送请求给它客户端随机端口号:一般客户端在我们主机上,端口号是由操作系统分配的(不定),并且服务端可以根据我们发送过
网络编程概念
什么是网络编程?
网络编程:就是我们书写代码实现:对于不同的进程之间,进行网络通信(网络数据的传递)
注意:进行通信的主机是同一台主机或者不同主机都可行(只要是两个不同的进程就行)
我们日常通过访问网页获取需要的网络资源(视频,图片)就是使用网络编程实现的。
如何实现网络编程?
网络编程的实质就是:传递数据+编写代码
因此,我们只要完成这两部分就能实现网络编程。
传递数据:
我们网络通信传递数据是有“协议”的--规定了数据传输的规则,这些“协议”来自于“传输层协议”(普遍使用的是UDP或者TCP协议)。那么我们就要简单先了解一下这两个传输层协议的特性,理解我们传输数据规则的大概要求,后续再详细介绍这些协议。
1.UDP协议:无连接,不可靠传输,面向数据报,半双工
- 无连接:每次数据的传输,直接传输,不用保存通信双方信息(我只要知道发给谁,直接发就行)
- 不可靠传输:数据在传输过程中可能会出现“丢包”(数据丢失),UDP不会管这些丢失的数据,通信双方也不知道是否有数据丢失(但是传输效率更高)
- 面向数据报:数据会被构造成一个“数据报”形式才会在网络中传输
- 半双工:一个通信只能“单向传输”
2.TCP协议:有连接,可靠传输,面向字节流,全双工
- 有连接:通信双方要在传输数据之前保留双方核心信息(IP和端口号),断开连接后删除信息
- 可靠传输:数据在传输过程中可能会出现“丢包”(数据丢失),TCP协议会尝试重传数据(对抗丢包),就算最后仍然“丢包”,双方都会知道是否有数据丢失(传输效率相较于较低)
- 面向字节流:数据按照字节流传输(类似文件IO操作),更加灵活
- 全双工:一个通信能“双向传输”
编写代码:
编写代码其实就是,我们程序员在应用层编写代码调用传输层(操作系统)接口(API),从而配置好“协议”以及进行通信传输
网络编程的基本概念
我们知道网络编程是双方的网络通信,因此我们通过一个常见的网络编程例子,辅以解释我们网络编程中常见到的概念:

- 客户端:请求服务的一方(比如,请求下载视频的我们主机)
- 服务端:给请求提供服务的一方(提供下载视频的资源网站)
- 请求:一般是先发送信息的一方进行的操作
- 响应:收到消息后做做出回应的一方的操作
- 发送端:源主机
- 接收端:目的主机
网络编程套接字Socket
上面我们说到进行网络编程的进行需要“编写代码”,我们编写代码本质就是在应用层使用代码调用操作系统的接口,使用其中实现的传输层“协议”。
这个操作系统提供的接口就是Socket,通过Socket我们可以调用传输层中协议规定数据形式的实现。(可以理解成Socket就像“遥控器”,遥控我们的“空调”(网卡)进行传输数据的规范和传输)
我们常使用的Socket有两类:
流套接字:使用传输层TCP协议(具有TCP协议的特性)
数据报套接字:使用传输层UDP协议(具有UDP协议的特性)
马上我们会介绍到两类套接字实现的通信流程的工作流程。
UDP数据报套接字编程
API介绍
DatagramSocket
DatagramSocket是UDP Socket用于发送和接收UDP数据报
构造方法:
| 方法签名 | 方法说明 |
| DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到任意随机端口(一般用于客户端) |
| DatagramSocket(int port) | 创建一个UDP数据报套接字的socket,绑定到指定端口(一般用于服务端) |
注:服务端指定端口号:方便客户端找到它,发送请求给它
客户端随机端口号:一般客户端在我们主机上,端口号是由操作系统分配的(不定),并且服务端可以根据我们发送过去的数据报提取出我们客户端的“端口号”
功能方法:
| 方法签名 | 方法说明 |
| void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
| void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
| void close() | 关闭此数据报套接字 |
注:receive方法是“输出型函数”,相当于传进去一个参数作为“容器”承载数据回来。
DatagramPacket
DatagramPacket是UDP socket发送和接收的数据报(数据想要发送就要先封装成该形式)
构造方法(常用的):
| 方法签名 | 方法说明 |
| DatagramPacket(byte[] buf,int length) | 构造一个DatagramPacket以用来接收数据报,接收的 数据保存在字节数组(第一个参数buf)中,接收指定 长度(第二个参数length) |
| DatagramPacket(byte[] buf,int offset,int length,SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的 数据为字节数组(第一个参数buf)中,从0到指定长 度(第二个参数length)。address指定目的主机的IP 和端口号 |
功能方法:
| 方法签名 | 方法说明 |
| InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
| int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从 发送的数据报中,获取接收端主机端口号 |
| byte[] getData() | 获取数据报中的数据 |
UDP通信模型实现

图示就是UDP通信模型工作的流程和需要实现的操作。
代码示例:
Sever(服务器):
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPSever {
//基于UDP协议进行网络通信的服务器
//操纵网卡的遥控器
DatagramSocket socket=null;
//构造方法
//服务器的端口号要指定,这样客户端才能找到(系统随机则找不到)
public UDPSever(int port) throws SocketException {
socket=new DatagramSocket(port);
}
//服务器启动
public void start() throws IOException {
System.out.println("服务器已启动");
//服务器的基本工作流程
while (true) {
//1.接收客户端请求
//构造数据报接收
DatagramPacket datagramPacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(datagramPacket);
//假设我们传递的都是字符串,将数据报解析成原始要求
String request = new String(datagramPacket.getData(),0, datagramPacket.getLength());
//2.处理客户端请求
String response = process(request);
//根据响应内容,构造数据报发送回去
//值得注意的是,我们UDP无连接特性,因此客户端的IP和端口从请求数据报中提取
DatagramPacket resPacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
datagramPacket.getAddress(), datagramPacket.getPort());
//3.返回响应给客户端
socket.send(resPacket);
//4.打印日志报告
System.out.printf("[%s:%d]客户端:req:%s,res:%s\n", datagramPacket.getAddress(), datagramPacket.getPort()
, request, response);
}
}
//这里写一个最简单的回显服务器,请求是什么,就重复一遍请求当作响应发送回去
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UDPSever udpSever=new UDPSever(9090);
udpSever.start();
}
}
Client(客户端):
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPClient {
//基于UDP协议进行网络通信的客户端
DatagramSocket datagramSocket=null;
String SeverIP;
int SeverPort;
//构造方法
//客户端端口根据操作系统进行分配(因为无所谓)
public UDPClient(String ip,int port) throws SocketException {
//因为UDP的无连接特性,每次启动服务器都要用IP和端口号连接一次服务器(也不算连接,就是知道发送对象是谁)
this.SeverIP=ip;
this.SeverPort=port;
datagramSocket=new DatagramSocket();
}
//启动客户端
public void start() throws IOException {
System.out.println("客户端已启动");
//客户端的工作基本流程
//1.发送请求给服务器
while (true) {
System.out.println("请输入你要发送的请求->");
Scanner scanner = new Scanner(System.in);
String request = scanner.next();
if (request.equals("exists")) {
return;
}
//根据请求构造发送的数据报
DatagramPacket reqPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(SeverIP), SeverPort);
datagramSocket.send(reqPacket);
//2.接收服务器返回响应
//接收响应数据报
DatagramPacket resPacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(resPacket);
//3.解析响应
String response =new String(resPacket.getData(),0,resPacket.getLength());
//4.展示服务器响应结果
System.out.println("服务器响应结果是:" + response);
}
}
public static void main(String[] args) throws IOException {
UDPClient udpClient=new UDPClient("127.0.0.1",9090);
udpClient.start();
}
}
注:1.传输的数据是封装成数据报的形式,发送还是解析都要先构造数据报或者分用数据报
2.由于UDP的无连接特性,因此服务器传回IP与端口号,需要从接收的请求数据报中去提取
TCP流套接字编程
API介绍
ServerSocket
serversocket是创建TCP服务端Socket的API
构造方法:
| 方法签名 | 方法说明 |
| ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
功能方法:
| 方法签名 | 方法说明 |
| Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端 连接后,返回一个服务端Socket对象,并基于该 Socket建立与客户端的连接,否则阻塞等待 |
| void close() | 关闭此套接字 |
这个类的主要作用就是领着“客户端的请求”来找到创建Socket文件对象,进行真正的客户端,服务器连接,进行后续的请求,响应操作
Socket
Socket是客户端的Socket,或服务器中接收到客户端建立连接(accept方法)请求后,返回的服务器Socket。
不管是客户端的,还是服务器的Socket,都是双方建立连接以后,保存对端信息,及用来与对方收发数据的
构造方法:
|
方法签名 |
方法说明 |
| Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
功能方法:
| 方法签名 | 方法说明 |
| InetAddress getInetAddress() | 返回套接字所连接的地址 |
| InputStream getInputStream() | 返回此套接字的输入流 |
| OutputStream getOutputStream() | 返回此套接字的输出流 |
TCP通信模型实现

图示为TCP流套接字的网络通信基本流程
Sever(服务器):
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPSever {
//作为接待客户端连接请求
ServerSocket serverSocket = null;
//构造方法,指定端口号便于被客户端指定连接
public TCPSever(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
//启动服务器
public void start() {
System.out.println("服务器启动");
//
ExecutorService executorService = Executors.newCachedThreadPool();
//服务器要一直运行处理客户端请求
while (true) {
executorService.submit(() -> {
Socket socket = null;
try {
socket = serverSocket.accept();
processConnection(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
//真正处理连接
private void processConnection(Socket socket) {
System.out.printf("[%s:%d]服务器已经上线\n", socket.getInetAddress(), socket.getPort());
//开始进行真正通信
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream)) {
Scanner scanner=new Scanner(inputStream);
//服务器的基本工作流程
while (true) {
//1.接收客户端请求
if(!scanner.hasNext()){
System.out.printf("[%s:%d]客户端已经下线\n",socket.getInetAddress(),socket.getPort());
break;
}
String request=scanner.next();
//2.处理客户端请求
String response=process(request);
//3.返回服务器的响应
printWriter.println(response);
printWriter.flush();
//4.打印日志
System.out.printf("[%s:%d]客户端:req:%s,res:%s\n",socket.getInetAddress(),socket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String process(String s) {
return s;
}
public static void main(String[] args) throws IOException {
TCPSever tcpSever=new TCPSever(9090);
tcpSever.start();
}
}
Client(客户端):
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
Socket socket=null;
//存储服务器的IP和端口号
String SeverIP;
int SeverPort;
//构造方法
public TCPClient(String IP,int port) throws IOException {
this.SeverIP = IP;
this.SeverPort = port;
socket = new Socket(SeverIP, SeverPort);
}
//启动客户端
public void start() {
System.out.println("客户端启动");
//客户端的工作基本流程
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
PrintWriter printWriter=new PrintWriter(outputStream)) {
Scanner req=new Scanner(System.in);
while (true) {
//1.用户输入请求
System.out.println("请输入你的请求->");
String requeset=req.next();
if(requeset.equals("exists")){
break;
}
//2.发送用户请求给服务器
printWriter.println(requeset);
printWriter.flush();
//3.接收服务器的响应信息
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNext()){
break;
}
String response=scanner.next();
//4.展示响应信息结果
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPClient tcpClient=new TCPClient("127.0.0.1",9090);
tcpClient.start();
}
}
注:1.由于服务器要应对多个客户端的连接,因此这里采用“线程池”来处理“accept的连接客户端和服务器”操作
2.服务器中的ServiceSocket相当于“接客前台”的作用,领着客户端到真正“负责连接”的人
Socket相当于是“负责连接”的人,建立连接后,由Socket处理后续的请求和响应操作(相当于前台接过来,然后socket进行服务)
长短连接
我们知道TCP的特性(有连接),因此,客户端和服务器传输数据连接时间的长短就决定了,此次连接是长连接,还是短连接。
- 长连接:进行多次传输请求和接收响应,双方一直保持着连接状态(可以进行多次通信)
- 短连接:进行一次传输请求和接收响应之后,就断开连接(只进行一次通信)
关于长,短连接也有各自的优劣之处以及使用场合:
- 短连接适合于客户端使用(一般客户端拿到自己需要的数据后,就会断开了),客户端与服务器的连接次数少的情况(请求频次少,比如查阅网页资料)
- 长连接适合于需要一直进行连接传输的场景(网络聊天和打游戏)
- 短连接连接,中断频次高,占用资源,效率较低;长连接只进行一次连接断开,比较节省资源,效率较高
同时,关于提升我们TCP服务器的效率还有“IO多路复用”--一个线程处理多个socket(同一时刻,连接的客户端多,但不是每个客户端都在传输数据,因此,一个线程处理多个可以尽可能节省资源,提升效率),但是IO多路复用在Java标准库中封装的过于麻烦,大家有兴趣可以去了解。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)