Java网络编程socket

发布于:2025-03-17 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、UDP

特点:

 ① 用户数据报协议(User Datagram Protocol)

​ ② UDP是面向无连接通信协议

 ​③ 速度快,一次只能传输64KB数据,数据不安全,容易丢失

(1)单播

    一对一

    客户端:

  1. 定义socket
  2. 封装数据报
  3. 发送到目标主机
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UDPClient {
    public static void main(String[] args)throws SocketException, IOException {

        //  1.创建DatagramSocket
        DatagramSocket datagramSocket = new DatagramSocket(60001);
        //  2.封装要传输的数据
        String content = "hello";
        InetAddress targetHost = InetAddress.getLocalHost();
        int targetPort = 8080;
        DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), content.length(), targetHost, targetPort);
        //  3.发送数据
        datagramSocket.send(datagramPacket);
    }
}

服务端:

  1. 定义socket
  2. 定义接收数据报的DatagramPacket
  3. 接收数据报
  4. 解析数据报
import java.io.IOException;
import java.net.*;

public class UDPServer {
    public static void main(String[] args) throws IOException {
        //  1.定义数据报socket
        DatagramSocket datagramSocket = new DatagramSocket(8080);
        //  2.定义接收数据报的对象
        byte[] receiveContent = new byte[1024 * 64];    //  UDP一次最多只能传输64KB
        DatagramPacket datagramPacket = new DatagramPacket(receiveContent, receiveContent.length);

        while (true) {

            //  3.接收数据报(阻塞等待)
            datagramSocket.receive(datagramPacket);

            //  4.解析数据报
            byte[] data = datagramPacket.getData();
            int length = datagramPacket.getLength();
            InetAddress address = datagramPacket.getAddress();
            SocketAddress socketAddress = datagramPacket.getSocketAddress();
            int port = datagramPacket.getPort();

            System.out.println("data: " + new String(data, 0, length));
            System.out.println("length: " + length);
            System.out.println("ip port:" + address + " " + port);
            System.out.println("socketAddress: " + socketAddress);
            System.out.println(receiveContent == data);
        }
    }
}

(2)组播

   ​ 给一组电脑发消息

   ​ 组播地址:224.0.0.0 ~ 239.255.255.255

​    其中224.0.0.0 ~ 224.0.0.255为预留的组播地址

​    客户端:

  1. 创建组播socket(MulticastSocket)
  2. 封装数据报文(ip填写组ip)
  3. 发送报文
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class Client {

    public static void main(String[] args) throws IOException {
        //  1.创建组播socket
        MulticastSocket ms = new MulticastSocket(60001);

        //  2.封装数据报文
        byte[] data = new byte[64 * 1024];
        InetAddress targetHost = InetAddress.getByName("224.0.0.1");  //  组地址
        DatagramPacket dp = new DatagramPacket(data, data.length, targetHost, 8080);
        String content = "hello";
        dp.setData(content.getBytes(), 0, content.length());

        //  3.发送报文
        ms.send(dp);
    }
}

服务端

  1. 创建组播socket(MulticastSocket)
  2. 将当前服务加入到指定ip组
  3. 创建接收数据的报文对象DatagramPacket
  4. 等待接收客户端发来的数据
  5. 解析报文
import java.io.IOException;
import java.net.*;

public class Server {

    public static void main(String[] args) throws IOException {
        
        //  1.创建组播socket
        MulticastSocket ms = new MulticastSocket(8080);
        //  2.给当前服务加入组
        ms.joinGroup(InetAddress.getByName("224.0.0.1"));
        //  3.创建接收数据的报文
        byte[] data = new byte[64 * 1024];
        DatagramPacket dp = new DatagramPacket(data, data.length);
        
        while (true) {
            //  4.接收数据
            ms.receive(dp);
            
            //  5.解析数据
            SocketAddress socketAddress = dp.getSocketAddress();
            int length = dp.getLength();
            int offset = dp.getOffset();
            System.out.println(socketAddress + ": " + new String(data, offset, length));
        }
    }
}

(3)广播

   向局域网下所有的服务发送报文

   只需要将单播的发送ip改为255.255.255.255就可以了

   客户端

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/*
    广播
 */
public class Client {

