- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE初阶
本篇文章将带你了解什么是网络编程?
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
网络编程中需要有发送端,接收端,也就是服务器和客户端进行数据交互。
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:
1. 客户端先发送请求到服务端
2. 服务端根据请求数据,执行相应的业务处理
3. 服务端返回响应:发送业务处理结果
4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
目录
文章目录
一、Socket套接字
1.1 概念
1.2 分类
1.3 TCP和UDP的特点
二、DatagramSocket API
三、DatagramPacket API
四、基于UDP Socket实现回显服务器
4.1 服务器端
4.1.1 创建Socket对象
4.1.2 绑定端口号
4.1.3 启动服务器主逻辑
4.2 客户端
五、Socket文件怎么是存储数据的
六、测试udp回显服务器
七、udp英语查询服务器
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程~~
程序猿写网络程序,主要编写的应用层代码!!
真正要发送这个数据,需要上层协议,调用下层协议,应用层需要调用传输层。(想了解协议分层,请看【JavaEE】网络通信中的一些基本概念及协议分层_xyk:的博客-CSDN博客
传输层给应用层提供一组api,统称为socket api
系统给程序猿提供了两组socket api
1.基于UDP的api(User Datagram Protocol)
2.基于TCP的api(Transmission Control Protocol)
UDP: TCP:
无连接 有连接
不可靠传输 可靠传输
面向数据报 面向字节流
全双工 全双工
所谓的无连接:是指使用UDP通信的双方,不需要刻意保存对端的相关信息
不可靠传输:消息发了就发了,不关注结果了
面向数据报:以一个UDP数据报为基本单位
全双工:一条路径,双向通信
所谓的有连接:使用TCP通信的双方,则需要刻意保存对方的相关信息
可靠传输:不是说,发了就100%能够到达对方,而是尽可能的传输过去(知道结果)
面向字节流:以字节为传输的基本单位,读写方式非常灵活
全双工:一条路径,双向通信
半双工:
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是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地址的包装
- 端口号代表服务器程序所在的“位置”
回显服务器(echo server)
客户端发了个请求,服务器返回一个一模一样的响应~~
三个核心工作:
需要先定义一个 socket 对象
通过网络通信,必须要使用 socket对象
绑定一个端口, 不一定能成功
如果某个端口已经被别的进程占用了, 此时这里的绑定操作就会出错.
同一个主机上, 一个端口, 同一时刻, 只能被一个进程绑定.
需要先构造一个空饭盒并接收:
类似去食堂打饭,带着空饭盒去(只有内存空间,没有有意义的数据),让大妈给打饭~~
传一个空的packet对象,然后由receive方法内部,把参数的这个packet进行填充
此时,如果还没有客户端发来的数据,怎么办?
为了方便处理,把数据报转成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.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();
}
}
小结:
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 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();
}
}