网络编程~
什么是⽹络编程
网络编程是指通过编写程序实现计算机之间的通信和数据交换,通常基于网络协议(如TCP/IP)进行数据传输。其核心目标是让不同设备或系统能够通过网络连接共享信息、资源或服务。
常⻅的客⼾端服务端模型
最常⻅的场景,客⼾端是指给⽤⼾使⽤的程序,服务端是提供⽤⼾服务的程序:
- 客⼾端先发送请求到服务端
- 服务端根据请求数据,执⾏相应的业务处理
- 服务端返回响应:发送业务处理结果
- 客⼾端根据响应数据,展⽰处理结果(展⽰获取的资源,或提⽰保存资源的处理结果)
Socket套接字
Socket(套接字)是网络编程中的一个重要概念,它是网络通信的端点,用于在不同计算机之间进行数据传输。可以把它理解为两个程序之间通信的桥梁。
IP 地址:用于标识网络中的设备
端口号:用于标识设备中的具体应用程序
协议:定义数据传输的规则,主要有 TCP 和 UDP
Socket 的类型
1.流式套接字(SOCK_STREAM)
- 有连接
- 可靠传输
- ⾯向字节流
- 有接收缓冲区,也有发送缓冲区
- ⼤⼩不限
2. 数据报套接字(SOCK_DGRAM)
- ⽆连接
- 不可靠传输
- ⾯向数据报
- 有接收缓冲区,⽆发送缓冲区
- ⼤⼩受限:⼀次最多传输64k
3. 原始套接字(SOCK_RAW)简单理解即可
- 可以直接访问底层协议
- 主要用于实现新的网络协议
Java数据报套接字通信模型
对于UDP协议来说,具有⽆连接,⾯向数据报的特征,即每次都是没有建⽴连接,并且⼀次发送全部 数据报,⼀次接收全部的数据报。
java中使⽤UDP协议通信,报主要基于 DatagramSocket 类来创建数据套接字,并使⽤ DatagramPacket 作为发送或接收的UDP数据报。
具体流程:
UDP数据报套接字编程
这里的DatagramSocket t可以看作邮局,DatagramPacket可以看作信件,邮件对新建进行发送和接收,信件里面这里记录着我们的内容(也就是IP和端口)。
DatagramSocket
- 用于发送和接收 UDP 数据包
- 不需要建立连接,直接发送数据
- 提供无连接、不可靠的数据传输服务
DatagramSocket 构造方法
方法签名 | 说明 |
---|---|
DatagramSocket() | 创建一个未绑定的套接字 |
DatagramSocket(int port) | 创建一个绑定到指定端口的套接字 |
DatagramSocket(int port, InetAddress addr) | 创建一个绑定到指定地址和端口的套接字 |
DatagramSocket(SocketAddress bindaddr) | 创建一个绑定到指定本地地址的套接字 |
DatagramSocket 主要方法
方法签名 | 说明 |
---|---|
void send(DatagramPacket p) | 从此套接字发送数据报包 |
void receive(DatagramPacket p) | 从此套接字接收数据报包 |
void bind(SocketAddress addr) | 将套接字绑定到指定的本地地址 |
void close() | 关闭此套接字 |
boolean isClosed() | 返回套接字是否已关闭 |
void setSoTimeout(int timeout) | 启用/禁用 SO_TIMEOUT,以毫秒为单位 |
int getPort() | 返回套接字连接的远程端口 |
InetAddress getInetAddress() | 返回套接字连接的地址 |
DatagramPacket 类
- 表示 UDP 数据包
- 包含数据、目标地址和端口信息
- 是数据传输的基本单位
DatagramPacket 构造方法
方法签名 功能描述 DatagramPacket(byte[] buf, int length)
构造用于接收长度为 length
的数据包DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度为 length
的数据包到指定主机的指定端口DatagramPacket(byte[] buf, int offset, int length)
构造用于接收从 offset
开始的长度为length
的数据包DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造用于发送从 offset
开始的长度为length
的数据包到指定主机的指定端口DatagramPacket 主要方法
方法签名 功能描述 InetAddress getAddress()
返回发送/接收数据报的机器 IP 地址 byte[] getData()
返回数据缓冲区 int getLength()
返回发送/接收数据的长度 int getOffset()
返回缓冲区中数据的偏移量 int getPort()
返回远程端口号 void setAddress(InetAddress iaddr)
设置目标机器 IP 地址 void setData(byte[] buf)
设置数据缓冲区 void setData(byte[] buf, int offset, int length)
设置带偏移量和长度的数据缓冲区 void setLength(int length)
设置数据包长度 void setPort(int iport)
设置远程端口号 InetSocketAddress 构造方法
方法签名 方法说明 InetSocketAddress(InetAddress addr, int port)
创建一个Socket地址,包含IP地址和端口号
客户端示例
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;public class UdpClient {public DatagramSocket socket = null;private String serverIp;private int serverPort;public UdpClient(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) {//1这里读取用户输入的内容System.out.println("这里输入发送的内容");if (!scanner.hasNext()) {break;}String request = scanner.next();//2.把请求发送给服务器,需要构造 DatagramPacket 对象.DatagramPacket resquePacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);//3.发送数据报socket.send(resquePacket);//4.接收服务器的响应DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);socket.receive(responsePacket);//5.解析并打印String response = new String(responsePacket.getData(),0,responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpClient client = new UdpClient("127.0.0.1",9000);client.start();}
}
服务端示例
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;public class UdpEchoServer {public DatagramSocket socket = null;public UdpEchoServer(int port) throws Exception {socket = new DatagramSocket(port);}public String start() throws IOException {//服务器启动了System.out.println("服务器启动了");while (true){// 循环一次, 就相当于处理一次请求.// 处理请求的过程, 典型的服务器都是分成三个步骤的.// 1. 读取请求并解析.// DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4069);//接收数据报socket.receive(requestPacket);//把读取到的二进制数据转换为字符串。String request= new String(requestPacket.getData(),0,requestPacket.getLength());//2.根据请求,计算响应//此处写的是回显服务器,也就是服务器返回与请求相同的字符串。String response = process(request);//3.把响应返回给客服端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);}}public String process(String request) {return request;}public static void main(String[] args) throws Exception {UdpEchoServer server = new UdpEchoServer(9000);server.start();}
}
重写process
public class UdpDictServer extends UdpEchoServer{private HashMap<String,String> dict = new HashMap<>();public UdpDictServer(int port) throws Exception {super(port);//初始话字典dict.put("hello","你好");dict.put("world","世界");dict.put("book","书");dict.put("apple","苹果");}@Overridepublic String process(String request) {return dict.getOrDefault(request, "词典无此单词");}public static void main(String[] args) throws Exception {UdpEchoServer server = new UdpDictServer(9000);server.start();}
TCP流套接字编程
它就像打电话一样,需要先建立连接,然后才能传输数据。
TCP的特点
- 面向连接:通信前需要建立连接(三次握手)
- 可靠传输:保证数据不丢失、不重复、按序到达
- 面向字节流:数据像水流一样连续传输
- 一对一通信:一个连接只能在两个端点之间通信
服务端
- ServerSocket:用于监听客户端连接请求
- Socket:用于与客户端通信
服务端工作流程
- 创建 ServerSocket 并绑定端口
- 调用 accept() 等待客户端连接
- 客户端连接后获得 Socket 对象
- 通过 Socket 的输入输出流进行数据传输
- 关闭连接
客户端
- Socket:用于连接服务器
客户端工作流程
- 创建 Socket 并连接服务器
- 通过 Socket 的输入输出流进行数据传输
- 关闭连接
简单客服端示例
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.Scanner;public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws Exception {//这里直接设置//创建socket对象就会在底层建立tcp连接,就不需要直接保存了socket = new Socket(serverIp, serverPort);}public void start() throws IOException {Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {// 为了使用方便, 套壳操作Scanner scannerNet = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//从控制读取请求发送给服务器while (true) {System.out.println("请输入请求数据");//读取用户输入String request = scanner.next();//发送给服务器writer.println(request);//缓冲区刷新才可以真正发的发送数据writer.flush();//读取返回的响应String response = scannerNet.next();//打印System.out.println(response);}}catch (IOException e ){throw new RuntimeException(e);}}public static void main(String[] args) throws Exception {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9000);client.start();}
}
简单服务端示例
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 TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");//创建线程池ExecutorService executorService = Executors.newCachedThreadPool();//处理连接,可能有多个连接while(true){//先处理客户端发来的连接,如果没有客户端发起连接,此时accept就会阻塞Socket clientSocket = serverSocket.accept();//处理连接//processConnection(clientSocket);//采用多线程的方式调整可以进行多个用户同时进行
// Thread t= new Thread(()->{
// try {
// processConnection(clientSocket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// t.start();//采用线程池的方式executorService.submit(() -> {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}}private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {//针对inputStream套了一层Scanner scanner = new Scanner(inputStream);//针对outputStreamPrintWriter writer = new PrintWriter(outputStream);//分成三个步骤while (true){//读取请求并解析if(!scanner.hasNext()){System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}String request = scanner.next();//根据请求计算响应String response = Process(request);//返回客户端writer.println(response);//缓冲区刷新才可以真正发的发送数据writer.flush();// 打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch (IOException e) {throw new RuntimeException(e);}finally {clientSocket.close();}}private String Process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9000);server.start();}
}