java使用教程怎样实现基于TCP的网络通信 java使用教程的TCP编程入门技巧​

java中实现tcp通信需编写服务器端和客户端代码,服务器使用serversocket监听端口并接受连接,客户端通过socket连接服务器,双方通过输入输出流交换数据,tcp确保可靠、有序传输。1. 服务器端创建serversocket绑定端口,调用accept()阻塞等待客户端连接,每接受一个连接便启动新线程处理,避免阻塞主线程;2. 客户端创建socket指定服务器ip和端口发起连接,成功后通过输入输出流与服务器通信;3. 使用try-with-resources语法自动关闭socket和流,防止资源泄露;4. 处理连接断开时需检测read()返回-1或捕获ioexception,及时释放资源;5. 数据传输中若需发送对象,可选择java序列化(仅限java间通信)、json/xml(跨语言、可读性强)或protocol buffers(高性能、小体积、跨语言)。选择tcp而非udp因其提供可靠、有序、连接性的数据传输,适用于文件传输、聊天等对数据完整性要求高的场景,而udp适用于实时性高、可容忍丢包的场景如音视频直播。

java使用教程怎样实现基于TCP的网络通信 java使用教程的TCP编程入门技巧​

Java中实现基于TCP的网络通信,说白了,就是服务器和客户端通过建立连接,然后像打电话一样,你一句我一句地交换数据。核心在于服务器端的

ServerSocket

监听端口,等待连接;客户端用

Socket

去连接服务器。一旦连接建立,双方就都能通过各自的

Socket

获取输入输出流,进行数据的读写了。这整个过程,TCP确保了数据的可靠、有序传输,不会丢包,也不会乱序。

解决方案

要实现TCP通信,我们通常需要编写两部分代码:服务器端和客户端。

服务器端(Server)

立即学习“Java免费学习笔记(深入)”;

服务器的主要任务是监听一个特定的端口,等待客户端的连接请求。一旦有客户端连接上来,它就接受这个连接,然后与客户端进行数据交换。