    public static void main(String[] args) throws IOException {
        //  1.创建socket
        DatagramSocket ds = new DatagramSocket(60001);
        
        //  2.封装数据报
        byte[] data = new byte[64 * 1024];
        DatagramPacket dp = new DatagramPacket(data, data.length, InetAddress.getByName("255.255.255.255"), 8080);
        String content = "hello";
        dp.setData(content.getBytes(), 0, content.length());
        
        //  3.发送数据报
        ds.send(dp);
    }
}

服务端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.net.SocketException;

public class Server {

    public static void main(String[] args) throws IOException {
        //  1.创建socket
        DatagramSocket ds = new DatagramSocket(8080);
        //  2.创建接收数据的报文
        byte[] data = new byte[64 * 1024];
        DatagramPacket dp = new DatagramPacket(data, data.length);

        while (true) {
            //  3.接收数据
            ds.receive(dp);
            
            //  4.解析报文
            SocketAddress socketAddress = dp.getSocketAddress();
            int length = dp.getLength();
            int offset = dp.getOffset();
            System.out.println(socketAddress + ": " + new String(data, offset, length));
        }
    }
}

二、TCP

TCP是一种可靠的通信协议,在通信的两端各建立一个Socket对象,通信之前连接已经建立,数据是通过IO流进行网络传输的

特点:

​  1. 传输控制协议(Transmission Control Protocol)

  2. TCP协议是面向连接的协议

​  3. 速度慢,没有限制,数据安全

(1)简单通信

   客户端

  1. 创建socket建立连接
  2. 通过IO流通道进行网络传输
import java.io.*;
import java.net.Socket;

public class TCPClient {

    public static void main(String[] args) throws IOException {

        //  1.创建socket,与服务端建立连接
        Socket socket = new Socket("127.0.0.1", 8080);
        
        //  2.获取输入输出流
        //      获取输出流
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        //      获取输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        
        //  3.通过输出流发送数据
        String data = "hello";
        out.println(data);
        
        //  4.通过输入流等待服务端回应
        String res = in.readLine();
        System.out.println("回应:" + res);
    }
}

服务端

  1. 创建ServerSocket,监听指定端口
  2. 等待与客户端建立连接
  3. 通过输入流读取客户端传递的数据,通过输出流给客户端响应
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

    public static void main(String[] args) throws IOException {
        
        //  1.创建server socket
        ServerSocket serverSocket = new ServerSocket(8080);
        
        //  2.等待客户端建立连接
        Socket accept = serverSocket.accept();
        
        //  3.获取输入输出流
        BufferedReader in = new BufferedReader(new InputStreamReader(accept.getInputStream()));
        PrintWriter out = new PrintWriter(accept.getOutputStream(), true);
        
        while (true) {
            //  4.等待客户端的数据
            String msg = in.readLine();
            System.out.println(msg);
            
            //  5.通过输出流给客户端响应
            out.println("好的,我收到了");
        }
    }
}

