目录
网络编程套接字,就是研究如何写代码完成网络编程
套接字(socket):是操作系统给应用程序提供的API,来让应用层和传输层进行交互,实际上API也就是传输层给应用层提供的
网络传输层中,有很多种协议,其中最常见的就是 TCP 和 UDP,这两种协议的特性差别是比较大的,所以操作系统就提供了两种不同风格的API
TCP | UDP | 解释 |
---|---|---|
有连接 | 无连接 | 打电话是有连接,这是通信双方建立好连接,才能通信(交互数据) A打电话给B,B接了,才能说话 发短信/ 发微信 是无连接 直接一发就过去了 |
可靠传输 | 不可靠传输 | 首先明确,可靠传输不是指A发给B的数据,B100%能收到 而是指 A能够知道B是不是收到了,这才是可靠传输
|
面向字节流 | 面向数据报 | 面向字节流:TCP和文件操作一样,也是属于 流 的 面向数据报:这个“数据报”为基本单位 |
全双工 | 全双工 | 全双工:一个通道,双向通信 和全双工相对的就是 “半双工” 半双工:一个通道,单向通信 |
UDP 的 socket
客户端 - 服务器
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
构造方法
方法签名 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据包套接字的Socket,绑定到本机任意的一个随机接口(一般用于客户端) |
DatagramSocket | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
方法
方法签名 | 说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket 代表了一个UDP数据报,也就是一次 发送 / 接收的基本单位
构造方法
方法签名 | 说明 |
---|---|
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()
|
获取数据报中的数据
|
服务器 UdpEchoServer
1.先写DatagramSocket对象
private DatagramSocket socket = null;
2.然后再写Udp回显服务器的构造方法
(参数的端口表示我们自己的服务器要绑定的端口)
public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); }3.写一个启动服务器的方法start(),在start中写上while循环来执行具体逻辑
public void start() throws IOException { System.out.println("服务器启动!"); while(true) { } }a)读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); socket.receive(requestPacket); // 把这个DatagramPacket 对象转为字符串,方便去打印 // 里面的参数对应的是 取出字节数组,对应的范围 String request = new String(requestPacket.getData(),0,requestPacket.getLength());b)根据请求计算响应(这里写一个process方法)
String response = process(request);
// 当前写的是回显服务器,响应数据和请求是一样的 public String process(String request) { return request; }c)把响应写回到客户端
// 以这个对象为单位进行发送。这就是面向数据报,发送和接收都是以这个DatagramPacket为基本单位 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); //getSockAddress() 此时只是有数据了,还需要知道数据发给谁,要写上客户端的地址的端口 //getBytes() 得到字符串里面包含的字节数组 socket.send(responsePacket);d)打印一个日志,记录当前的情况
System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);4.写一个mian方法来执行Udp回显服务器
public static void main(String[] args) throws IOException { UdpEchoServer server = new UdpEchoServer(9090); server.start(); }
在进行 读取请求并解析 和 把响应写回到客户端 时都分别创建了新的DatagramPacket对象
里面传的值有什么区别?
在进行 把响应写回到客户端 时,如何确定发给谁的问题?
在给DataGramPacket对象传参时,传入客户端地址和端口(用getSocketAddress方法)
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.SocketException;
-
- /**
- * Created with IntelliJ IDEA.
- * Description:
- * User: 28463
- * Date: 2022—10—12
- * Time: 15:17
- */
- public class UdpEchoServer {
- 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);
- // 把这个DatagramPacket 对象转为字符串,方便去打印
- // 里面的参数对应的是 取出字节数组,对应的范围
- String request = new String(requestPacket.getData(),0,requestPacket.getLength());
-
- //2.根据请求计算响应
- String response = process(request);
-
- //3.把响应写回到客户端
- // 以这个对象为单位进行发送。这就是面向数据报,发送和接收都是以这个DatagramPacket为基本单位
- DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
- response.getBytes().length,
- requestPacket.getSocketAddress());
- //getSockAddress() 此时只是有数据了,还需要知道数据发给谁,要写上客户端的地址的端口
- //getBytes() 得到字符串里面包含的字节数组
-
- socket.send(responsePacket);
-
- //4.打印一个日志,记录当前的情况
- 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 server = new UdpEchoServer(9090);
- server.start();
- }
-
- }
UdpEchoClient
1. 写一个DatagramSocket对象
DatagramSocket socket = null; private String serverIP; private int serverPort;2. 再写回显客户端的构造方法
// 两个参数一会会在发送数据的时候用到,暂时先将这两个参数存起来,以备后用 public UdpEchoClient(String serverIP, int serverPort) throws SocketException { //这里不是说没有端口,而是让系统自动指定一个空闲的端口 socket = new DatagramSocket(); // 假设serverIP 是形如 1.2.3.4 这种点分十进制的表示方式 this.serverIP = serverIP; this.serverPort = serverPort; }3. .写一个启动客户端的方法start(),在start中写上while循环来执行具体逻辑
public void start() throws IOException { Scanner scan = new Scanner(System.in); while(true) { } }a)从控制台读取用户输入的内容
System.out.println("-> "); String request = scan.next();b)构造一个UDP请求,发送给服务器
DatagramPacket requestpacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(this.serverIP), this.serverPort); //把数据报通过网络发送出去 socket.send(requestpacket);c)从服务器读取UDP响应数据,并解析
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096); //用receive针对空的responsePacket进行填充 socket.receive(responsePacket); String response = new String(responsePacket.getData(),0,responsePacket.getLength());d)把服务器的响应提示到控制台上
System.out.println(response);
4. 最后再写个main方法,进行回显客户端的执行
public static void main(String[] args) throws IOException { UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090); client.start(); }
给服务器发送请求,给DatagramPacket对象传入IP和端口来描述发送给谁
在写客户端构造的时候,不用写客户端IP
再看一个DatagramPacket对象的传值,注意里面传的是字节
客户端给服务器发送一个数据:
客户端自己的主机IP:源IP
客户端没有手动的绑定一个端口,操作系统就会自动分配一个空闲的端口
服务器的主机IP:目的IP
服务器绑定的端口:目的端口
服务器需要手动指定指定端口,是为了方便客户端找到服务器在哪。
客户端不需要手动指定端口,是因为操作系统会自动分配一个空闲的端口,如果你自己手动指定一个端口,可能这个端口别的程序正在使用,就会导致程序无法正确运行了
总之,服务器是运行在程序员自己的服务器上,这个主机是可控的,而客户端是运行在用户自己的电脑上,是不可控的
- import java.io.IOException;
- import java.net.*;
- import java.util.Scanner;
-
- /**
- * Created with IntelliJ IDEA.
- * Description:
- * User: 28463
- * Date: 2022—10—12
- * Time: 15:16
- */
- public class UdpEchoClient {
- DatagramSocket socket = null;
- private String serverIP;
- private int serverPort;
-
- // 两个参数一会会在发送数据的时候用到,暂时先将这两个参数存起来,以备后用
- public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
- //这里不是说没有端口,而是让系统自动指定一个空闲的端口
- socket = new DatagramSocket();
- // 假设serverIP 是形如 1.2.3.4 这种点分十进制的表示方式
- this.serverIP = serverIP;
- this.serverPort = serverPort;
- }
-
- public void start() throws IOException {
- Scanner scan = new Scanner(System.in);
- while(true) {
- // 1.从控制台读取用户输入的内容
- System.out.println("-> ");
- String request = scan.next();
-
- // 2.构造一个 UDP 请求,发送给服务器
- DatagramPacket requestpacket = new DatagramPacket(request.getBytes(),
- request.getBytes().length,
- InetAddress.getByName(this.serverIP),
- this.serverPort);
- //把数据报通过网络发送出去
- socket.send(requestpacket);
-
- // 3.从服务器读取 UDP 响应数据,并解析
- DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
- //用receive针对空的responsePacket进行填充
- socket.receive(responsePacket);
- String response = new String(responsePacket.getData(),0,responsePacket.getLength());
-
- // 4.把服务器的响应提示到控制台上
- System.out.println(response);
- }
- }
-
- public static void main(String[] args) throws IOException {
- UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
- client.start();
- }
- }
也可以在idea中设置,允许运行多个客户端
英译汉;
请求输入 hello,响应返回一个 你好
请求输入 car,响应返回一个 汽车
- import java.io.IOException;
- import java.net.SocketException;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * Created with IntelliJ IDEA.
- * Description:
- * User: 28463
- * Date: 2022—10—14
- * Time: 16:08
- */
- public class UdpTranslateServer extends UdpEchoServer{
- //翻译本质上是 key -> value
- private Map
dict = new HashMap<>(); -
- public UdpTranslateServer(int port) throws SocketException {
- super(port);
-
- dict.put("hello", "你好");
- dict.put("car", "汽车");
- dict.put("dog", "小狗");
- dict.put("cat", "小猫");
- //在这里就可以输入很多的内容。
- //现在的翻译程序基本都是这样,只不过用一个很大的哈希表,包含了很多单词和翻译
- }
-
- //重写 process 方法,实现查询哈希表的操作
- @Override
- public String process(String request) {
- return dict.getOrDefault(request,"词在词典中未找到!");
- }
-
- //start 和父类完全一样
- public static void main(String[] args) throws IOException {
- UdpTranslateServer server = new UdpTranslateServer(9090);
- server.start();
- }
- }
启动服务器,在客户端输入看看(这里用前面写的回显—客户端就可以)
一个服务器程序的基本流程,都是和上面一样的
但是最核心的区别就是“根据请求计算响应”,这个取决于服务器的业务逻辑
qq服务器,根据请求计算响应:根据用户发送的消息,返回一个“发送成功还是失败”
淘宝服务器,根据请求计算响应:支付一个订单,根据你的订单信息,完成支付操作
(扣款,修改订单状态)返回支付是否成功等待状态
百度服务器,根据请求计算响应:根据用户输入的查询词,找到匹配的网页
抖音服务器,根据请求计算响应:根据用户的身份特征(用户会话),返回可能感兴趣的视频