
00:00:00
外卖卷:非常划算 扫码领劵 省点小钱钱
一个 InetAddress 类的对象就代表一个 IP 地址对象
成员方法:
static InetAddress getLocalHost():获得本地主机 IP 地址对象static InetAddress getByName(String host):根据 IP 地址字符串或主机名获得对应的 IP 地址对象String getHostName():获取主机名String getHostAddress():获得 IP 地址字符串public class InetAddressDemo {
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostName());//DESKTOP-NNMBHQR
System.out.println(ip.getHostAddress());//192.168.11.1
// 2.获取域名 ip 对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());//www.baidu.com
System.out.println(ip2.getHostAddress());//14.215.177.38
// 3.获取公网 IP 对象。
InetAddress ip3 = InetAddress.getByName("182.61.200.6");
System.out.println(ip3.getHostName());//182.61.200.6
System.out.println(ip3.getHostAddress());//182.61.200.6
// 4.判断是否能通:ping 5s 之前测试是否可通
System.out.println(ip2.isReachable(5000)); // ping 百度
}
}UDP(User Datagram Protocol)协议的特点:
UDP 协议的使用场景:在线视频、网络语音、电话
UDP 协议相关的两个类:
DatagramPacket:
DatagramPacket 类:
public new DatagramPacket(byte[] buf, int length, InetAddress address, int port):创建发送端数据包对象
public new DatagramPacket(byte[] buf, int length):创建接收端的数据包对象
DatagramPacket 类常用方法:
public int getLength():获得实际接收到的字节个数public byte[] getData():返回数据缓冲区DatagramSocket:
protected DatagramSocket():创建发送端的 Socket 对象,系统会随机分配一个端口号protected DatagramSocket(int port):创建接收端的 Socket 对象并指定端口号public void send(DatagramPacket dp):发送数据包public void receive(DatagramPacket p):接收数据包public void close():关闭数据报套接字public class UDPClientDemo {
public static void main(String[] args) throws Exception {
System.out.println("===启动客户端===");
// 1.创建一个集装箱对象,用于封装需要发送的数据包!
byte[] buffer = "我学 Java".getBytes();
DatagramPacket packet = new DatagramPacket(buffer,bubffer.length,InetAddress.getLoclHost,8000);
// 2.创建一个码头对象
DatagramSocket socket = new DatagramSocket();
// 3.开始发送数据包对象
socket.send(packet);
socket.close();
}
}
public class UDPServerDemo{
public static void main(String[] args) throws Exception {
System.out.println("==启动服务端程序==");
// 1.创建一个接收客户都端的数据包对象(集装箱)
byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer, bubffer.length);
// 2.创建一个接收端的码头对象
DatagramSocket socket = new DatagramSocket(8000);
// 3.开始接收
socket.receive(packet);
// 4.从集装箱中获取本次读取的数据量
int len = packet.getLength();
// 5.输出数据
// String rs = new String(socket.getData(), 0, len)
String rs = new String(buffer , 0 , len);
System.out.println(rs);
// 6.服务端还可以获取发来信息的客户端的 IP 和端口。
String ip = packet.getAddress().getHostAdress();
int port = packet.getPort();
socket.close();
}
}UDP 通信方式:
单播:用于两个主机之间的端对端通信
组播:用于对一组特定的主机进行通信
IP : 224.0.1.0
Socket 对象 : MulticastSocket
广播:用于一个主机对整个局域网上所有主机上的数据通信
IP : 255.255.255.255
Socket 对象 : DatagramSocket
TCP/IP (Transfer Control Protocol) 协议,传输控制协议
TCP/IP 协议的特点:
TCP 协议的使用场景:文件上传和下载、邮件发送和接收、远程登录
注意:TCP 不会为没有数据的 ACK 超时重传


推荐阅读:https://yuanrengu.com/2020/77eef79f.html
TCP 通信也叫 Socket 网络编程,只要代码基于 Socket 开发,底层就是基于了可靠传输的 TCP 通信
双向通信:Java Socket 是全双工的,在任意时刻,线路上存在 A -> B 和 B -> A 的双向信号传输,即使是阻塞 IO,读和写也是可以同时进行的,只要分别采用读线程和写线程即可,读不会阻塞写、写也不会阻塞读
TCP 协议相关的类:
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():返回套接字连接到的端点的地址,未连接返回 nullServerSocket 类:
构造方法:public ServerSocket(int port)
常用 API:public Socket accept(),阻塞等待接收一个客户端的 Socket 管道连接请求,连接成功返回一个 Socket 对象
三次握手后 TCP 连接建立成功,服务器内核会把连接从 SYN 半连接队列(一次握手时在服务端建立的队列)中移出,移入 accept 全连接队列,等待进程调用 accept 函数时把连接取出。如果进程不能及时调用 accept 函数,就会造成 accept 队列溢出,最终导致建立好的 TCP 连接被丢弃

相当于客户端和服务器建立一个数据管道(虚连接,不是真正的物理连接),管道一般不用 close
客户端的开发流程:
服务端的开发流程:


