🙉专栏推荐:Java入门知识🙉
🙉 内容推荐:Java网络编程🙉
🐹今日诗词:姑苏城外寒山寺,夜半钟声到客船🐹
⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏
⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏
目录
网络编程套接字
套接字
TCP和UDP区别
有连接和无连接
可靠和不可靠传输
面向字节流和面向数据报
全双工和半双工
UDP数据报套接字编程
回显服务器
服务端
客户端
公网IP和私网IP
字典服务器
美图分享
网络编程套接字
套接字
socket就是网络编程的套接字, 可以认为是网络编程API的统称
操作系统提供的套接字不止一种, 常见的三种
1. 流式套接字 -> 给TCP使用
2. 数据报套接字 -> 给UDP使用
3. Unix域套接字(很少使用了) -> 不能跨主机通信, 只能本地主机上的进程和进程通信
TCP和UDP协议都是传输层协议, 都是服务于应用层
但是这两协议的差别去很大, 因此我们需要两套API分别去使用它们
TCP和UDP区别
总结概括就是
TCP: 有连接, 可靠传输, 面向字节流, 全双工
UDP: 无连接, 不可靠传输, 面向数据报, 全双工
有连接和无连接
TCP有连接: 比如打电话, 电话打通了才能说话, 可以选择接听, 也可以不接听
UDP无连接: 发短信微信, 对方不需要接通, 可以直接发送
有连接的特点: 通信双方保存了对方的信息
无连接的特点: 通信双方不需要保存对方的信息
可靠和不可靠传输
可靠传输 != 绝对安全, 只是尽可能保持数据不会丢失, 传输的过程可能会出现丢包
丢包: A向B传输10个数据, 但是中途经过很多交换机和路由器, 到B手中可能只剩下9个数据流了
TCP可靠传输: 尽量保持数据在传输中不丢失
UDP不可靠传输: 传输数不管对方有没有收到, 反正我发了
可靠传输也是有代价的, 传输效率会大打折扣
面向字节流和面向数据报
TCP面向字节流: 传输数据的基本单位是字节, 文件操作就是字节流, 像水流一样, 文件读写都非常灵活, TCP也是如此
UDP面向数据报: 传输数据的基本单位是UDP数据报, 一次读写必须读写一个完整的数据报, 不能缺斤少两, 不能多读多写
网络传输数据的基本单位
有四个, 数据报, 数据段, 数据包, 数据帧, 但一般不会过于区分, 都是混着用, 毕竟他们都是传输数据的基本单位, 硬要区分的话就是下面这样
1. 数据报DataGram (UDP传输的基本单位)
2. 数据段Segment (TCP)
3. 数据包 Packet (IP)
4. 数据帧 Frame (数据链路层)
全双工和半双工
全双工: 一条链路能够进行双向通信
半双工: 一条链路只能进行单向通信
TCP和UDP都是全双工
全双工特点: 使用socket对象时, 既可以读(接收)又可以写(发送)
半双工特点: 不能同时读和写, 只能进行其中一项
UDP数据报套接字编程
DatagramSocket: 这个类用于UDP数据报进行网络通信
DatagramPacket: 这个类封装数据报的类, 相当于将数据报包装成了一个类
一个形象的例子理解二者区别:
如果把UDP网络通信比作送快递的话, DatagramSocket相当于快递员, DatagramPacket相当于包裹
DatagramSocket负责管理和创建套接字(相当于快递员), 可以用来接收或者发送数据报(包裹), DatagramPacket类用于封装要发送或者接收的信息及目标地址和端口信息(相当于包裹), 可以被DatagramSocket发送或者接收
DatagramSocket构造方法
DatagramSocket(int port) : port是手动指定的端口号, 大小在0-->65535
DatagramSocket() : 无参数版本, 系统会随机指定一个端口号
DatagramPacket构造方法
DatagramPacket( byte[] buf, int length ) : 表示创建了一个名为buf的缓冲区, DatagramPacket只能使用length长度的缓冲区大小, 这种写法很奇怪, 比如这样:
为什么要有缓冲区:
如果客户端请求直接发送给服务端, 数量非常多时, 服务端压力会非常大,IO会非常频繁
可能会导致服务器挂了, 如果有一个缓冲区, 服务器可以不用那么及时去处理请求
大大减轻了服务器的压力, 同时存在缓冲区, 可以降低客户端和服务端的耦合性
回显服务器
服务端
我们直接来看服务端代码, 我在代码里加了注释, 建议复制粘贴到自己的编译器去看
import java.io.IOException; import java.net.*; public class UDPSever { private DatagramSocket socket = null; public UDPSever(String IP, int port) throws IOException { socket = new DatagramSocket(port); } public void start() throws IOException{ System.out.println("服务器, 启动!"); while (true) { DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //创建了一个4096大小的缓冲区, DatagramPacket能使用缓冲区的大小为4096 socket.receive(requestPacket); //DatagramSocket接收请求, receive可能会出现异常, 网络请求和响应也IO的一种抛出IOException异常即可 //如果没有请求, receive就会自动阻塞, 阻塞到客户端发送请求为止 String request = new String(requestPacket.getData(),0, requestPacket.getLength()); //这里就是解析数据了 //由于数据报都是二进制数据,我们将他转化成字符串数据, 便于观察 //getData()是获取整个缓冲区的数据, getLength()获取实际有效数据长度 String response = responsemethod(request); //服务器响应请求 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length, requestPacket.getSocketAddress()); //构建响应数据报, 将响应的数据封装成数据报, 然后由服务器发送 //数据报包含: 有效数据内容, 客户端地址, 端口号 //getSocketAddress()可以获取完整的套接字信息, 包括IP信息端口号 //getAddress只获取IP信息 socket.send(responsePacket); //将数据报发送会客户端 System.out.printf("日志: [%s:%d] requset = %s response = %s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response); //打印日志, System.out.printf(),可以像C语言一样, 格式化输出 //System.out.println("[%s:%d]\n", requestPacket.getAddress(), requestPacket.getPort()); //sout以ln结尾的不能格式化输出代码, System.out.println("%d", 5);像这种写法就是错的 } } private String responsemethod(String request) { return request; //因为这里是模拟客户端和服务端, 所以响应我们写的简单点 //客户端发送什么, 我们就原封不动的发送回去, 这种方式叫回显 } public static void main(String[] args) throws IOException { UDPSever udpSever = new UDPSever("127.0.0.1", 9090); udpSever.start(); } }
客户端
import java.io.IOException; import java.net.*; import java.util.Scanner; public class UDPClient { public DatagramSocket socket = null; private String SeverIP;//服务器的IP private int SeverPort;//服务器的端口号 public UDPClient(String SeverIP, int SeverPort) throws SocketException { socket = new DatagramSocket();//系统随机分配客户端端口号 //为什么不手动指定客户端端口号呢?因为服务器端口号固定,客户端才能找到, //如果服务器端口号经常变,就会非常麻烦,但是服务器并不需要关注客户端的端口号 this.SeverIP = SeverIP; this.SeverPort = SeverPort; } public void start() throws IOException { System.out.println("客户端, 启动!"); while (true) { Scanner scanner = new Scanner(System.in); System.out.println("请输入你要发送的请求: "); String request = scanner.next(); DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0, request.getBytes().length, InetAddress.getByName(SeverIP), SeverPort); //构建请求数据报,包含有效数据, 服务器IP地址, 服务器端口号 //SecerIP是字符串, 需要转成服务器能够识别的类型, // 我们也可以在创建IP的时候就传入InetAddress类型的,但这是给编译器看的,不方便我们理解 socket.send(requestPacket); //客户端发送数据报到服务器 DatagramPacket responsePacket= new DatagramPacket(new byte[4096], 4096);//用于存放响应信息 socket.receive(responsePacket);//接收响应信息 String response = new String(responsePacket.getData(), 0, responsePacket.getData().length); System.out.print(response); } } public static void main(String[] args) throws IOException { UDPClient udpClient = new UDPClient("127.0.0.1", 9090); udpClient.start(); } }
运行结果
公网IP和私网IP
每台电脑都有对应的IP, 但这并不意味着你知道了对方的IP就可以随意连接对方, 必须是公网IP才能够连接对方,如果不是公网IP, 还要连接对方, 你就需要和服务器在同一个局域网下才可以, 有点类似于开热点
私网IP和公网IP概念
私网IP: 地址满足(1) 10.*开头
(2) 172.16 -- 172.31*开头
(3) 192.168.*开头
满足这些开头的IP地址是私网地址, 其余都是公网IP地址
字典服务器
字典服务器: 输入英文单词, 返回对应的中文单词
这个很好实现, 就是在前面的回显服务器的基础上, 将响应方法改一下即可
因此我们的字典服务器直接继承UDPSever服务器类即可,然后重新里面的回显方法
import java.io.IOException; import java.util.HashMap; public class UDPdictionarySever extends UDPSever { HashMap<String, String> dictionary = null; public UDPdictionarySever(String IP, int port) throws IOException { super(IP, port); dictionary = new HashMap<>(); dictionary.put("hello", "你好"); dictionary.put("world", "世界"); dictionary.put("dog", "小狗"); dictionary.put("cat", "小猫"); dictionary.put("pig", "GGBond我的男神GGBond"); } @Override public String responsemethod(String request) { return dictionary.getOrDefault(request, "该单词没有对应的中文"); } public static void main(String[] args) throws IOException { UDPdictionarySever udPdictionarySever = new UDPdictionarySever("127.0.0.1", 9090); udPdictionarySever.start(); } }
注意事项
运行效果
美图分享
✨🎆谢谢你的阅读和耐心!祝愿你在编程的道路上取得更多的成功与喜悦!"🎆✨🎄
⭐️点赞收藏加关注,学习知识不迷路⭐️
🎉✔️💪🎉✔️💪🎉✔️💪🎉✔️💪🎉
👍😏⛳️点赞☀️收藏⭐️关注😏👍
👍😏⛳️点赞☀️收藏⭐️关注😏👍
👍😏⛳️点赞☀️收藏⭐️关注😏👍
🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️🙆♂️