InetAddress
一个 InetAddress 类的对象就代表一个 IP 地址对象
成员方法:
static InetAddress getLocalHost()
:获得本地主机 IP 地址对象
static InetAddress getByName(String host)
:根据 IP 地址字符串或主机名获得对应的 IP 地址对象
String getHostName()
:获取主机名
String getHostAddress()
:获得 IP 地址字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class InetAddressDemo { public static void main(String[] args) throws Exception { InetAddress ip = InetAddress.getLocalHost(); System.out.println(ip.getHostName()); System.out.println(ip.getHostAddress()); InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress()); InetAddress ip3 = InetAddress.getByName("182.61.200.6"); System.out.println(ip3.getHostName()); System.out.println(ip3.getHostAddress()); System.out.println(ip2.isReachable(5000)); } }
|
UDP
基本介绍
UDP(User Datagram Protocol)协议的特点:
- 面向无连接的协议,发送端只管发送,不确认对方是否能收到,速度快,但是不可靠,会丢失数据
- 尽最大努力交付,没有拥塞控制
- 基于数据包进行数据传输,发送数据的包的大小限制 64KB 以内
- 支持一对一、一对多、多对一、多对多的交互通信
UDP 协议的使用场景:在线视频、网络语音、电话
实现UDP
UDP 协议相关的两个类:
- DatagramPacket(数据包对象):用来封装要发送或要接收的数据,比如:集装箱
- DatagramSocket(发送对象):用来发送或接收数据包,比如:码头
DatagramPacket:
DatagramPacket 类:
public new DatagramPacket(byte[] buf, int length, InetAddress address, int port)
:创建发送端数据包对象
- buf:要发送的内容,字节数组
- length:要发送内容的长度,单位是字节
- address:接收端的IP地址对象
- port:接收端的端口号
public new DatagramPacket(byte[] buf, int length)
:创建接收端的数据包对象
- buf:用来存储接收到内容
- length:能够接收内容的长度
DatagramPacket 类常用方法:
public int getLength()
:获得实际接收到的字节个数
public byte[] getData()
:返回数据缓冲区
DatagramSocket:
- DatagramSocket 类构造方法:
protected DatagramSocket()
:创建发送端的 Socket 对象,系统会随机分配一个端口号
protected DatagramSocket(int port)
:创建接收端的 Socket 对象并指定端口号
- DatagramSocket 类成员方法:
public void send(DatagramPacket dp)
:发送数据包
public void receive(DatagramPacket p)
:接收数据包
public void close()
:关闭数据报套接字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class UDPClientDemo { public static void main(String[] args) throws Exception { System.out.println("===启动客户端==="); byte[] buffer = "我学Java".getBytes(); DatagramPacket packet = new DatagramPacket(buffer,bubffer.length,InetAddress.getLoclHost,8000); DatagramSocket socket = new DatagramSocket(); socket.send(packet); socket.close(); } } public class UDPServerDemo{ public static void main(String[] args) throws Exception { System.out.println("==启动服务端程序=="); byte[] buffer = new byte[1024*64]; DatagramPacket packet = new DatagramPacket(buffer, bubffer.length); DatagramSocket socket = new DatagramSocket(8000); socket.receive(packet); int len = packet.getLength(); String rs = new String(buffer , 0 , len); System.out.println(rs); String ip = packet.getAddress().getHostAdress(); int port = packet.getPort(); socket.close(); } }
|
TCP
基本介绍
TCP/IP (Transfer Control Protocol) 协议,传输控制协议
TCP/IP 协议的特点:
- 面向连接的协议,提供可靠交互,速度慢
- 点对点的全双工通信
- 通过三次握手建立连接,连接成功形成数据传输通道;通过四次挥手断开连接
- 基于字节流进行数据传输,传输数据大小没有限制
TCP 协议的使用场景:文件上传和下载、邮件发送和接收、远程登录
注意:TCP 不会为没有数据的 ACK 超时重传
推荐阅读:https://yuanrengu.com/2020/77eef79f.html
Socket
TCP 通信也叫 Socket 网络编程,只要代码基于 Socket 开发,底层就是基于了可靠传输的 TCP 通信
双向通信:Java Socket 是全双工的,在任意时刻,线路上存在 A -> B
和 B -> A
的双向信号传输,即使是阻塞 IO,读和写也是可以同时进行的,只要分别采用读线程和写线程即可,读不会阻塞写、写也不会阻塞读
TCP 协议相关的类:
- Socket:一个该类的对象就代表一个客户端程序。
- ServerSocket:一个该类的对象就代表一个服务器端程序。
Socket 类:
构造方法:
Socket(InetAddress address,int port)
:创建流套接字并将其连接到指定 IP 指定端口号
Socket(String host, int port)
:根据 IP 地址字符串和端口号创建客户端 Socket 对象
注意事项:执行该方法,就会立即连接指定的服务器,连接成功,则表示三次握手通过,反之抛出异常
常用 API:
OutputStream getOutputStream()
:获得字节输出流对象
InputStream getInputStream()
:获得字节输入流对象
void shutdownInput()
:停止接受
void shutdownOutput()
:停止发送数据,终止通信
SocketAddress getRemoteSocketAddress()
:返回套接字连接到的端点的地址,未连接返回 null
ServerSocket 类:
构造方法:public ServerSocket(int port)
常用 API:public Socket accept()
,阻塞等待接收一个客户端的 Socket 管道连接请求,连接成功返回一个 Socket 对象
三次握手后 TCP 连接建立成功,服务器内核会把连接从 SYN 半连接队列(一次握手时在服务端建立的队列)中移出,移入 accept 全连接队列,等待进程调用 accept 函数时把连接取出。如果进程不能及时调用 accept 函数,就会造成 accept 队列溢出,最终导致建立好的 TCP 连接被丢弃
相当于客户端和服务器建立一个数据管道(虚连接,不是真正的物理连接),管道一般不用 close
实现TCP
开发流程
客户端的开发流程:
- 客户端要请求于服务端的 Socket 管道连接
- 从 Socket 通信管道中得到一个字节输出流
- 通过字节输出流给服务端写出数据
服务端的开发流程:
- 用 ServerSocket 注册端口
- 接收客户端的 Socket 管道连接
- 从 Socket 通信管道中得到一个字节输入流
- 从字节输入流中读取客户端发来的数据
- 如果输出缓冲区空间不够存放主机发送的数据,则会被阻塞,输入缓冲区同理
- 缓冲区不属于应用程序,属于内核
- TCP 从输出缓冲区读取数据会加锁阻塞线程
实现通信
需求一:客户端发送一行数据,服务端接收一行数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class ClientDemo { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1", 8080); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); ps.println("我是客户端"); ps.flush(); System.out.println("客户端发送完毕~~~~"); } } public class ServerDemo{ public static void main(String[] args) throws Exception { System.out.println("----服务端启动----"); ServerSocket serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; if((line = br.readLine()) != null){ System.out.println(line); } } }
|
需求二:客户端可以反复发送数据,服务端可以反复数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class ClientDemo { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1",8080); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); while(true){ Scanner sc = new Scanner(System.in); System.out.print("请说:"); ps.println(sc.nextLine()); ps.flush(); } } } public class ServerDemo{ public static void main(String[] args) throws Exception { System.out.println("----服务端启动----"); ServerSocket serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while((line = br.readLine()) != null){ System.out.println(line); } } }
|
需求三:实现一个服务端可以同时接收多个客户端的消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class ClientDemo { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1",8080); OutputStream os = new socket.getOutputStream(); PrintStream ps = new PrintStream(os); while(true){ Scanner sc = new Scanner(System.in); System.out.print("请说:"); ps.println(sc.nextLine()); ps.flush(); } } } public class ServerDemo{ public static void main(String[] args) throws Exception { System.out.println("----服务端启动----"); ServerSocket serverSocket = new ServerSocket(8080); while(true){ Socket socket = serverSocket.accept(); new ServerReaderThread(socket).start(); } } } class ServerReaderThread extends Thread{ privat Socket socket; public ServerReaderThread(Socket socket){this.socket = socket;} @Override public void run() { try(InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)) ){ String line; while((line = br.readLine()) != null){ sout(socket.getRemoteSocketAddress() + ":" + line); } }catch(Exception e){ sout(socket.getRemoteSocketAddress() + "下线了~~~~~~"); } } }
|
上述缺点:
- 一个客户端对应一个线程,容易导致资源耗尽
- 在客户端接入但并未发送消息的情况下,线程空等,浪费资源
伪异步
伪异步
一个客户端要一个线程,并发越高系统瘫痪的越快,可以在服务端引入线程池,使用线程池来处理与客户端的消息通信
优势:不会引起系统的死机,可以控制并发线程的数量
劣势:同时可以并发的线程将受到限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class BIOServer { public static void main(String[] args) throws Exception { ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服务器启动了"); while (true) { System.out.println("线程名字 = " + Thread.currentThread().getName()); System.out.println("等待连接...."); final Socket socket = serverSocket.accept(); System.out.println("连接到一个客户端"); newCachedThreadPool.execute(new Runnable() { public void run() { handler(socket); } }); } }
public static void handler(Socket socket) { try { System.out.println("线程名字 = " + Thread.currentThread().getName()); byte[] bytes = new byte[1024]; InputStream inputStream = socket.getInputStream(); int len; while ((len = inputStream.read(bytes)) != -1) { System.out.println("线程名字 = " + Thread.currentThread().getName()); System.out.println(new String(bytes, 0, read)); } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("关闭和client的连接"); try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } } }
|