本篇文章将带你了解什么是网络编程?
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
网络编程中需要有发送端,接收端,也就是服务器和客户端进行数据交互。
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:
1. 客户端先发送请求到服务端
2. 服务端根据请求数据,执行相应的业务处理
3. 服务端返回响应:发送业务处理结果
4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
目录
一、Socket套接字
1.1 概念
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程~~
程序猿写网络程序,主要编写的应用层代码!!
真正要发送这个数据,需要上层协议,调用下层协议,应用层需要调用传输层。(想了解协议分层,请看【JavaEE】网络通信中的一些基本概念及协议分层_xyk:的博客-CSDN博客
传输层给应用层提供一组api,统称为socket api
1.2 分类
系统给程序猿提供了两组socket api
1.基于UDP的api(User Datagram Protocol)
2.基于TCP的api(Transmission Control Protocol)
1.3 TCP和UDP的特点
UDP: TCP:
无连接 有连接
不可靠传输 可靠传输
面向数据报 面向字节流
全双工 全双工
所谓的无连接:是指使用UDP通信的双方,不需要刻意保存对端的相关信息
不可靠传输:消息发了就发了,不关注结果了
面向数据报:以一个UDP数据报为基本单位
全双工:一条路径,双向通信
所谓的有连接:使用TCP通信的双方,则需要刻意保存对方的相关信息
可靠传输:不是说,发了就100%能够到达对方,而是尽可能的传输过去(知道结果)
面向字节流:以字节为传输的基本单位,读写方式非常灵活
全双工:一条路径,双向通信
半双工:
二、DatagramSocket API
- Socket ==> 数据报的Socket对象
- Packet ==> 这个对象就是一个UDP数据报
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
Socket,说明这个对象是一个socket对象,相当于对应到系统中一个特殊的文件(socket文件)
socket文件并非对应到硬盘上的某个数据存储区域,而是对应到,网卡这个硬件设备
(cmd中输出ipconfig查看):
有线网卡:
所以想要进行网络通信,就需要有socket文件这样的对象
借助这个socket文件对象,才能间接的操作网卡(遥控器)
往这个socket对象中写数据,相当于通过网卡发送消息
往这个socket对象中读数据,相当于通过网卡接收消息
DatagramSocket 构造方法:
方法签名 | 方法说明 |
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端) |
此处的Socket对象,对于服务器这边的socket往往要关联一个具体的端口号(必须要不变!!)
客户端这边则不需要手动指定,系统自动分配即可(则不要求)
举例说明一下:
而客户端这边,不需要有地址,只需要知道餐厅在哪就行,坐在哪里吃都行,是随机的~~~
DatagramSocket 方法:
方法签名 | 方法说明 |
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
socket也是文件,文件用完了要记得关闭!否则会出现文件资源泄漏的问题!!
三、DatagramPacket API
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和端口号 |
- IP — 确认电脑
- 端口号port — 确定应用程序
第一个版本,不需要设置地址进去,通常用来接受消息
第二个版本,需要显式的设置地址进去,通常用来发送消息
DatagramPacket 方法
方法签名 | 方法说明 |
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
- InetAddress是对服务器IP地址的包装
- 端口号代表服务器程序所在的“位置”
四、基于UDP Socket实现回显服务器
回显服务器(echo server)
客户端发了个请求,服务器返回一个一模一样的响应~~
三个核心工作:
- 读取请求并解析
- 根据请求求得对应响应(回显服务器 无此操作)
- 把响应返回给客户端
4.1 服务器端
4.1.1 创建Socket对象
需要先定义一个 socket 对象
通过网络通信,必须要使用 socket对象
4.1.2 绑定端口号
绑定一个端口, 不一定能成功
如果某个端口已经被别的进程占用了, 此时这里的绑定操作就会出错.
同一个主机上, 一个端口, 同一时刻, 只能被一个进程绑定.
4.1.3 启动服务器主逻辑
需要先构造一个空饭盒并接收:
类似去食堂打饭,带着空饭盒去(只有内存空间,没有有意义的数据),让大妈给打饭~~
传一个空的packet对象,然后由receive方法内部,把参数的这个packet进行填充
此时,如果还没有客户端发来的数据,怎么办?
- receive阻塞等待就行了,直到客户端发来真的数据请求
- 有点类似于阻塞队列~~
为了方便处理,把数据报转成String
这个操作并非是必须的,只是此处为了后续代码简单,就简单的构造一个String,
如果客户端发来的数据是“老板来份蛋炒饭”,此时这个数据就会以二进制的形式躺在requestPackct中的字节数组中,把这个字节数组拿出来,重新构造一个String,这个String 的内容就是“老板来一份蛋炒饭”
根据请求计算响应(此处省略这个步骤)
把响应结果写回客户端,并打印日志
- getSocketAddress === 数据报里的客户端主机IP
- 传给客户端得转化为数据报才行
- 而计算数据报的信息计算只能以字符串为对象
SocketAddress同时包含了 ip 和 端口号
完整代码:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* @author xyk的电脑
* @version 1.0
* @description: TODO
* @date 2023/4/6 16:48
*/
public class UdpEchoServer {
//需要先定义一个 socket 对象
//通过网络通信,必须要使用 socket对象
private DatagramSocket socket = null;
// 绑定一个端口, 不一定能成功!!
// 如果某个端口已经被别的进程占用了, 此时这里的绑定操作就会出错.
// 同一个主机上, 一个端口, 同一时刻, 只能被一个进程绑定.
public UdpEchoServer(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
String request = new String(requestPacket.getData(),0,
requestPacket.getLength());
// 2. 根据请求计算响应(此处省略这个步骤)
String response = process(request);
// 3. 把响应结果写回到客户端
// 根据 response 字符串, 构造一个 DatagramPacket .
// 和请求 packet 不同, 此处构造响应的时候, 需要指定这个包要发给谁.
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
// requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口
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 IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
4.2 客户端
4.2.1 客户端启动,需要知道服务器在哪里!!
对于客户端不需要指定关联端口,不代表没有端口,而是系统自动分配了端口
4.2.2 用户输入内容
4.2.3 把字符串构造成 UDP packet, 并进行发送.
这个构造,也是把数据构造成DatagramPacket,一方面需要String中的getBytes数组
另一方面,需要指定服务器的 ip 和 端口,此处不是通过InetAddress直接构造了,而是分开设置
4.2.4 把响应数据转换成 String 显示出来.
完整代码:
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* @author xyk的电脑
* @version 1.0
* @description: TODO
* @date 2023/4/6 16:48
*/
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
// 客户端启动, 需要知道服务器在哪里!!
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
// 对于客户端来说, 不需要显示关联端口.
// 不代表没有端口, 而是系统自动分配了个空闲的端口.
socket = new DatagramSocket();
this.serverIp = serverIp;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
// 1. 先从控制台, 读取一个字符串过来
// 先打印一个提示符, 提示用户要输入内容
System.out.print("-> ");
String request = scanner.next();
// 2. 把字符串构造成 UDP packet, 并进行发送.
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);
// 4. 把响应数据转换成 String 显示出来.
String response = new String(responsePacket.getData(),
0, responsePacket.getLength());
System.out.printf("req: %s, resp: %s\n", request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
小结:
五、Socket文件怎么是存储数据的
六、测试udp回显服务器
七、udp英语查询服务器
package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
/**
* @author xyk的电脑
* @version 1.0
* @description: TODO
* @date 2023/4/9 21:39
*/
public class UdpDictServer extends UdpEchoServer {
private Map<String,String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("dog","小狗");
dict.put("cat","小猫");
dict.put("fuck","卧槽");
}
@Override
public String process(String request){
return dict.getOrDefault(request,"该单词没有查到! ");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDictServer = new UdpDictServer(9090);
udpDictServer.start();
}
}