目录
网络编程套接字,就是研究如何写代码完成网络编程
套接字(socket):是操作系统给应用程序提供的API,来让应用层和传输层进行交互,实际上API也就是传输层给应用层提供的
网络传输层中,有很多种协议,其中最常见的就是 TCP 和 UDP,这两种协议的特性差别是比较大的,所以操作系统就提供了两种不同风格的API
1. TCP 和 UDP 的区别
TCP | UDP | 解释 |
---|---|---|
有连接 | 无连接 |
打电话是有连接,这是通信双方建立好连接,才能通信(交互数据) A打电话给B,B接了,才能说话 发短信/ 发微信 是无连接 直接一发就过去了 |
可靠传输 | 不可靠传输 |
首先明确,可靠传输不是指A发给B的数据,B100%能收到 而是指 A能够知道B是不是收到了,这才是可靠传输 |
面向字节流 | 面向数据报 |
面向字节流:TCP和文件操作一样,也是属于 流 的 面向数据报:这个“数据报”为基本单位 |
全双工 | 全双工 |
全双工:一个通道,双向通信 和全双工相对的就是 “半双工” 半双工:一个通道,单向通信 |
UDP 的 socket
客户端 – 服务器
2. DatagramSocket
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
构造方法
方法签名 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据包套接字的Socket,绑定到本机任意的一个随机接口(一般用于客户端) |
DatagramSocket | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
方法
方法签名 | 说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
3. DatagramPacket
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()
|
获取数据报中的数据
|
4. 写回显—服务器
服务器 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方法)
5. 回显服务器代码
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();
}
}
6. 写回显—客户端
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
服务器绑定的端口:目的端口
服务器需要手动指定指定端口,是为了方便客户端找到服务器在哪。
客户端不需要手动指定端口,是因为操作系统会自动分配一个空闲的端口,如果你自己手动指定一个端口,可能这个端口别的程序正在使用,就会导致程序无法正确运行了
总之,服务器是运行在程序员自己的服务器上,这个主机是可控的,而客户端是运行在用户自己的电脑上,是不可控的
7. 回显—客户端代码
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();
}
}
8. 回显服务器客户端的,执行流程步骤
9. 运行服务器 再运行客户端
也可以在idea中设置,允许运行多个客户端
10. 写翻译—服务器
英译汉;
请求输入 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<String, String> 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服务器,根据请求计算响应:根据用户发送的消息,返回一个“发送成功还是失败”
淘宝服务器,根据请求计算响应:支付一个订单,根据你的订单信息,完成支付操作
(扣款,修改订单状态)返回支付是否成功等待状态
百度服务器,根据请求计算响应:根据用户输入的查询词,找到匹配的网页
抖音服务器,根据请求计算响应:根据用户的身份特征(用户会话),返回可能感兴趣的视频
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/91221.html