从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java 数据类型

    • Java 多版本配置
    • 面向对象

    • Java 核心类

    • IO

    • Java 与时间

    • 异常处理

    • 哈希和加密算法

    • Java8 新特性

    • 网络编程

      • TCP 编程
        • Socket
        • 服务器端
        • 客户端
        • 演示
        • Socket 流
        • 小结
      • UDP 编程
      • 发送 Email
      • 接受邮件
      • HTTP 编程
      • RMI 远程调用
  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 网络编程
2023-04-17
目录

TCP 编程

# TCP 编程

本文简单说下 TCP 编程

# Socket

在开发网络应用程序的时候,我们又会遇到 Socket 这个概念。Socket 是一个抽象概念,一个应用程序通过一个 Socket 来建立一个远程连接,而 Socket 内部通过 TCP/IP 协议把数据传输到网络:

┌───────────┐                                   ┌───────────┐
│Application│                                   │Application│
├───────────┤                                   ├───────────┤
│  Socket   │                                   │  Socket   │
├───────────┤                                   ├───────────┤
│    TCP    │                                   │    TCP    │
├───────────┤      ┌──────┐       ┌──────┐      ├───────────┤
│    IP     │←────→│Router│←─────→│Router│←────→│    IP     │
└───────────┘      └──────┘       └──────┘      └───────────┘
1
2
3
4
5
6
7
8
9

Socket、TCP 和部分 IP 的功能都是由操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单的封装。例如,Java 提供的几个 Socket 相关的类就封装了操作系统提供的接口。

为什么需要 Socket 进行网络通信?因为仅仅通过 IP 地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器、QQ、邮件客户端等。当操作系统接收到一个数据包的时候,如果只有 IP 地址,它没法判断应该发给哪个应用程序,所以,操作系统抽象出 Socket 接口,每个应用程序需要各自对应到不同的 Socket,数据包才能根据 Socket 正确地发到对应的应用程序。

一个 Socket 就是由 IP 地址和端口号(范围是 0~65535)组成,可以把 Socket 简单理解为 IP 地址加端口号。端口号总是由操作系统分配,它是一个 0~65535 之间的数字,其中,小于 1024 的端口属于特权端口,需要管理员权限,大于 1024 的端口可以由任意用户的应用程序打开,例如:

  • 101.202.99.2:1201
  • 101.202.99.2:1304
  • 101.202.99.2:15000

使用 Socket 进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的 IP 地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个 TCP 连接,双方后续就可以随时发送和接收数据。

因此,当 Socket 连接成功地在服务器端和客户端之间建立后:

  • 对服务器端来说,它的 Socket 是指定的 IP 地址和指定的端口号;
  • 对客户端来说,它的 Socket 是它所在计算机的 IP 地址和一个由操作系统分配的随机端口号。

# 服务器端

要使用 Socket 编程,我们首先要编写服务器端程序。Java 标准库提供了 ServerSocket 来实现对指定 IP 和指定端口的监听。ServerSocket 的典型实现代码如下:

package chapter20;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class TCPDemo1Server {
    public static void main(String[] args) throws Exception{
        ServerSocket ss = new ServerSocket(7777);
        System.out.println("server is running");
        while (true){
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        }
    }
}


class Handler extends Thread{
    Socket sock;

    public Handler(Socket sock){
        this.sock = sock;
    }

    @Override
    public void run() {
        try(InputStream input = this.sock.getInputStream();
            OutputStream output = this.sock.getOutputStream()){
            handle(input, output);
        }catch (Exception e){
            System.out.println("Client disconnected: ");
            e.printStackTrace();
            try{
                this.sock.close();
            }catch (Exception e2){
                System.out.println("sock close error: ");
                e2.printStackTrace();
            }
        }
    }