需求一:客户端发送一行数据,服务端接收一行数据
public class ClientDemo {
public static void main(String[] args) throws Exception {
// 1.客户端要请求于服务端的socket管道连接。
Socket socket = new Socket("127.0.0.1", 8080);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
// 4.开始发消息出去
ps.println("我是客户端");
ps.flush();//一般不关闭IO流
System.out.println("客户端发送完毕~~~~");
}
}
public class ServerDemo{
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口: public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(8080);
// 2.开始等待接收客户端的Socket管道连接。
Socket socket = serverSocket.accept();
// 3.从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 6.按照行读取消息 。
String line;
if((line = br.readLine()) != null){
System.out.println(line);
}
}
}需求二:客户端可以反复发送数据,服务端可以反复数据
public class ClientDemo {
public static void main(String[] args) throws Exception {
// 1.客户端要请求于服务端的 socket 管道连接。
Socket socket = new Socket("127.0.0.1",8080);
// 2.从 socket 通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
// 4.开始发消息出去
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("----服务端启动----");
// 1.注册端口:public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(8080);
// 2.开始等待接收客户端的 Socket 管道连接。
Socket socket = serverSocket.accept();
// 3.从 socket 通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 6.按照行读取消息。
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
}
}需求三:实现一个服务端可以同时接收多个客户端的消息
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 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() - "下线了~~~~~~");
}
}
}一个客户端要一个线程,并发越高系统瘫痪的越快,可以在服务端引入线程池,使用线程池来处理与客户端的消息通信
优势:不会引起系统的死机,可以控制并发线程的数量
劣势:同时可以并发的线程将受到限制
public class BIOServer {
public static void main(String[] args) throws Exception {
//线程池机制
//创建一个线程池,如果有客户端连接,就创建一个线程,与之通讯 (单独写一个方法)
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//创建 ServerSocket
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);
}
});
}
}
//编写一个 handler 方法,和客户端通讯
public static void handler(Socket socket) {
try {
System.out.println("线程名字 = " - Thread.currentThread().getName());
byte[] bytes = new byte[1024];
//通过 socket 获取输入流
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();
}
}
}
}客户端:本地图片:E:\seazean\图片资源\beautiful.jpg 服务端:服务器路径:E:\seazean\图片服务器
UUID. randomUUID() : 方法生成随机的文件名
socket.shutdownOutput():这个必须执行,不然服务器会一直循环等待数据,最后文件损坏,程序报错
//常量包
public class Constants {
public static final String SRC_IMAGE = "D:\\seazean\\图片资源\\beautiful.jpg";
public static final String SERVER_DIR = "D:\\seazean\\图片服务器\\";
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8888;
}
public class ClientDemo {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(Constants.ERVER_IP,Constants.SERVER_PORT);
BufferedOutputStream bos=new BufferedOutputStream(socket.getOutputStream());
//提取本机的图片上传给服务端。Constants.SRC_IMAGE
BufferedInputStream bis = new BufferedInputStream(new FileInputStream());
byte[] buffer = new byte[1024];
int len ;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0 ,len);
}
bos.flush();// 刷新图片数据到服务端!!
socket.shutdownOutput();// 告诉服务端我的数据已经发送完毕,不要在等我了!
bis.close();
//等待着服务端的响应数据!!
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
System.out.println("收到服务端响应:"+br.readLine());
}
}public class ServerDemo {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1.注册端口:
ServerSocket serverSocket = new ServerSocket(Constants.SERVER_PORT);
// 2.定义一个循环不断的接收客户端的连接请求
while(true){
// 3.开始等待接收客户端的 Socket 管道连接。
Socket socket = serverSocket.accept();
// 4.每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信。
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket ;
public ServerReaderThread(Socket socket){this.socket = socket;}
@Override
public void run() {
try{
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream
(Constants.SERVER_DIR+UUID.randomUUID().toString()+".jpg"));
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
bos.close();
System.out.println("服务端接收完毕了!");
// 4.响应数据给客户端
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("您好,已成功接收您上传的图片!");
ps.flush();
Thread.sleep(10000);
}catch (Exception e){
sout(socket.getRemoteSocketAddress() - "下线了");
}
}
}构造方法:
DataOutputStream(OutputStream out) : 创建一个新的数据输出流,以将数据写入指定的底层输出流DataInputStream(InputStream in) : 创建使用指定的底层 InputStream 的 DataInputStream常用 API:
final void writeUTF(String str) : 使用机器无关的方式使用 UTF-8 编码将字符串写入底层输出流final String readUTF() : 读取以 modified UTF-8 格式编码的 Unicode 字符串,返回 String 类型public class Client {
public static void main(String[] args) {
InputStream is = new FileInputStream("path");
// 1、请求与服务端的 Socket 链接
Socket socket = new Socket("127.0.0.1" , 8888);
// 2、把字节输出流包装成一个数据输出流
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
// 3、先发送上传文件的后缀给服务端
dos.writeUTF(".png");
// 4、把文件数据发送给服务端进行接收
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) > 0 ){
dos.write(buffer , 0 , len);
}
dos.flush();
Thread.sleep(10000);
}
}
public class Server {
public static void main(String[] args) {
ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
// 1、得到一个数据输入流读取客户端发送过来的数据
DataInputStream dis = new DataInputStream(socket.getInputStream());
// 2、读取客户端发送过来的文件类型
String suffix = dis.readUTF();
// 3、定义一个字节输出管道负责把客户端发来的文件数据写出去
OutputStream os = new FileOutputStream("path"+
UUID.randomUUID().toString()+suffix);
// 4、从数据输入流中读取文件数据,写出到字节输出流中去
byte[] buffer = new byte[1024];
int len;
while((len = dis.read(buffer)) > 0){
os.write(buffer,0, len);
}
os.close();
System.out.println("服务端接收文件保存成功!");
}
}
评论