『JavaWeb』socket网络编程

本文深入解析UDP和TCP网络编程,涵盖DatagramSocket与ServerSocket类详解,演示简单服务端与客户端交互流程,探讨多线程及线程池在处理并发连接中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本篇博客主要介绍套接字网络编程的相关知识。

UDP网络程序


DatagramSocket类

方法说明
DatagramSocket(int port, InetAddress laddr)创建一个数据报套接字,绑定到指定的本地地址
DatagramSocket(SocketAddress bindaddr)创建一个数据报套接字,绑定到指定的本地套接字地址
void bind(SocketAddress addr)将此DatagramSocket绑定到特定的地址和端口
void connect(InetAddress addres, int port)将套接字连接到此套接字的远程地址
void receive(DatagramPacket p)从此套接字接收数据包
void close()关闭此数据包套接字
void send(DatagramPacket p)从此发送数据报包

简单的UDP网络程序

  • 实现一个简单的服务端接收客户端输入的信息然后在服务端显示

服务端代码

package udp;

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

public class UdpServer {

    public static void main(String[] args) {
        new UdpServer().start();
    }

    // 绑定端口号
    private static final int PORT = 9999;
    // 设置数据报大小
    private static final int DATA_LEN = 4096;
    private byte[] recvBuffer = new byte[DATA_LEN];

    // 用于接收数据
    private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
    // 用于发送数据
    private DatagramPacket out = null;

