news 2026/6/9 6:16:31

【JavaSE】十七、UDP套接字编程 TCP套接字编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JavaSE】十七、UDP套接字编程 TCP套接字编程

文章目录

  • Ⅰ. UDP 和 TCP 的区别
  • Ⅱ. UDP 套接字编程
    • 一、常用方法
    • 二、服务端
    • 三、客户端
  • Ⅲ. TCP 套接字编程
    • 一、常用方法
    • 二、服务端
      • ① 多线程版本
      • ② 线程池版本
    • 三、客户端

Ⅰ. UDP 和 TCP 的区别

特性TCP(传输控制协议)UDP(用户数据报协议)
类型面向连接、面向字节流无连接、面向数据报
可靠性可靠,数据按顺序到达、无丢包不可靠,可能丢包、乱序
缓冲区既有接收缓冲区,也有发送缓冲区只有接收缓冲区
速度较慢(需握手、确认等)较快(没有连接管理)
应用场景文件传输、Web、聊天视频直播、游戏、广播
Java支持类SocketServerSocketDatagramSocketDatagramPacket

Ⅱ. UDP 套接字编程

一、常用方法

  1. UDP是无连接的,因此每次发送数据报前都需要指定目标地址和目标端口(而TCP则只需要创建套接字时候绑定即可)
  2. Java创建一个DatagramSocket对象,就是在操作系统中打开了一个socket文件,通过这个文件,可以读写数据,而该文件负责将内容与网卡进行交互,从而达到网络通信功能。
  3. DatagramPacket就是一个数据报,DatagramSocket是真正的套接字文件,使用时候就是通过DatagramSocket中的send/receive方法来传输一个个数据报DatagramPacket
类名返回值功能
DatagramSocket()构造方法创建一个 UDP 套接字,系统会随机分配一个可用端口
DatagramSocket(int port)构造方法绑定到指定端口
DatagramSocket(int port, InetAddress laddr)构造方法绑定到指定 IP 和端口
DatagramSocket(SocketAddress bindaddr)构造方法使用 InetSocketAddress 绑定地址和端口
send(DatagramPacket p)void发送一个数据报包到目标地址
receive(DatagramPacket p)void阻塞接收一个数据报包
close()void关闭套接字,释放资源
isClosed()boolean判断套接字是否已关闭
setSoTimeout(int timeout)void设置接收数据的超时时间(毫秒)
getSoTimeout()int获取当前接收超时时间
setReuseAddress(boolean on)void设置是否允许地址重用
getLocalPort()int获取本地绑定的端口号
getLocalAddress()InetAddress获取本地绑定的 IP 地址
DatagramPacket(byte[] buf, int length)构造方法创建空数据报,用于接收数据
DatagramPacket(byte[] buf, int length, InetAddress addr, int port)构造方法创建用于发送的数据报,指定目标地址和端口
DatagramPacket(byte[] buf, int offset, int length, InetAddress addr, int port)构造方法发送指定字节数据,支持偏移和长度控制
DatagramPacket(byte buf[], int offset, int length, SocketAddress address)构造方法创建数据报,通过SocketAddress直接指定端口和地址
getData()byte[]获取数据报的缓冲区内容
setData(byte[] buf)void设置数据缓冲区
getLength()int获取数据报中有效数据长度
setLength(int length)void设置数据报的有效数据长度
getSocketAddress()SocketAddress获取地址、端口的一个结构体 相当于下面两个方法的结合
getAddress()InetAddress获取目标地址或发送者地址
getPort()int获取目标端口或发送者端口
setAddress(InetAddress addr)void设置数据包的目标地址
setPort(int port)void设置数据包的目标端口

💥注意事项:

  1. 要构造InetAddress的话,需要调用InetAddress.getByName(s)静态方法,其中s是地址字符串,比如"127.0.0.1",最后就能拿到的就是地址为sInetAddress对象!
  2. 一般构造DatagramPacketgetSocketAddress()的版本比较方便,因为这个方法实际上就包括了端口和地址,如下图所示:

  1. 创建DatagramPacket的时候要传入的byte数组,实际上是一个应用层的缓冲区,通常建议缓冲区大小设置为大于或等于预期最大UDP包大小,比如40968192
    1. 此外,要使用req.getLength()来获取实际接收数据的长度,而不是buf.length,即在读取数据时要用new String(req.getData(), 0, req.getLength()),避免读到多余的空字节。

