如何学习 Java 中的 Socket 编程,进行网络通信

news2024/11/19 1:32:29

Socket编程是网络编程的核心技术之一,它使得不同主机之间可以进行数据通信。Java提供了丰富的网络编程API,使得编写网络应用程序变得相对简单和直观。本文将详细讲解如何学习Java中的Socket编程,并通过示例代码展示如何实现网络通信。

一、Socket编程基础

1.1 什么是Socket?

Socket是一种网络通信的端点,包含IP地址和端口号。在网络通信中,一个Socket实例代表一个网络连接的一个端点,通过两个Socket实例(客户端和服务器)可以实现网络通信。

1.2 TCP和UDP

Socket编程主要分为两种类型:基于TCP(Transmission Control Protocol)的Socket编程和基于UDP(User Datagram Protocol)的Socket编程。

  • TCP:面向连接的协议,提供可靠的、顺序正确的、无差错的数据传输。常用于需要高可靠性的场景。
  • UDP:无连接的协议,提供不保证可靠性的数据传输。适用于对传输速度要求较高,但对可靠性要求较低的场景。

二、Java中的Socket类

2.1 Java Socket API

Java提供了以下主要的Socket编程类:

  • java.net.Socket:实现客户端Socket,负责与服务器建立连接并进行通信。
  • java.net.ServerSocket:实现服务器Socket,负责监听客户端的连接请求。
  • java.net.DatagramSocket:实现UDP协议的Socket,用于发送和接收数据报文。
  • java.net.DatagramPacket:表示UDP的数据报文。

三、基于TCP的Socket编程

3.1 客户端Socket编程

客户端通过java.net.Socket类实现。步骤如下:

  1. 创建Socket对象,并连接到服务器。
  2. 获取输入输出流,用于发送和接收数据。
  3. 关闭Socket连接。

示例代码:

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