    public void start() {
        try {
            DatagramSocket socket = new DatagramSocket(PORT);
            SocketAddress address = null;
            String word = null;
            byte[] sendBuffer = null;

            System.out.println("服务端启动了...");

            while (true) {
                socket.receive(in);
                address = in.getSocketAddress();

                recvBuffer = in.getData();
                word = new String(recvBuffer).trim();
                System.out.println("客户端:" + word);

                if ("down".equals(word)) {
                    sendBuffer = "服务端已关闭,请重试!".getBytes();
                    out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
                    socket.send(out);
                    break;
                }

                sendBuffer = ("您输入的内容是:" + word).getBytes();
                out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
                socket.send(out);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端代码

package udp;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpClient {

    public static void main(String[] args) {
        new UdpClient().start();
    }

    // 目的端口号
    private static final int DEST_PORT = 9999;
    // 目的IP
    private static final String DEST_IP = "127.0.0.1";
    // 数据报长度
    private static final int DATA_LEN = 4096;
    private byte[] recvBuffer = new byte[DATA_LEN];

    // 用于接收数据
    private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
    // 用于发送数据
    private DatagramPacket out = null;

    public void start() {
        try {
            DatagramSocket socket = new DatagramSocket();

            InetAddress ip = InetAddress.getByName(DEST_IP);
            out = new DatagramPacket(new byte[0], 0, ip, DEST_PORT);

            Scanner sc = new Scanner(System.in);
            System.out.println("请输入数据:");

            String word = null;
            byte[] sendBuffer = null;
            while (sc.hasNextLine()) {
                word = sc.nextLine();

                if ("exit".equals(word)) {
                    break;
                }

                sendBuffer = word.getBytes();
                out.setData(sendBuffer);
                socket.send(out);

                socket.receive(in);
                System.out.println(new String(recvBuffer, 0, in.getLength()));
                System.out.println("请输入数据:");
            }

            System.out.println("客户端退出...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

实现简单的字典服务器


代码如下

package udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;

public class UdpDictServer {

    public static void main(String[] args) {
        new UdpDictServer().start();
    }

    private static final int PORT = 9999;
    private static final int DATA_LEN = 4096;
    private byte[] recvBuffer = new byte[DATA_LEN];

    private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
    private DatagramPacket out = null;

    private static Map<String, String> dict = new HashMap<>();
    static {
        dict.put("苹果", "apple");
        dict.put("香蕉", "banana");
        dict.put("草莓", "strawberry");
        dict.put("西瓜", "watermelon");
    }

    public void start() {
        try {
            DatagramSocket socket = new DatagramSocket(PORT);
            SocketAddress address = null;
            byte[] sendBuffer = null;
            String word = null;
            String result = null;

            System.out.println("字典服务器启动...");

            while (true) {
                socket.receive(in);
                recvBuffer = in.getData();
                word = new String(recvBuffer).trim();
                result = dict.get(word);

                if (result == null) {
                    result = "查询失败!";
                }

                address = in.getSocketAddress();

                if ("down".equals(word)) {
                    sendBuffer = "服务端已关闭!".getBytes();
                    out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
                    socket.send(out);
                    break;
                }

                sendBuffer = result.getBytes();
                out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
                socket.send(out);
            }

            System.out.println("服务端关闭...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下
在这里插入图片描述

TCP网络程序


TCP socket API


ServerSocket类

方法说明
ServerSocket(int port)创建绑定到指定端口的服务器套接字
ServerSocket(int port, int backlog)创建服务器套接字并绑定到指定的端口号,并指定了积压
Socket accept()侦听要连接到此套接字并接受它
bind(SocketAddress endpoint)将ServerSocket绑定到特定地址(IP和端口号)
InetAddress getInetAddress()返回此服务器套接字的本地地址
void close()关闭此套接字
int getLocalPort()返回此套接字正在侦听的端口号

Socket类

方法说明
Socket(InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
Socket(String host, int port)创建流套接字并将其连接到指定主机的指定端口号
void bind(SocketAddress bindpoint)将套接字绑定到本地地址
void connect(SocketAddress endpoint)将次套接字连接到服务器
InetAddress getInetAddress()返回套接字锁连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

简单的TCP服务端和客户端程序

TCP服务端代码如下

package tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

    public static void main(String[] args) {
        try {
            ServerSocket server = new ServerSocket(9999);
            System.out.println("服务器启动...");

            Socket client = server.accept();
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(client.getInputStream())
            );
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter(client.getOutputStream())
            );

            String word = null;

            while ((word = br.readLine()) != null) {
                if (word != null) {
                    System.out.println("客户端:" + word);
                    bw.write("您输入的是\"" + word + "\"\n");
                    bw.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TCP客户端代码如下

package tcp;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpClient {

    private static final String HOST = "127.0.0.1";
    private static final int PORT = 9999;

    public static void main(String[] args) {
        try {
            Socket socket = new Socket(HOST, PORT);

            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );

            Scanner sc = new Scanner(System.in);

            while (sc.hasNextLine()) {
                bw.write(sc.nextLine() + "\n");
                bw.flush();

                System.out.println("服务端:" + br.readLine());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下
在这里插入图片描述

我们可以看到服务端和客户端能够正常通信,但是这个代码还有点问题

测试多客户端连接的情况


我们启动一个服务器,然后启动多个客户端去连接这个服务器

如何启动多个客户端程序呢,步骤如下

  • 点击Run
    在这里插入图片描述
  • 选中其中的Edit Configurations…
    在这里插入图片描述
  • 左边选中我们要重复启动的类
    在这里插入图片描述
  • 勾选右上角的Allow parallel run,然后点击Apply
    在这里插入图片描述

配置完成,我们来测试一下多客户端连接的情况

  • 首先,我们启动服务端程序
    在这里插入图片描述
  • 然后,我们启动一个客户端,测试一下单个客户端的情况
    在这里插入图片描述
  • 前面可以看到,启动一个客户端一切正常,下面我们再启动一个客户端
    在这里插入图片描述

从上述运行结果,我们可以看到第二个客户端没反应,这是为什么呢?明明一个客户端的时候能够正常
我们来仔细读一下服务端的代码

ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");

Socket client = server.accept();
BufferedReader br = new BufferedReader(
        new InputStreamReader(client.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(client.getOutputStream())
);

String word = null;

while ((word = br.readLine()) != null) {
    if (word != null) {
        System.out.println("客户端:" + word);
        bw.write("您输入的是\"" + word + "\"\n");
        bw.flush();
    }
}

其实原因也很容易理解,因为我们阻塞等待客户端连接的代码只会执行一次,也就是说,这个服务器只能接受跟一个客户端建立连接,因为server.accept()这个代码只会执行一次,所以就出现了上面的现象。
是不是说我们把这些代码全部放到while循环中就可以了呢
我们来试一下:

ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");

while (true) {
    Socket client = server.accept();
    BufferedReader br = new BufferedReader(
            new InputStreamReader(client.getInputStream())
    );
    BufferedWriter bw = new BufferedWriter(
            new OutputStreamWriter(client.getOutputStream())
    );

    String word = null;
    while ((word = br.readLine()) != null) {
        System.out.println("客户端:" + word);
        bw.write("您输入的是\"" + word + "\"\n");
        bw.flush();
    }
}

这个代码也是不行的,因为代码会一直在下面这个while循环中不停的执行,还是没办法去执行上面的server.accept()这行代码。只有当下面while循环结束的时候才会去执行上面的server.accept()这行代码,也就是当前客户端断开连接后,才回去建立新的连接,这显然是不行的

下面我们来看一下改进的方法。

多线程版本的TCP程序


TCP服务端代码

package tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

    public static void main(String[] args) {
        try {
            ServerSocket server = new ServerSocket(9999);
            System.out.println("服务器启动...");

            int index = 1;

            while (true) {
                Socket client = server.accept();

                System.out.println("客户端" + index + "已连接...");
                new Thread(new ServerThread(client, index)).start();
                ++index;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package tcp;

import java.io.*;
import java.net.Socket;

public class ServerThread implements Runnable {

    private Socket socket = null;
    private int index = 0;

    public ServerThread(Socket socket, int index) {
        this.socket = socket;
        this.index = index;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );

            String word = null;

            while ((word = br.readLine()) != null) {
                System.out.println("客户端" + index + ":" + word);
                bw.write("您输入的是\"" + word + "\"\n");
                bw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我们来运行验证一下

  • 首先,我们启动服务端,然后启动三个客户端
    在这里插入图片描述
  • 我们来验证一下三个客户端
    在这里插入图片描述

线程池版本的TCP程序


服务端代码

package tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpServer {

    private static final ExecutorService EXE = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        try {
            ServerSocket server = new ServerSocket(9999);
            System.out.println("服务器启动...");

            int index = 1;

            while (true) {
                Socket client = server.accept();

                System.out.println("客户端" + index + "已连接...");
                EXE.submit(new ServerThread(client, index));
                ++index;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package tcp;

import java.io.*;
import java.net.Socket;

public class ServerThread implements Runnable {

    private Socket socket = null;
    private int index = 0;

    public ServerThread(Socket socket, int index) {
        this.socket = socket;
        this.index = index;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );

            String word = null;

            while ((word = br.readLine()) != null) {
                System.out.println("客户端" + index + ":" + word);
                bw.write("您输入的是\"" + word + "\"\n");
                bw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值