    private void handle(InputStream input, OutputStream output) throws IOException{
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        writer.write("hello!\n");
        writer.flush();
        while (true){
            String s = reader.readLine();
            if(s.equals("bye, server")){
                writer.write("bye, client!");
                writer.flush();
                break;
            }

            writer.write("server successfully receive message \" " + s + " \" from client. \n");
            writer.flush();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

我们解读下这段代码。首先,服务器端通过代码:

ServerSocket ss = new ServerSocket(7777);
1

在指定端口 7777 监听。这里我们没有指定 IP 地址,表示在计算机的所有网络接口上进行监听。

如果 ServerSocket 监听成功,我们就使用一个无限循环来处理客户端的连接:

 while (true){
    Socket sock = ss.accept();
    System.out.println("connected from " + sock.getRemoteSocketAddress());
    Thread t = new Handler(sock);
    t.start();
}
1
2
3
4
5
6

注意到代码 ss.accept() 表示每当有新的客户端连接进来后,就返回一个 Socket 实例,这个 Socket 实例就是用来和刚连接的客户端进行通信的。由于客户端很多,要实现并发处理,我们就必须为每个新的 Socket 创建一个新线程来处理,这样,主线程的作用就是接收新的连接,每当收到新连接后,就创建一个新线程进行处理。

我们在多线程编程的章节中介绍过线程池,这里也完全可以利用线程池来处理客户端连接,能大大提高运行效率。

如果没有客户端连接进来,accept() 方法会阻塞并一直等待。如果有多个客户端同时连接进来,ServerSocket 会把连接扔到队列里,然后一个一个处理。对于 Java 程序而言,只需要通过循环不断调用 accept() 就可以获取新的连接。

然后 handle 函数就可以通过 IO 来读取(reader)客户端发送的数据,并通过 IO 返回数据给客户端(writer)。如果客户端发送了字符串 bye, server,则认为客户端要停止连接,跳出循环不再接受数据。

# 客户端

相比服务器端,客户端程序就要简单很多。一个典型的客户端程序如下:

package chapter20;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class TCPDemo2Client {
    public static void main(String[] args) throws IOException{
        Socket sock = new Socket("localhost", 7777);
        try(InputStream input = sock.getInputStream();
            OutputStream output = sock.getOutputStream()){
            handle(input, output);
        }
        sock.close();
        System.out.println("Disconnected from server.");
    }

    private static void handle(InputStream input, OutputStream output) throws IOException{
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        Scanner scanner = new Scanner(System.in);
        System.out.println("[server] " + reader.readLine());
        while(true) {
            System.out.print(">>> ");
            String s= scanner.nextLine();
            writer.write(s);
            writer.newLine();
            writer.flush();
            String resp = reader.readLine();
            System.out.println("<<< " + resp);
            if(resp.equals("bye, client!")){
                break;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

客户端程序通过:

Socket sock = new Socket("localhost", 7777);
1

连接到服务器端,注意上述代码的服务器地址是 "localhost",表示本机地址,端口号是 7777。如果连接成功,将返回一个 Socket 实例,用于后续通信。

# 演示

我们运行 TCPDemo1Server,然后再运行 TCPDemo2Client,测试发送一些字符串给服务器:

可以看到服务器正常接受了数据,并返回了接受到的数据是什么。

# Socket 流

当 Socket 连接创建成功后,无论是服务器端,还是客户端,我们都使用 Socket 实例进行网络通信。因为 TCP 是一种基于流的协议,因此,Java 标准库使用 InputStream 和 OutputStream 来封装 Socket 的数据流,这样我们使用 Socket 的流,和普通 IO 流类似:

// 用于读取网络数据:
InputStream in = sock.getInputStream();
// 用于写入网络数据:
OutputStream out = sock.getOutputStream();
1
2
3
4

最后我们重点来看看,为什么写入网络数据时,要调用 flush() 方法。

如果不调用 flush(),我们很可能会发现,客户端和服务器都收不到数据,这并不是 Java 标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用 flush() 强制把缓冲区数据发送出去。

# 小结

使用 Java 进行 TCP 编程时,需要使用 Socket 模型:

  • 服务器端用 ServerSocket 监听指定端口;
  • 客户端使用 Socket(InetAddress, port) 连接服务器;
  • 服务器端用 accept() 接收连接并返回 Socket;
  • 双方通过 Socket 打开 InputStream/OutputStream 读写数据;
  • 服务器端通常使用多线程同时处理多个客户端连接,利用线程池可大幅提升效率;
  • flush() 用于强制输出缓冲区到网络。
上次更新: 2025/6/3 09:31:54
Optional
UDP 编程

← Optional UDP 编程→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式