欢迎来到飞鸟慕鱼博客,开始您的技术之旅!
当前位置: 首页知识笔记正文

javaEE 89000字详解网络编程,linux高性能网络详解

终极管理员 知识笔记 100阅读

Socket绑定到本机指定的端口

DatagramSocket 方法

方法签名方法说明void receive(DatagramPacket p)从此套接字接收数据报如果没有接收到数据报该方法会阻塞等待void send(DatagramPacket p)从此套接字发送数据报包不会阻塞等待直接发送void close()关闭此数据报套接字 3.2 DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报。

DatagramPacket 构造方法

方法签名方法说明DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报接收的数据保存在字节数组第一个参数buf中接收指定长度第二个参数lengthDatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报发送的数据为字节数组第一个参数buf中从0到指定长度第二个参数length。address指定目的主机的IP和端口号

DatagramPacket 方法

方法签名方法说明InetAddress.getAddress()从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址InetAddress.getPort()从接收的数据报中获取发送端主机的端口号或从发送的数据报中获取接收端主机端口号DatagramPacket.getData()获取数据报中的数据

构造UDP发送的数据报时需要传入 SocketAddress 该对象可以使用 InetSocketAddress 来创建。

3.3 InetSocketAddress API

InetSocketAddress SocketAddress 的子类 构造方法

方法签名方法说明InetSocketAddress(InetAddress addr, int port)创建一个Socket地址包含IP地址和端口号 3.4 案例演示

一发一收无响应