(2)聊天室案例

   客户端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    
    private static final Scanner sc = new Scanner(System.in);;
    public static void main(String[] args) throws IOException {
        //  输入用户名
        System.out.print("请输入你的聊天名称:");
        String user = sc.next();

        //  建立socket连接
        Socket socket = new Socket("127.0.0.1", 10000);

        //  通过输出流注册用户
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        out.println(user);

        //  开启一个线程接收服务端发来的消息
        Thread serverThread = new Thread(() -> {
            //  获取输入流
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //  读取传来的消息
                while (true) {
                    //  通过输入流读取客户端传递的数据
                    String msg = br.readLine();
                    if (msg == null || msg.trim().equals("")) {
                        continue;
                    }
                    //  打印在控制台
                    System.out.println("\n"+ msg);
                    System.out.print("\n"+ user + ": ");
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        serverThread.start();
        
        //  与用户交互的控制台
        while (true) {
            System.out.print("\n" + user + ": ");
            //  获取用户的输入消息
            String msg = sc.nextLine();
            if (msg == null || msg.trim().equals("")) {
                continue;
            }
            //  发送给服务端
            out.println(msg);
        }
    }
}

服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatRoomServer {

    private static final ExecutorService executorService = Executors.newFixedThreadPool(16);

    public static void main(String[] args) throws IOException {
        //  创建ServerSocket监听10000端口
        ServerSocket serverSocket = new ServerSocket(10000);
        //  存储所有的socket
        Vector<Socket> sockets = new Vector<>();
        ConcurrentHashMap<String, Socket> socketMap = new ConcurrentHashMap<>();

        System.out.println("===============聊天室服务端创建完毕==============");
        
        //  一直等待客户端来建立连接
        while (true) {
            
            //  等待客户端建立连接
            Socket socket = serverSocket.accept();
            
            //  将建立好连接的客户端交给线程池去执行聊天任务
            executorService.submit(() -> {
                String user = null;
                try {
                    //  字节流作为字符流读取
                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    //  输出流
                    PrintWriter out = new PrintWriter(socket.getOutputStream());
                    //  读取用户名称
                    user = br.readLine();

                    //  用户已经存在
                    if (socketMap.contains(user)) {
                        out.printf("用户'%s'已经存在!", user);
                        socket.close();
                        return;
                    }

                    //  添加用户进入聊天室
                    sockets.add(socket);
                    socketMap.put(user, socket);

                    //  告诉所有用户来新人了
                    String notify = LocalDateTime.now() + ": 来新人了!!!欢迎小伙伴【" + user + "】";
                    System.out.println(notify);
                    
                    //  将消息同步给其他用户
                    for (Socket soc : sockets) {
                        //  自己发的消息,不用通知自己
                        if (soc == socket) {
                            continue;
                        }
                        PrintWriter printWriter = new PrintWriter(soc.getOutputStream(), true);
                        printWriter.println(notify);
                    }

                    //  循环等待读取用户消息
                    while (true) {
                        //  等待用户发来消息
                        String content = br.readLine();
                        if (content == null || content.trim().equals("")) {
                            continue;
                        }
                        String msg = user + ": " + content;
                        System.out.println(msg);
                        
                        //  将消息发送给其他人
                        for (Socket soc : sockets) {
                            if (soc == socket) {
                                continue;
                            }
                            PrintWriter printWriter = new PrintWriter(soc.getOutputStream(), true);
                            printWriter.println(msg);
                        }
                    }
                } catch (IOException e) {
                    //  出现异常,将用户踢出
                    sockets.remove(socket);
                    if (user != null) {
                        socketMap.remove(user);
                        System.out.printf("用户'%s'退出聊天室", user);
                    }
                    throw new RuntimeException(e);
                }
            });
        }

    }
}

tcp三次握手(保证连接的建立)

  1. 客户端向服务端发出连接请求(SYN),等待服务器确认
  2. 服务端响应给客户端(ACK,SYN)代表接收到请求并询问客户端
  3. 客户端向服务端确认信息(ACK),连接建立

tcp四次挥手(确保连接断开,且数据处理完毕)

  1. 客户端向服务器发送断开连接的请求
  2. 服务端向客户端响应,表示收到了客户端的取消请求,然后处理剩余数据
  3. 服务端向客户端确认取消
  4. 客户端发送确认消息,连接取消

三、拓展

IP地址

IPv4
        ① 32位长度,分为4组,比如192.168.1.101,总共不到43亿个IP
        ② 利用局域网分配IP缓解IP不够用的问题
        ③ 向127.0.0.1发送数据不会经过路由器,如果192.168.1.100是当前电脑的IP,向这个地址发送数据会经过路由器

IPv6
        ① 128位,16位为一组,共分为8组,冒分十六进制表示法
如:2001:0DB8:0000:0023:0008:0008:200C:417A,省略无效的0 -> 2001:DB8:0:23:8:8:200C:417A
        ② 特殊情况:如果计算出的16进制表示形式中间有多个连续的0,则会进行0位压缩,
如:FF01:0:0:0:0:0:0:1101 -> FF01::1101

常见软件架构:

C/S:Client/Server(客服端/服务端)

  1. 在用户本地需要下载并安装客户端程序,远程有一个服务端程序
  2. 软件更新比较麻烦,需要客户端下载资源

B/S:Browser/Server(浏览器/服务端)

  1. 只需要浏览器访问不同网址就可以访问不同服务器
  2. 访问即可使用,但是不适合大型游戏,因为资源的临时加载的,所以比较耗时