public class TCPClient {
    public static void main(String[] args) {
        String serverName = "localhost"; // 服务器地址
        int port = 8080; // 服务器端口

        try {
            // 连接到服务器
            Socket client = new Socket(serverName, port);

            // 获取输出流,发送数据到服务器
            OutputStream outToServer = client.getOutputStream();
            DataOutputStream out = new DataOutputStream(outToServer);
            out.writeUTF("Hello from " + client.getLocalSocketAddress());

            // 获取输入流,接收服务器返回的数据
            InputStream inFromServer = client.getInputStream();
            DataInputStream in = new DataInputStream(inFromServer);
            System.out.println("Server says " + in.readUTF());

            // 关闭连接
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.2 服务器Socket编程

服务器通过java.net.ServerSocket类实现。步骤如下:

  1. 创建ServerSocket对象,并绑定到指定端口。
  2. 调用accept()方法,等待客户端连接。
  3. 获取输入输出流,用于接收和发送数据。
  4. 关闭Socket连接。

示例代码:

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

public class TCPServer {
    public static void main(String[] args) {
        int port = 8080; // 监听端口

        try {
            // 创建服务器Socket
            ServerSocket serverSocket = new ServerSocket(port);

            while (true) {
                // 等待客户端连接
                Socket server = serverSocket.accept();

                // 获取输入流,接收客户端发送的数据
                DataInputStream in = new DataInputStream(server.getInputStream());
                System.out.println("Client says " + in.readUTF());

                // 获取输出流,发送数据到客户端
                DataOutputStream out = new DataOutputStream(server.getOutputStream());
                out.writeUTF("Thank you for connecting to " + server.getLocalSocketAddress());

                // 关闭连接
                server.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、基于UDP的Socket编程

4.1 客户端Socket编程

客户端通过java.net.DatagramSocketjava.net.DatagramPacket类实现。步骤如下:

  1. 创建DatagramSocket对象。
  2. 创建DatagramPacket对象,封装要发送的数据。
  3. 调用send()方法发送数据。
  4. 调用receive()方法接收数据。
  5. 关闭DatagramSocket。

示例代码:

import java.net.*;

public class UDPClient {
    public static void main(String[] args) {
        String serverName = "localhost"; // 服务器地址
        int port = 8080; // 服务器端口

        try {
            // 创建客户端Socket
            DatagramSocket clientSocket = new DatagramSocket();

            // 发送数据
            byte[] sendData = "Hello from client".getBytes();
            InetAddress IPAddress = InetAddress.getByName(serverName);
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
            clientSocket.send(sendPacket);

            // 接收数据
            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            clientSocket.receive(receivePacket);
            String modifiedSentence = new String(receivePacket.getData());
            System.out.println("FROM SERVER: " + modifiedSentence);

            // 关闭连接
            clientSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
4.2 服务器Socket编程

服务器通过java.net.DatagramSocketjava.net.DatagramPacket类实现。步骤如下:

  1. 创建DatagramSocket对象,并绑定到指定端口。
  2. 创建DatagramPacket对象,用于接收数据。
  3. 调用receive()方法接收数据。
  4. 创建DatagramPacket对象,封装要发送的数据。
  5. 调用send()方法发送数据。
  6. 关闭DatagramSocket。

示例代码:

import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        int port = 8080; // 监听端口

        try {
            // 创建服务器Socket
            DatagramSocket serverSocket = new DatagramSocket(port);

            while (true) {
                // 接收数据
                byte[] receiveData = new byte[1024];
                DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                serverSocket.receive(receivePacket);
                String sentence = new String(receivePacket.getData());
                System.out.println("RECEIVED: " + sentence);

                // 发送数据
                InetAddress IPAddress = receivePacket.getAddress();
                int clientPort = receivePacket.getPort();
                String capitalizedSentence = sentence.toUpperCase();
                byte[] sendData = capitalizedSentence.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, clientPort);
                serverSocket.send(sendPacket);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

五、Java Socket编程中的常见问题

5.1 端口冲突

端口冲突是指多个程序尝试绑定到同一个端口,这会导致端口不可用。解决方法是确保每个程序使用不同的端口,或者检查端口是否被占用。

5.2 数据传输中的阻塞

Socket通信是阻塞式的,意味着读写操作会阻塞线程直到数据被完全传输。可以使用多线程或者非阻塞I/O(NIO)来解决这个问题。

5.3 数据粘包与拆包

在TCP协议中,由于数据是以流的形式发送,接收方可能会一次接收到多个数据包(粘包)或一个数据包被分成多次接收(拆包)。解决方法是在数据包中添加长度字段,或者使用定界符来标识每个数据包的边界。

六、高级话题:多线程和NIO

6.1 多线程Socket编程

在实际应用中,服务器通常需要同时处理多个客户端的连接。可以使用多线程来实现,每个客户端连接由一个线程处理。

示例代码:

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

public class MultiThreadedTCPServer {
    public static void main(String[] args) {
        int port = 8080; // 监听端口

        try {
            // 创建服务器Socket
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("Server started on port " + port);

            while (true) {
                // 等待客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress());

                // 创建一个新的线程处理客户端请求
                new ClientHandler(clientSocket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler extends Thread {
    private Socket clientSocket;

    public ClientHandler(Socket socket) {
        this.clientSocket = socket;
    }

    public void run() {
        try {
            // 获取输入流,接收客户端发送的数据
            DataInputStream in = new DataInputStream(clientSocket.getInputStream());
            System.out.println("Client says: " + in.readUTF());

            // 获取输出流,发送数据到客户端
            DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
            out.writeUTF("Thank you for connecting!");

            // 关闭连接
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
6.2 非阻塞I/O(NIO)

Java NIO(New I/O)提供了非阻塞的网络通信方式,可以提高服务器的性能和扩展性。NIO中的核心组件包括Channel、Buffer和Selector。

示例代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioTCPServer {
    public static void main(String[] args) {
        int port = 8080; // 监听端口

        try {
            // 创建选择器
            Selector selector = Selector.open();

            // 打开服务器Socket通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server started on port " + port);

            while (true) {
                // 等待事件
                selector.select();

                // 获取发生事件的SelectionKey
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        // 接受连接
                        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = serverChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        // 读取数据
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(256);
                        socketChannel.read(buffer);
                        String message = new String(buffer.array()).trim();
                        System.out.println("Client says: " + message);

                        // 响应客户端
                        buffer.flip();
                        socketChannel.write(buffer);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

黑马程序员免费预约咨询

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1838627.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

特征标注——OpenCV

特征标注 导入必要的库创建窗口显示原始图片和标注后的图片存储用户选择的图片路径字体样式和大小定义了select_image函数定义了annotate_landmarks()函数设置按钮调整图片标签的位置设置图片位置主事件循环运行显示&#xff1a;全部代码 导入必要的库 import tkinter as tk: 导…

细说MCU输出互补型PWM波形时设置死区时间的作用

目录 一、工程背景 二、死区时间的作用 一、工程背景 在作者的文章里建立工程时&#xff0c;为配置输出互补型PWM波形曾经设置了死区时间&#xff0c;DEAD100个定时器的时间周期&#xff08;简称实例1&#xff09;&#xff1a;细说MCU输出互补型PWM波形的实现方法-CSDN博客 …

【Python教程】如何搭建一个高效的Python开发环境?结尾附安装包直通车

前言&#xff1a; Python 丰富的函数库和组件库是这门语言强大的核心原因&#xff01;但我们不可能去记忆所有的方法名和参数名&#xff0c;往往只能记住一些常用的或者某个方法开头的几个字母。这个时候一个好的开发工具就需要能聪明地“猜”出你想输入的代码&#xff0c;并给…

数据结构基础(基于c++)

数据结构基础&#xff08;基于c&#xff09; 文章目录 数据结构基础&#xff08;基于c&#xff09;前言1. 递归、迭代、时间复杂度、空间复杂度2. 数据结构 数组与链表1. 数组2. 链表3. 动态数组4. 数组与链表对比 前言 参考资料&#xff1a;Hello 算法 (hello-algo.com) 1. 递…

20240619每日小程序-------朋友想开发微信小程序,那就搞一把demo

下载开发工具 hbuildX 微信开发者工具 随便搞个开源项目 会员小程序 下载后导入到hbuildX 安装依赖 npm i 安装hbuildX插件 工具—》插件安装 推荐安装&#xff1a; 微信小程序一键打包插件sass编译 启动 选择5.用微信开发者工具启动 报错不要怕 比如&#xff1a…

【车载开发系列】IIC总线协议时序图

【车载开发系列】IIC总线协议时序图 【车载开发系列】IIC总线协议时序图 【车载开发系列】IIC总线协议时序图一、前言二、IIC硬件软件实现1&#xff09;使用I2C控制器实现2&#xff09;使用GPIO通过软件模拟实现 三、I2C协议标准代码1&#xff09;起始信号2&#xff09;停止信号…

判断对称二叉树/判断相同的数-二叉树

都利用递归&#xff0c;思路相似&#xff1b; 对称二叉树就是两个相同的二叉树&#xff0c;但是子节点是right left因为对称&#xff1b; 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isSymmetric(TreeNode* root) {TreeNode* rt…

【C语言】解决C语言报错:Null Pointer Dereference

文章目录 简介什么是Null Pointer DereferenceNull Pointer Dereference的常见原因如何检测和调试Null Pointer Dereference解决Null Pointer Dereference的最佳实践详细实例解析示例1&#xff1a;未初始化的指针示例2&#xff1a;释放内存后未将指针置为NULL示例3&#xff1a;…

市值飙升!超微软、苹果,英伟达成为全球市值最高上市公司

KlipC报道&#xff1a;当地时间6月18日&#xff0c;英伟达股价再度大涨&#xff0c;盘后股价上涨3.51%&#xff0c;总市值达3.335万亿美元&#xff0c;报135.58美元再刷历史新高&#xff0c;超微软、苹果成为全球市值最高的上市公司。 值得一提的是&#xff0c;在本月初&#x…

hackbar插件安装教程

目录 HackBar 插件简介 下载 Firefox浏览器&#xff08;火狐&#xff09;安装 2.1.3版本 2.5.3版本 使用 chrome浏览器&#xff08;谷歌&#xff09;安装 方法1&#xff1a;开发者模式拖安装包 激活方式&#xff1a; 方法2&#xff1a;从 Chrome 应用商店 HackBar 插件…

【推荐100个unity插件之22】基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.0插件的使用

效果 文章目录 效果前言特性截图基础介绍插件信息5分钟上手 XCharts 3.0实例创建一个默认的折线图代码修改显示的值 推荐完结 前言 unity怎么绘制图表&#xff1f;这是最近最常听到的问题。这次就介绍一款基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.…

LuxTrust、契约锁联合启动中欧两地跨境电子签服务

6月18日&#xff0c;欧洲领先的数字身份和电子签名厂商-LuxTrust、全球领先的数字化技术和服务的提供商-浩鲸科技一行莅临契约锁上海总部&#xff0c;并于当日下午联合举行“跨境签战略合作”现场签约仪式。 三方将以此次合作为契机&#xff0c;发挥各自领域专业优势&#xff…

在同一个 Blazor 应用中结合 SQL-DB 和 MongoDB

介绍 传统上&#xff0c;在单应用程序中&#xff0c;我们对整个应用程序使用单个数据库服务器。但是&#xff0c;我将 SQL 数据库和 MongoDB 结合在同一个应用程序中。此应用程序将是 RDBMS 和 No SQL 数据库的组合。我们将从头开始创建一个 Blazor 应用程序&#xff0c;并使用…

微信小程序开发模式--第三方代开发

研发小程序功能&#xff0c;还是必须要学习了一下小程序开发文档的谋篇布局&#xff0c;这样能快速定位且解决自己业务问题。 如何布局&#xff1f; 1、指南微信开放文档 2、框架微信开放文档 3、组件视图容器 | 微信开放文档 4、API基础 | 微信开放文档 5、平台能力平台…

LeetCode - 415 字符串相加(Java JS Python C C++)

题目来源 415. 字符串相加 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定两个字符串形式的非负整数 num1 和 num2 &#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c;…

刷代码随想录有感(108):动态规划——目标和

题干&#xff1a; 代码&#xff1a; class Solution { public:int findTargetSumWays(vector<int>& nums, int target) {int sum 0;for(int i : nums) sum i;if(abs(target) > sum)return 0;if((sum target) % 2 ! 0)return 0;int bagweight (sum target) /…

高考志愿填报选专业,你是听父母还是自己选?

在就业环境如此激烈的今天&#xff0c;就读好的专业意味着在竞争中占据一定的优势&#xff0c;而在拿到高考分数后&#xff0c;如何进行专业选择让不少人伤透了脑筋。很多学生愿意遵从自身意愿&#xff0c;就读自己喜欢的专业&#xff0c;但是当他提出想要就读哪个专业的想法后…

Python爬虫小白入门(二)BeautifulSoup库

一、前言 上一篇演示了如何使用requests模块向网站发送http请求&#xff0c;获取到网页的HTML数据。这篇来演示如何使用BeautifulSoup模块来从HTML文本中提取我们想要的数据。 二、运行环境 我的运行环境如下&#xff1a; 系统版本 Windows10。 Python版本 Python3.5&#xf…

为什么人们对即将推出的 Go 1.23 迭代器感到愤怒

原文&#xff1a;gingerBill - 2024.06.17 TL;DR 它让 Go 变得太“函数式”&#xff0c;而不再是不折不扣的命令式语言。 最近&#xff0c;我在 Twitter 上看到一篇帖子&#xff0c;展示了 Go 1.23&#xff08;2024 年 8 月&#xff09;即将推出的 Go 迭代器设计。据我所知&a…

「动态规划」如何求乘积为正数的最长子数组长度?

1567. 乘积为正数的最长子数组长度https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/description/ 给你一个整数数组nums&#xff0c;请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。…