import java.io.*;import java.net.*;public class SimpleTcpServer {    public static void main(String[] args) {        int port = 8080; // 监听的端口        ServerSocket serverSocket = null;        try {            serverSocket = new ServerSocket(port);            System.out.println("服务器已启动,正在监听端口 " + port + "...");            while (true) { // 持续监听新的连接                Socket clientSocket = serverSocket.accept(); // 阻塞,直到有客户端连接                System.out.println("客户端 " + clientSocket.getInetAddress().getHostAddress() + " 已连接。");                // 为每个客户端连接创建一个新的线程来处理,避免阻塞主线程                new Thread(() -> {                    try (                        // 使用try-with-resources确保流自动关闭                        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));                        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true) // true表示自动刷新                    ) {                        String clientMessage;                        while ((clientMessage = in.readLine()) != null) {                            System.out.println("收到客户端消息: " + clientMessage);                            out.println("服务器已收到: " + clientMessage); // 回复客户端                            if ("bye".equalsIgnoreCase(clientMessage.trim())) {                                break; // 客户端发送"bye"则结束通信                            }                        }                    } catch (IOException e) {                        System.err.println("处理客户端连接时发生错误: " + e.getMessage());                    } finally {                        try {                            clientSocket.close(); // 关闭客户端socket                            System.out.println("客户端连接已关闭: " + clientSocket.getInetAddress().getHostAddress());                        } catch (IOException e) {                            System.err.println("关闭客户端socket时发生错误: " + e.getMessage());                        }                    }                }).start();            }        } catch (IOException e) {            System.err.println("服务器启动或运行异常: " + e.getMessage());        } finally {            if (serverSocket != null && !serverSocket.isClosed()) {                try {                    serverSocket.close();                    System.out.println("服务器已关闭。");                } catch (IOException e) {                    System.err.println("关闭服务器socket时发生错误: " + e.getMessage());                }            }        }    }}

客户端(Client)

客户端相对简单,它需要知道服务器的IP地址和端口号,然后尝试连接。连接成功后,就可以向服务器发送数据,并接收服务器的回复。

import java.io.*;import java.net.*;import java.util.Scanner;public class SimpleTcpClient {    public static void main(String[] args) {        String serverIp = "127.0.0.1"; // 服务器IP地址,这里用本机        int port = 8080; // 服务器端口        try (            Socket socket = new Socket(serverIp, port); // 尝试连接服务器            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));            PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // true表示自动刷新            Scanner scanner = new Scanner(System.in) // 用于从控制台读取用户输入        ) {            System.out.println("已连接到服务器 " + serverIp + ":" + port);            String userInput;            String serverResponse;            while (true) {                System.out.print("请输入消息 (输入 'bye' 退出): ");                userInput = scanner.nextLine();                out.println(userInput); // 发送消息给服务器                if ("bye".equalsIgnoreCase(userInput.trim())) {                    break; // 如果输入"bye",则退出循环                }                // 尝试读取服务器的回复,这里需要注意服务器端是否会立即回复                // 如果服务器是异步处理,或者回复有延迟,这里可能会阻塞                // 对于简单的请求-回复模式,readLine通常是可行的                if ((serverResponse = in.readLine()) != null) {                    System.out.println("收到服务器回复: " + serverResponse);                }            }        } catch (UnknownHostException e) {            System.err.println("未知主机: " + serverIp);        } catch (IOException e) {            System.err.println("无法连接到服务器或I/O错误: " + e.getMessage());        } finally {            System.out.println("客户端已断开连接。");        }    }}

为什么选择TCP而不是UDP进行网络通信?

在我刚接触网络编程的时候,也曾纠结过TCP和UDP这两种协议的选择。简单来说,TCP(传输控制协议)就像你寄送一份非常重要的快递,需要签名、需要确认收货,确保货物完整无损、顺序不乱地送到。它提供的是可靠的、面向连接的、基于字节流的服务。这意味着:

可靠性: TCP会确保数据到达目的地,如果数据丢失,它会自动重传。你不需要操心数据包是不是丢了,是不是乱序了。有序性: 数据包会按照发送的顺序到达接收方,即使在网络中它们走的路线不同。连接性: 在数据传输之前,客户端和服务器之间会先建立一个“握手”过程,形成一个逻辑连接。数据传输完毕后,也会有一个“挥手”过程来断开连接。流量控制和拥塞控制: TCP会根据网络状况调整发送速率,避免发送方把接收方或网络淹没。

而UDP(用户数据报协议)则更像你往一个方向扔纸飞机,你只管扔出去,至于它能不能飞到、飞到谁那里、什么时候飞到,你一概不管。它是不可靠的、无连接的、基于数据报的服务。UDP的优点在于它的速度快、开销小,因为它省去了建立连接、重传、排序等复杂机制。

所以,选择哪一个,完全取决于你的应用场景。如果你的应用对数据完整性和顺序有严格要求,比如文件传输、网页浏览(HTTP底层就是TCP)、邮件发送、在线聊天等,那么TCP是你的不二之选。如果你的应用对实时性要求极高,可以容忍少量数据丢失,比如在线音视频直播、网络游戏(部分数据)、DNS查询等,那么UDP可能会是更好的选择,因为它能提供更低的延迟。在我看来,对于大多数需要确保数据正确送达的应用,TCP无疑是更稳妥、更省心的方案。

Java TCP编程中常见的陷阱与应对策略

说实话,刚开始写Java TCP代码,特别是涉及到多线程和资源管理时,确实踩过不少坑。这里总结几个常见的“陷阱”以及我的应对方法:

阻塞I/O的“死锁”与性能问题:

陷阱: 默认情况下,

socket.getInputStream().read()

socket.getOutputStream().write()

都是阻塞的。这意味着如果一方没有数据可读或可写,对应的线程就会一直等待。在服务器端,如果不对每个客户端连接使用单独的线程处理,一个客户端的阻塞操作就会导致整个服务器无法响应其他客户端。应对策略:多线程处理客户端: 最常见的做法是,每当

ServerSocket.accept()

接受到一个新的客户端连接时,就为这个

Socket

创建一个新的线程来处理其输入输出。这样,一个客户端的阻塞不会影响其他客户端。我在上面的服务器代码中就是这么做的。非阻塞I/O(NIO): 对于需要处理大量并发连接,且每个连接数据量不大但交互频繁的场景,传统的多线程模型可能会因为线程上下文切换开销过大而性能下降。这时,Java NIO(New I/O)就是更好的选择。NIO引入了

Selector

(选择器),一个线程可以同时监听多个

Channel

(通道)上的事件(如连接就绪、读就绪、写就绪),从而实现高并发。不过,NIO的学习曲线相对陡峭,对于入门来说,多线程模型已经足够了。

资源未正确关闭导致的泄露:

陷阱:

Socket

ServerSocket

InputStream

OutputStream

等都是系统资源,如果不及时关闭,可能会导致文件句柄泄露、端口占用等问题,最终影响系统稳定性和性能。特别是在发生异常时,资源很容易被遗忘。应对策略:

try-with-resources

这是Java 7及更高版本提供的语法糖,对于实现了

AutoCloseable

接口的资源(大部分I/O流和Socket都实现了),可以在

try

语句中声明并初始化它们,JVM会在

try

块结束时(无论正常结束还是异常结束)自动调用它们的

close()

方法。这是我强烈推荐的写法,它让代码更简洁,也更健壮。

finally

块: 如果你使用的是旧版Java或者资源没有实现

AutoCloseable

,那么在

finally

块中显式关闭资源是必须的。但要注意,在

finally

块中关闭资源时,也要捕获可能发生的

IOException

连接断开的判断与处理:

豆包AI编程 豆包AI编程

豆包推出的AI编程助手

豆包AI编程 483 查看详情 豆包AI编程 陷阱: 客户端或服务器可能会突然断开连接(例如,程序崩溃、网络中断)。如果代码没有正确处理这种情况,可能会导致

read()

方法一直返回-1(表示流结束),或者抛出

SocketException

应对策略:

read()

返回-1:

InputStream.read()

方法返回-1时,表示流已经到达末尾,对方已经关闭了连接。这是正常断开的一种信号,你应该相应地关闭自己的

Socket

和流。捕获

IOException

网络传输过程中,各种I/O异常(如

SocketException

Connection Reset by Peer

)是常态。你的代码应该有健壮的异常处理机制,捕获这些异常,并进行相应的清理和日志记录。例如,在服务器端,当处理某个客户端时捕获到异常,通常意味着这个客户端的连接出了问题,应该关闭这个连接。心跳机制: 对于长时间保持的连接,如果双方长时间没有数据交换,有时很难判断连接是否仍然存活。可以实现一个简单的“心跳”机制,定期发送小数据包来探测对方是否在线。

如何处理TCP通信中的数据序列化与反序列化?

在TCP通信中,数据总是以字节流的形式传输的。这意味着如果你想发送一个Java对象,或者更复杂的数据结构,你需要把它们转换成字节序列,然后在接收端再把字节序列还原成原来的数据。这个过程就是序列化(Serialization)反序列化(Deserialization)

Java内置的对象序列化(

ObjectOutputStream

/

ObjectInputStream

):

原理: Java提供了一套非常方便的机制来序列化Java对象。只要你的类实现了

java.io.Serializable

接口,你就可以使用

ObjectOutputStream

将对象写入到输出流,使用

ObjectInputStream

从输入流中读取对象。

示例(片段):

// 发送端// out是Socket的OutputStreamObjectOutputStream oos = new ObjectOutputStream(out);MyCustomObject objToSend = new MyCustomObject("Hello", 123);oos.writeObject(objToSend);oos.flush();// 接收端// in是Socket的InputStreamObjectInputStream ois = new ObjectInputStream(in);MyCustomObject receivedObj = (MyCustomObject) ois.readObject();System.out.println("收到对象: " + receivedObj.getName() + ", " + receivedObj.getValue());// MyCustomObject类需要实现Serializable接口class MyCustomObject implements Serializable {    private static final long serialVersionUID = 1L; // 推荐定义    private String name;    private int value;    // 构造函数,getter/setter等    public MyCustomObject(String name, int value) {        this.name = name;        this.value = value;    }    public String getName() { return name; }    public int getValue() { return value; }}

优点: 使用简单,对Java对象原生支持。

缺点: 它是Java特有的,如果你需要与其他语言编写的客户端或服务器进行通信,这种方式就行不通了。此外,它对对象结构的修改比较敏感,序列化后的数据通常比较大,且存在一定的安全风险(反序列化攻击)。所以,对于跨平台或对性能、安全性要求高的场景,我通常不会首选它。

基于文本的序列化(JSON / XML):

原理: 将对象转换成JSON字符串或XML字符串进行传输。这些格式都是人类可读的,并且有大量的库支持,方便跨语言使用。

常用库:

JSON: Jackson、Gson(Google)、Fastjson(阿里巴巴)等。XML: JAXB、Dom4j、SAX等。

示例(JSON with Gson):

import com.google.gson.Gson; // 假设你已经引入了Gson库// 发送端// out是Socket的OutputStreamPrintWriter outText = new PrintWriter(out, true);MyCustomObject objToSend = new MyCustomObject("Hello", 123);String jsonString = new Gson().toJson(objToSend);outText.println(jsonString); // 发送JSON字符串// 接收端// in是Socket的InputStreamBufferedReader inText = new BufferedReader(new InputStreamReader(in));String receivedJson = inText.readLine(); // 读取JSON字符串MyCustomObject receivedObj = new Gson().fromJson(receivedJson, MyCustomObject.class);System.out.println("收到对象: " + receivedObj.getName() + ", " + receivedObj.getValue());

优点: 跨语言兼容性好,数据可读性强,生态系统成熟。

缺点: 相比二进制协议,通常数据量会稍大,解析也需要额外开销。

基于二进制的序列化(Protocol Buffers / Apache Avro / Thrift):

原理: 这些是更高级的序列化框架,它们通常需要你定义一个Schema(数据结构),然后根据Schema生成对应语言的代码,进行高效的二进制序列化和反序列化。优点: 性能极高,序列化后的数据量小,跨语言支持良好,并且有严格的版本控制和向后兼容性。缺点: 学习成本和使用复杂性相对较高,需要额外定义Schema文件。

在我自己的项目里,如果只是简单的Java应用内部通信,或者原型阶段,Java自带的序列化有时也用。但如果涉及到服务间通信、微服务架构,或者需要与非Java客户端交互,我几乎总是倾向于使用JSON或Protocol Buffers。JSON简单直观,适合快速开发;Protocol Buffers则在性能和数据大小上有显著优势,适合对这些方面有更高要求的场景。选择哪种方式,最终还是看项目的具体需求和团队的偏好。

以上就是java使用教程怎样实现基于TCP的网络通信 java使用教程的TCP编程入门技巧​的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/230439.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月3日 20:49:57
下一篇 2025年11月3日 20:51:48

相关推荐

  • CSS mask属性无法获取图片:为什么我的图片不见了?

    CSS mask属性无法获取图片 在使用CSS mask属性时,可能会遇到无法获取指定照片的情况。这个问题通常表现为: 网络面板中没有请求图片:尽管CSS代码中指定了图片地址,但网络面板中却找不到图片的请求记录。 问题原因: 此问题的可能原因是浏览器的兼容性问题。某些较旧版本的浏览器可能不支持CSS…

    2025年12月24日
    900
  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 为什么设置 `overflow: hidden` 会导致 `inline-block` 元素错位?

    overflow 导致 inline-block 元素错位解析 当多个 inline-block 元素并列排列时,可能会出现错位显示的问题。这通常是由于其中一个元素设置了 overflow 属性引起的。 问题现象 在不设置 overflow 属性时,元素按预期显示在同一水平线上: 不设置 overf…

    2025年12月24日 好文分享
    400
  • 网页使用本地字体:为什么 CSS 代码中明明指定了“荆南麦圆体”,页面却仍然显示“微软雅黑”?

    网页中使用本地字体 本文将解答如何将本地安装字体应用到网页中,避免使用 src 属性直接引入字体文件。 问题: 想要在网页上使用已安装的“荆南麦圆体”字体,但 css 代码中将其置于第一位的“font-family”属性,页面仍显示“微软雅黑”字体。 立即学习“前端免费学习笔记(深入)”; 答案: …

    2025年12月24日
    000
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么我的特定 DIV 在 Edge 浏览器中无法显示?

    特定 DIV 无法显示:用户代理样式表的困扰 当你在 Edge 浏览器中打开项目中的某个 div 时,却发现它无法正常显示,仔细检查样式后,发现是由用户代理样式表中的 display none 引起的。但你疑问的是,为什么会出现这样的样式表,而且只针对特定的 div? 背后的原因 用户代理样式表是由…

    2025年12月24日
    200
  • inline-block元素错位了,是为什么?

    inline-block元素错位背后的原因 inline-block元素是一种特殊类型的块级元素,它可以与其他元素行内排列。但是,在某些情况下,inline-block元素可能会出现错位显示的问题。 错位的原因 当inline-block元素设置了overflow:hidden属性时,它会影响元素的…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 为什么使用 inline-block 元素时会错位?

    inline-block 元素错位成因剖析 在使用 inline-block 元素时,可能会遇到它们错位显示的问题。如代码 demo 所示,当设置了 overflow 属性时,a 标签就会错位下沉,而未设置时却不会。 问题根源: overflow:hidden 属性影响了 inline-block …

    2025年12月24日
    000
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 为什么我的 CSS 元素放大效果无法正常生效?

    css 设置元素放大效果的疑问解答 原提问者在尝试给元素添加 10em 字体大小和过渡效果后,未能在进入页面时看到放大效果。探究发现,原提问者将 CSS 代码直接写在页面中,导致放大效果无法触发。 解决办法如下: 将 CSS 样式写在一个单独的文件中,并使用 标签引入该样式文件。这个操作与原提问者观…

    2025年12月24日
    000
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 em 和 transition 设置后元素没有放大?

    元素设置 em 和 transition 后不放大 一个 youtube 视频中展示了设置 em 和 transition 的元素在页面加载后会放大,但同样的代码在提问者电脑上没有达到预期效果。 可能原因: 问题在于 css 代码的位置。在视频中,css 被放置在单独的文件中并通过 link 标签引…

    2025年12月24日
    100
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信