3.4.1 UDP服务端
package org.example.udp.demo1;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.util.Arrays;public class UdpServer {  //服务器socket要绑定固定的端口  private static final int PORT  8888;    public static void main(String[] args) throws IOException {    // 1.创建服务端DatagramSocket指定端口可以发送及接收UDP数据报    DatagramSocket socket  new DatagramSocket(PORT);        //不停的接收客户端udp数据报    while (true){          // 2.创建数据报用于接收客户端发送的数据      byte[] bytes  new byte[1024];//1m1024kb, 1kb1024byte, UDP最多64k包含UDP首部8byte      DatagramPacket packet  new DatagramPacket(bytes, bytes.length);      System.out.println(---------------------------------------------------);      System.out.println(等待接收UDP数据报...);            // 3.等待接收客户端发送的UDP数据报该方法在接收到数据报之前会一直阻塞接收到数据报以后DatagramPacket对象包含数据bytes和客户端ip、端口号      socket.receive(packet);      System.out.printf(客户端IP%s%n,packet.getAddress().getHostAddress());      System.out.printf(客户端端口号%s%n, packet.getPort());      System.out.printf(客户端发送的原生数据为%s%n, Arrays.toString(packet.getData()));      System.out.printf(客户端发送的文本数据为%s%n, new String(packet.getData()));   } }}

运行后服务端就启动了控制台输出如下

等待接收UDP数据报…

可以看出此时代码是阻塞等待在 socket.receive(packet) 代码行直到接收到一个UDP数据报。

3.4.2 UDP客户端
package org.example.udp.demo1;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetSocketAddress;import java.net.SocketAddress;public class UdpClient {  // 服务端socket地址包含域名或IP及端口号  private static final SocketAddress ADDRESS  newInetSocketAddress(localhost, 8888);  public static void main(String[] args) throws IOException {      // 4.创建客户端DatagramSocket开启随机端口就行可以发送及接收UDP数据报    DatagramSocket socket  new DatagramSocket();        // 5-1.准备要发送的数据    byte[] bytes  hello world!.getBytes();        // 5-2.组装要发送的UDP数据报包含数据及发送的服务端信息服务器IP端口号    DatagramPacket packet  new DatagramPacket(bytes, bytes.length,ADDRESS);    // 6.发送UDP数据报    socket.send(packet); }}

客户端启动后会发送一个hello world! 的字符串到服务端在服务端接收后控制台输出内容如下

等待接收UDP数据报…
客户端IP127.0.0.1
客户端端口号57910
客户端发送的原生数据为[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33,
0, 0, 0, …此处省略很多0]
客户端发送的文本数据为hello world!

等待接收UDP数据报…

从以上可以看出发送的UDP数据报假设发送的数据字节数组长度为M在接收到以后假设接收的数据字节数组长度为N

如果N>M则接收的byte[]字节数组中会有很多初始化byte[]的初始值0转换为字符串就是空白字符如果N<M则会发生数据部分丢失可以自己尝试把接收的字节数组长度指定为比发送的字节数组长度更短。

要解决以上问题就需要发送端和接收端双方约定好一致的协议如规定好结束的标识或整个数据的长度。

四TCP流套接字编程 4.1 ServerSocket API

ServerSocket 是创建TCP服务端Socket的API。

ServerSocket 构造方法

方法签名方法说明ServerSocket(int port)创建一个服务端流套接字Socket并绑定到指定端口。

ServerSocket 方法

方法签名方法说明Socket accept()开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket对象并基于该Socket建立与客户端的连接否则阻塞等待void close()关闭此套接字 4.2 Socket API

Socket 是客户端Socket或服务端中接收到客户端建立连接accept方法的请求后返回的服务端Socket。

不管是客户端还是服务端Socket都是双方建立连接以后保存的对端信息及用来与对方收发数据的。

Socket 构造方法

方法签名方法说明Socket(String host, int port)创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接

Socket 方法

方法签名方法说明getInetAddress()返回套接字所连接的地址getInputStream()返回此套接字的输入流getOutputStream()返回此套接字的输出流 4.3 TCP中的长短连接

TCP发送数据时需要先建立连接什么时候关闭连接就决定是短连接还是长连接

短连接每次接收到数据并返回响应后都关闭连接即是短连接。也就是说短连接只能一次收发数据。长连接不关闭连接一直保持连接状态双方不停的收发数据即是长连接。也就是说长连接可以多次收发数据。

对比以上长短连接两者区别如下

建立连接、关闭连接的耗时短连接每次请求、响应都需要建立连接关闭连接而长连接只需要第一次建立连接之后的请求、响应都可以直接传输。相对来说建立连接关闭连接也是要耗时的长连接效率更高。

主动发送请求不同短连接一般是客户端主动向服务端发送请求而长连接可以是客户端主动发送请求也可以是服务端主动发。

两者的使用场景有不同短连接适用于客户端请求频率不高的场景如浏览网页等。长连接适用于客户端与服务端通信频繁的场景如聊天室实时游戏等。

扩展了解
基于BIO同步阻塞IO的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说这样的消耗是不能承受的。

由于每个连接都需要不停的阻塞等待接收数据所以每个连接都会在一个线程中运行。

一次阻塞等待对应着一次请求、响应不停处理也就是长连接的特性一直不关闭连接不停的处理请求。

实际应用时服务端一般是基于NIO即同步非阻塞IO来实现长连接性能可以极大的提升。

4.4 案例演示

一发一收短连接

以下为一个客户端一次数据发送和服务端多次数据接收一次发送一次接收可以接收多次即只有客户端请求但没有服务端响应的示例

4.4.1 TCP服务端
package org.example.tcp.demo1;import java.io.*;import java.net.ServerSocket;import java.net.Socket;public class TcpServer {  //服务器socket要绑定固定的端口  private static final int PORT  8888;  public static void main(String[] args) throws IOException {      // 1.创建一个服务端ServerSocket用于收发TCP报文    ServerSocket server  new ServerSocket(PORT);        // 不停的等待客户端连接    while(true) {      System.out.println(---------------------------------------------------);      System.out.println(等待客户端建立TCP连接...);            // 2.等待客户端连接注意该方法为阻塞方法      Socket client  server.accept();      System.out.printf(客户端IP%s%n,client.getInetAddress().getHostAddress());      System.out.printf(客户端端口号%s%n, client.getPort());            // 5.接收客户端的数据需要从客户端Socket中的输入流获取      System.out.println(接收到客户端请求);      InputStream is  client.getInputStream();            // 为了方便获取字符串内容可以将以上字节流包装为字符流      BufferedReader br  new BufferedReader(new InputStreamReader(is,UTF-8));      String line;            // 一直读取到流结束TCP是基于流的数据传输一定要客户端关闭Socket输出流才表示服务端接收IO输入流结束      while ((line  br.readLine()) ! null) {        System.out.println(line);     }           // 6.双方关闭连接服务端是关闭客户端socket连接      client.close();   } }}

运行后服务端就启动了控制台输出如下

等待客户端建立TCP连接…

可以看出此时代码是阻塞等待在 server.accept() 代码行直到有新的客户端申请建立连接。

4.4.2 TCP客户端
package org.example.tcp.demo1;import java.io.*;import java.net.Socket;public class TcpClient {  //服务端IP或域名  private static final String SERVER_HOST  localhost;    //服务端Socket进程的端口号  private static final int SERVER_PORT  8888;  public static void main(String[] args) throws IOException {      // 3.创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接    Socket client  new Socket(SERVER_HOST, SERVER_PORT);        // 4.发送TCP数据是通过socket中的输出流进行发送    OutputStream os  client.getOutputStream();        // 为了方便输出字符串作为发送的内容可以将以上字节流包装为字符流    PrintWriter pw  new PrintWriter(new OutputStreamWriter(os, UTF-8));        // 4-1.发送数据    pw.println(hello world!);        // 4-2.有缓冲区的IO操作真正传输数据需要刷新缓冲区    pw.flush();        // 7.双方关闭连接客户端关闭socket连接    client.close(); }}

客户端启动后会发送一个hello world! 的字符串到服务端在服务端接收后控制台输出内容如下

等待客户端建立TCP连接…
客户端IP127.0.0.1
客户端端口号51118
接收到客户端请求
hello world!

等待客户端建立TCP连接…

以上客户端与服务端建立的为短连接每次客户端发送了TCP报文及服务端接收了TCP报文后双方都会关闭连接。

五再谈协议

以上我们实现的UDP和TCP数据传输除了UDP和TCP协议外程序还存在应用层自定义协议可以想想分别都是什么样的协议格式。

对于客户端及服务端应用程序来说请求和响应需要约定一致的数据格式

客户端发送请求和服务端解析请求要使用相同的数据格式。服务端返回响应和客户端解析响应也要使用相同的数据格式。请求格式和响应格式可以相同也可以不同。约定相同的数据格式主要目的是为了让接收端在解析的时候明确如何解析数据中的各个字段。可以使用知名协议广泛使用的协议格式如果想自己约定数据格式就属于自定义协议。 5.1 封装/分用 vs 序列化/反序列化

一般来说在网络数据传输中发送端应用程序发送数据时的数据转换如java一般就是将对象转换为某种协议格式即对发送数据时的数据包装动作来说

如果是使用知名协议这个动作也称为封装如果是使用小众协议包括自定义协议这个动作也称为序列化一般是将程序中的对象转换为特定的数据格式。

接收端应用程序接收数据时的数据转换即对接收数据时的数据解析动作来说

如果是使用知名协议这个动作也称为分用如果是使用小众协议包括自定义协议这个动作也称为反序列化一般是基于接收数据特定的格式转换为程序中的对象

标签:
声明:无特别说明,转载请标明本文来源!