二、服务端

  1. 接收客户端发来的请求,并且进行解析
  2. 处理请求
  3. 封装成数据报进行响应
/** * 服务器不需要知道数据从哪里来,所以不需要用字段来存放客户端的IP和端口,直接从请求中获取即可 */publicclassServer{privateDatagramSocketsocket=null;// 通信就靠DatagramSocket对象publicServer(intport)throwsSocketException{socket=newDatagramSocket(port);// 服务端需要指定port,让别人来连接}publicvoidstart()throwsIOException{System.out.println("服务器启动!");while(true){// 1. (阻塞)接收客户端发来的请求,并且进行解析DatagramPacketreq=newDatagramPacket(newbyte[4096],4096);socket.receive(req);Stringdata=newString(req.getData(),0,req.getLength());// 从 DatagramPacket 取到有效的数据// 2. 处理请求Stringoutcome=func(data);// 3. 封装成数据报进行响应(注意要填写目的端口和ip)DatagramPacketresp=newDatagramPacket(outcome.getBytes(),0,outcome.getBytes().length,//req.getAddress(), req.getPort()); // 可以这样子写,但是麻烦,推荐下面的写法req.getSocketAddress());socket.send(resp);// 打印日志,看看效果System.out.printf("[%s:%d] req: %s, resp: %s\n",req.getAddress(),req.getPort(),data,outcome);}}// 业务代码(不是现在的重点)publicStringfunc(Stringdata){returndata;}publicstaticvoidmain(String[]args)throwsIOException{Serverserver=newServer(8080);server.start();}}

三、客户端

  1. 构造请求数据报,注意要传入目的端口和IP到数据报中
  2. 发送请求到服务器
  3. 接收服务器发来的响应,然后进行解析
/** * 1. 因为客户端在发送数据报的时候需要知道服务器的IP和端口,并且由于UDP * 每次发送数据都得传入服务器的IP和端口,所以需要用单独的字段来保存 * * 2. 创建DatagramSocket时候传入的端口是指定客户端自己在本机的端口, * 而不是服务器的端口,注意和上面区分开! */publicclassClient{privateintport;// 服务器的端口privateStringaddr;// 服务器的地址privateDatagramSocketsocket=null;publicClient(intport,Stringaddr)throwsSocketException{this.port=port;this.addr=addr;socket=newDatagramSocket();// 让系统自动分配端口号}publicvoidstart()throwsIOException{System.out.println("客户端启动!");Scannersc=newScanner(System.in);while(true){// 1. 构造请求数据报,注意要传入目的端口和IP到数据报中System.out.print("请输入要发送给服务器的信息:");Stringmessage=sc.nextLine();DatagramPacketreq=newDatagramPacket(message.getBytes(),0,message.getBytes().length,InetAddress.getByName(addr),// 利用静态方法getByName构造InetAddressport);// 2. 发送请求到服务器socket.send(req);// 3. 接收服务器发来的响应,然后进行解析DatagramPacketresp=newDatagramPacket(newbyte[4096],4096);socket.receive(resp);Stringdata=newString(resp.getData(),0,resp.getLength());// 进行日志输出,查看效果System.out.println("响应:"+data);}}publicstaticvoidmain(String[]args)throwsIOException{Clientclient=newClient(8080,"127.0.0.1");client.start();}}

运行效果如下所示:

Ⅲ. TCP 套接字编程

一、常用方法

  1. TCP连接只需要在创建套接字时候绑定端口和地址即可,而不需要像UDP一样每次发送数据的时候都要指定目标端口和地址!
  2. ServerSocket这个类主要负责建立连接、监听新连接,而不负责数据的接收和发送!
  3. Socket这个类主要负责数据的接收和发送!
    1. 因为TCP是面向字节流的,所以实际上数据的接收和发送,都是通过Socket获取套接字文件的输入输出流InputStream/OutputStream,然后以字节为单位,来处理该底层套接字,本质还是文件IO
  4. Java中,如果你没有显式调用connect()创建连接,但你直接使用Socket.getOutputStream().write(...)来写数据,则系统会在你第一次写数据的时候自动调用connect()建立连接!
方法 / 构造方法返回值类型功能说明
ServerSocket()构造方法创建未绑定的服务器套接字
ServerSocket(int port)构造方法创建并绑定到指定端口的套接字
ServerSocket(int port, int backlog)构造方法指定端口与连接请求队列长度
ServerSocket(int port, int backlog, InetAddress bindAddr)构造方法指定端口、队列长度和绑定地址
accept()Socket阻塞等待客户端连接,返回通信用的 Socket
close()void关闭服务器套接字,释放资源
isClosed()boolean判断服务器套接字是否关闭
getInetAddress()InetAddress获取绑定的本地 IP 地址
getLocalPort()int获取绑定的本地端口
setSoTimeout(int timeout)void设置 accept() 阻塞的超时时间(毫秒)
getSoTimeout()int获取当前 accept() 超时时间
Socket()构造方法创建未连接的套接字(用于延迟连接)
Socket(String host, int port)构造方法创建并连接到指定主机和端口
Socket(InetAddress address, int port)构造方法同上,使用 IP 地址连接
getInputStream()InputStream获取输入流,用于接收数据
getOutputStream()OutputStream获取输出流,用于发送数据
close()void关闭连接,释放资源
isClosed()boolean判断是否关闭连接
isConnected()boolean判断是否连接成功
isBound()boolean判断是否已绑定本地地址
getInetAddress()InetAddress获取远程地址
getPort()int获取远程端口号
getLocalAddress()InetAddress获取本地地址
getLocalPort()int获取本地端口号
setSoTimeout(int timeout)void设置输入流读取的超时时间
getSoTimeout()int获取读取超时时间

二、服务端

  1. 监听新连接
  2. 创建新线程来处理新连接Socket,防止主线程阻塞
    1. 读取请求并进行解析
    2. 将解析后的请求进行业务处理
    3. 响应结果给客户端
  3. 关闭Socket,防止资源泄露问题

这里需要关闭Socket对象的原因是一个服务器会创建很多新线程来处理不同的连接,这些连接本质都是套接字文件,如果没有释放文件资源的话,最后就会导致资源泄露问题!

至于ServerSocketDatagramSocket为什么不需要释放,是因为它们全局只有一个对象,而且生命周期是随着程序生命周期的,所以不会出现资源泄露问题!

此外ScannerPrintWriter也不需要释放,因为它们是从 Socket.getXXX() 获得的,本质还是 Socket 对象套接字文件对应的流对象,所以不需要关心 Scanner 和 PrintWriter 的释放问题!

💥注意事项:

  1. 因为Scanner这里使用的是nextLine()hasNextLine(),所以PrintWriter在使用的时候不能用write(),而要用println(),因为write()是不带换行符的,此时就算回车结束了,字符串也不会带回车,那么进入判断语句中就卡在那里了!
    1. 结论:用Scanner.nextLine()读取,就一定要println()写入
  2. 在用PrintWriter写入数据到套接字文件后,要调用flush()进行缓冲区刷新,不然只能缓冲区快满了自动刷新!

① 多线程版本

/** * 服务器只需要指定端口即可 */publicclassServer{privateServerSocketsocket=null;publicServer(intport)throwsIOException{socket=newServerSocket(port);}publicvoidstart()throwsIOException{System.out.println("TCP服务器启动!");while(true){// 1. 监听新连接Socketconn=socket.accept();System.out.println("获取到新连接:"+conn.getInetAddress()+"/"+conn.getPort());// 2. 创建新线程来处理新连接,防止主线程阻塞newThread(()->{work(conn);}).start();}}// 新连接实际上要处理的任务privatevoidwork(Socketconn){try(InputStreamin=conn.getInputStream();OutputStreamout=conn.getOutputStream();Scannersc=newScanner(in);PrintWriterpw=newPrintWriter(out)){while(true){// 3. 读取请求并进行解析if(sc.hasNextLine()==false){// 要判断是否断开连接:如果客户端断开连接了,则会返回falseSystem.out.printf("[%s:%d] 客户端下线!\n",conn.getInetAddress().toString(),conn.getPort());break;}Stringreq=sc.nextLine();// 4. 将解析后的请求进行业务处理Stringresp=func(req);// 5. 响应结果给客户端pw.println(resp);// ✔自动添加换行符,满足服务端 Scanner.nextLine(),使用write则会死循环!pw.flush();// ❗❗❗细节❗❗❗// 搞一下日志输出看看效果System.out.printf("[%s:%d] req: %s, resp: %s\n",conn.getInetAddress().toString(),conn.getPort(),req,resp);}}catch(IOExceptione){thrownewRuntimeException(e);}finally{// 6. 释放Socket资源try{conn.close();}catch(IOExceptione){thrownewRuntimeException(e);}}}privateStringfunc(Stringdata){returndata;}publicstaticvoidmain(String[]args)throwsIOException{Serverserver=newServer(9090);server.start();}}

② 线程池版本

只需要改动上面start()函数里面创建线程的方式即可:

publicvoidstart()throwsIOException{System.out.println("TCP服务器启动!");while(true){// 监听新连接Socketconn=socket.accept();System.out.println("获取到新连接:"+conn.getInetAddress()+"/"+conn.getPort());// 创建线程池来处理新连接,推荐用newCachedThreadPoolExecutorServicepool=Executors.newCachedThreadPool();pool.submit(()->{work(conn);});}}

三、客户端

  1. 输入数据,写入Socket对象中,然后进行刷新
  2. 接收服务器的响应
publicclassClient{privateSocketconn=null;publicClient(Stringaddr,intport)throwsIOException{conn=newSocket(addr,port);}publicvoidstart(){System.out.println("客户端启动!");Scannertmp=newScanner(System.in);try(InputStreamin=conn.getInputStream();OutputStreamout=conn.getOutputStream();Scannersc=newScanner(in);PrintWriterpw=newPrintWriter(out)){while(true){// 1. 输入数据,写入Socket中,然后刷新System.out.print("请输入要发送的数据:");Stringreq=tmp.nextLine();pw.println(req);// ✔自动添加换行符,满足服务端 Scanner.nextLine(),使用write则会死循环!pw.flush();// ❗❗❗细节❗❗❗// 2. 接收服务器的响应if(sc.hasNextLine()==false){System.out.println("客户端断开连续!");break;}Stringresp=sc.nextLine();// 打印日志看看效果System.out.printf("[%s:%d] req: %s, resp: %s\n",conn.getInetAddress().toString(),conn.getPort(),req,resp);}}catch(IOExceptione){thrownewRuntimeException(e);}}publicstaticvoidmain(String[]args)throwsIOException{Clientclient=newClient("127.0.0.1",9090);client.start();}}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 18:19:56

Java数组的初始化与实例化:从概念到实战,拆解核心逻辑与避坑指南

Java数组的初始化与实例化:从概念到实战,拆解核心逻辑与避坑指南 在Java编程中,数组是最基础的引用数据类型之一,也是处理批量同类型数据的核心工具。但很多开发者(尤其是初学者)常混淆「初始化」和「实例化…

作者头像 李华
网站建设 2026/6/9 16:01:22

学生选课信息管理系统

学生信息管理 目录 基于springboot vue学生信息管理系统 一、前言 二、系统功能演示 详细视频演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue学生选课信息管理系统 一、…

作者头像 李华
网站建设 2026/6/7 5:48:15

基于Android的居家养老管理系统(源码+lw+部署文档+讲解等)

课题介绍 本课题聚焦居家养老服务响应慢、老人状态监测不及时、家属监管不便的痛点,设计实现基于 Android 的居家养老管理系统。系统以 Java 为核心开发语言,基于 Android 原生框架搭建移动端应用,搭配后端云服务架构,处理老人健康…

作者头像 李华
网站建设 2026/6/6 6:46:53

LobeChat能否撰写商业计划书?创业者的秘密武器

LobeChat能否撰写商业计划书?创业者的秘密武器 在今天这个快节奏的创业环境中,一份逻辑清晰、数据扎实、结构完整的商业计划书,往往是决定项目能否获得投资的关键。然而现实是,大多数创业者既不是专业写手,也没有专职的…

作者头像 李华
网站建设 2026/6/9 18:54:55

中小企业备份方案: 如何评估备份方案是否符合企业实际需求

成本、安全性与法规合规性这三方面考量,构成了评估备份解决方案是否符合企业实际需求的基础框架。业务需求评估评估备份解决方案时,必须首先审视公司的具体需求。企业通常有着多样化的数据保护要求,例如保护 Microsoft 365 等关键应用&#x…

作者头像 李华