目录
网络开发
套接字
套接字(Socket):站在应用层,做网络编程很重要的一个概念
OS原生的提供的系统调用(Linux上的网络编程)
int fd = socket();
setsocketopt(fd,TCP or UDP)
Java中使用UDP协议,相关的类介绍
DatagramSocket API
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket 构造方法:
DatagramSocket 构造方法:
DatagramPacket API
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:
DatagramPacket 方法:
InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
服务器和客户端
服务器(Server):提供服务(Service)的一类程序,一般这个概念是应用层概念。
想象成生活中的:一家商店(提供售卖货品服务)、一家饭馆(提供售卖食品服务)、一家律师事务所(提供法律咨询服务)
淘宝(提供了淘宝平台服务).….
我们这里的服务器指的是一个具体的进程。广义上,我们也经常把该进程所在的主机也称为服务器。由于服务器需要对外提供服务(开张营业),所以,服务器都需要公开其地址(ip / port)。客户端(Client):享受服务的角色
客户端和服务器双方的常见模式,一般有两种:
1.请求(Request)-响应(Response)模式
1)客户端主动提出自己的要求(点菜)
2)服务器根据请求,给回响应(上菜)服务器是被动的
这个周期可能在一个客户端、服务器内部发生多次。一次请求一次响应最简单,我们只展示这种模式。
2.订阅(subscript) -广播(broadcase)模式订报纸
1)客户端第一次主动订阅某份报纸(关心某些消息)
2)以后,凡是这份报纸有新的信息,服务器就会主动推给客户端
服务器相对来说是主动的
Java数据报套接字通信模型
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数据报,一次接收全部的数据报。
Java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket 作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:
以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。
对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下:
演示
import com.xxx.net_demo.util.Log; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; import java.util.Arrays; import java.util.HashMap; // 提供翻译的服务器 public class TranslateServer { // 公开的 ip 地址:就看进程工作在哪个 ip 上 // 公开的 port:需要程序中指定 public static final int PORT = 8888; // SocketException -> IOException -> Exception public static void main(String[] args) throws Exception { Log.println("准备进行字典的初始化"); initMap(); Log.println("完成字典的初始化"); Log.println("准备创建 UDP socket,端口是 " + PORT); DatagramSocket socket = new DatagramSocket(PORT); Log.println("UDP socket 创建成功"); // 作为服务器,是被动的,循环的进行请求-响应周期的处理 // 等待请求,处理并发送响应,直到永远 while (true) { // 1. 接收请求 byte[] buf = new byte[1024]; // 1024 代表我们最大接收的数据大小(字节) DatagramPacket receivedPacket = new DatagramPacket(buf, buf.length); Log.println("准备好接收 DatagramPacket,最大大小为: " + buf.length); Log.println("开始接收请求"); socket.receive(receivedPacket); // 这个方法就会阻塞(程序执行到这里就不动了,直到有客户发来请求,才能继续) Log.println("接收到请求"); // 2. 一旦走到此处,一定是接收到请求了,拆信 // 拆出对方的 ip 地址 InetAddress address = receivedPacket.getAddress(); Log.println("对方的 IP 地址: " + address); // 拆出对方的端口 int port = receivedPacket.getPort(); Log.println("对方的 port: " + port); // 拆出对方的 ip 地址 + port SocketAddress socketAddress = receivedPacket.getSocketAddress(); Log.println("对象的完整地址: " + socketAddress); // 拆出对方发送过来的数据,其实这个 data 就是我们刚才定义的 buf 数组 byte[] data = receivedPacket.getData(); Log.println("接收到的对象的数据: " + Arrays.toString(data)); // 拆出接收到的数据的大小(字节) int length = receivedPacket.getLength(); Log.println("接收的数据大小(字节):" + length); // 3. 解析请求 :意味着我们需要定义自己的应用层协议 // 首先,做字符集解码 byte[] -> String String request = new String(data, 0, length, "UTF-8"); // 这个按照我们的应用层协议 String engWord = request; Log.println("请求(英文单词):" + engWord); // 4. 执行业务(翻译服务),不是我们本次演示的重点 String chiWord = translate(engWord); Log.println("翻译后的结果:" + chiWord); // 5. 按照应用层协议,封装响应 String response = chiWord; // 进行字符集编码 String -> byte[] byte[] sendBuf = response.getBytes("UTF-8"); // 6. 发送响应 // 作为发送方需要提供 DatagramPacket sentPacket = new DatagramPacket( sendBuf, 0, sendBuf.length, // 要发送的数据 socketAddress // 从请求信封中拆出来的对象的地址(ip + port) ); Log.println("准备好发送 DatagramPacket 并发送"); socket.send(sentPacket); Log.println("发送成功"); // 7. 本次请求-响应周期完成,继续下一次请求-响应周期 } // socket.close(); // 由于我们是死循环,这里永远不会走到 } private static final HashMap<String, String> map = new HashMap<>(); private static void initMap() { map.put("apple", "苹果"); map.put("pear", "梨"); map.put("orange", "橙子"); } private static String translate(String engWord) { String chiWord = map.getOrDefault(engWord, "查无此单词"); return chiWord; } }
import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; public class Log { public static void println(Object o) { LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String now = formatter.format(localDateTime); String message = now + ": " + (o == null ? "null" : o.toString()); System.out.println(message); } public static void main(String[] args) { println(1); } }
TCP流套接字编程
基于TCP做的翻译服务
TCP:可靠、有连接、面向字节流有连接:打电话(先拨号,拨通才能通信)
无连接:接发信(无脑发送,对方在不在都没关系)
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 构造方法:
服务器使用的TCP Socket对象(传入的端口,就是要公开的端口,一般称为监听(listen)端口)
ServerSocket 方法:
accept(接受):接起电话(服务器是电话铃响的这一方)
Socket对象:建立起的连接
socket代表的是一条连接
close():挂电话(谁都可以挂)
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 构造方法:
服务器的Socket对象是从accept()中获取到的
所以,只有客户端的Socket对象需要手动实例化出来
这个构造方法是给客户端使用,传入服务器的ip + port(服务器的ip + port是公开的)
一旦socket对象拿到(双方是同时拿到的:电话接通是双方同时接通的),双方就地位平等了,只区分发送方/接收方即可。
Socket 方法:
面向数据报文和面向字节流
import com.xxx.net_demo.util.Log; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.HashMap; import java.util.Scanner; public class TranslateServer { public static final int PORT = 8888; public static void main(String[] args) throws Exception { initMap(); ServerSocket serverSocket = new ServerSocket(PORT); while (true) { // 接电话 Log.println("等待对方来连接"); Socket socket = serverSocket.accept(); Log.println("有客户端连接上来了"); // 对方信息: InetAddress inetAddress = socket.getInetAddress(); // ip Log.println("对方的 ip: " + inetAddress); int port = socket.getPort(); // port Log.println("对方的 port: " + port); SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); // ip + port Log.println("对方的 ip + port: " + remoteSocketAddress); // 读取请求 InputStream inputStream = socket.getInputStream(); Scanner scanner = new Scanner(inputStream, "UTF-8"); String request = scanner.nextLine(); // nextLine() 就会去掉换行符 String engWord = request; Log.println("英文: " + engWord); // 翻译 String chiWord = translate(engWord); Log.println("中文: " + engWord); // 发送响应 String response = chiWord; // TODO: 响应的单词中是没有 \r\n OutputStream outputStream = socket.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8"); PrintWriter writer = new PrintWriter(outputStreamWriter); Log.println("准备发送"); writer.printf("%s\r\n", response); writer.flush(); Log.println("发送成功"); // 挂掉电话 socket.close(); Log.println("挂断电话"); } // serverSocket.close(); } private static final HashMap<String, String> map = new HashMap<>(); private static void initMap() { map.put("apple", "苹果"); map.put("pear", "梨"); map.put("orange", "橙子"); } private static String translate(String engWord) { String chiWord = map.getOrDefault(engWord, "查无此单词"); return chiWord; } }
import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; public class Log { public static void println(Object o) { LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String now = formatter.format(localDateTime); String message = now + ": " + (o == null ? "null" : o.toString()); System.out.println(message); } public static void main(String[] args) { println(1); } }