网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

简介: 1. 什么是网络编程2. 网络编程中的基本概念1)发送端和接收端2)请求和响应3)客户端和服务端4)常见的客户端服务端模型3. Socket 套接字1)Socket 的分类2)Java 数据报套接字通信模型3)Java 流套接字通信模型4. UDP 数据报套接字编程1)DatagramSocket API2)DatagramPacket API3)示例5. TCP 流套接字编程1)ServerSocket API2)Socket API3)示例a. 短连接版本b. 长连接并发版本

1. 什么是网络编程

网络编程是指网络上的主机,通过不同的进程,以编程的方式实现 网络通信(或称为网络数据传输)


66.png


只要满足不同的进程就可以进行通信,所以即便是在同一个主机,只要不同的进程,基于网络传输数据,也属于网络编程


2. 网络编程中的基本概念

1)发送端和接收端

在一次网络传输中:


发送端: 数据的 发送方进程,称为发送端。发送端主机即网络通信中的源主机


接收端: 数据的 接收方进程,称为接收端。接收端主机即网络通信中的目的主机


收发端: 发送端和接收端两端,简称为收发端


注意: 发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念


67.png


2)请求和响应

一般来说,获取一个网络资源时,涉及到两次网络数据传输

  • 第一次:请求数据的发送
  • 第二次:响应数据的发送

就好比在饭店里点了一碗面

顾客先发起请求:来一碗面

饭店再做出响应:提供一碗面


68.png


3)客户端和服务端

服务端: 在常见的网络传输场景下,把 提供服务 的一方进程,称为服务端,可以对外提供服务

客户端:获取服务 的一方进程,称为客户端

对于服务来说,一般是提供:

  • 客户端获取服务资源

69.png


  • 客户端保存资源到服务端
  • 70.png

4)常见的客户端服务端模型

在常见的场景中,客户端是指给用户使用的程序,服务端是指提供用户服务的程序,具体流程如下:

  1. 客户端发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果

71.png


3. Socket 套接字

Socket 套接字时由系统提供用于网络通信的技术,是基于 TCP/IP 协议的网络通信的基本操作单元。基于 Socket 套接字的网络程序开发就是网络编程


1)Socket 的分类

常用的 Socket 套接字有以下两类:


流套接字: 使用 TCP 协议


传输数据基于 IO 流,流式数据的特征就是在 IO 流没有关闭的情况下,是无边界的数据,可以多次发送数据,也可以分开多次接收


数据报套接字: 使用 UDP 协议


传输数据是一块一块的,每一块数据必须一次性发送,接受也必须一次性接受,不能分开发送或接收


2)Java 数据报套接字通信模型

UDP 协议具有无连接、面向数据报的特征,即每次传输都是没有建立连接,并且一次发送全部数据,一次接收全部数据


Java 中使用 UDP 协议通信时,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或者接受的数据报。


具体流程如下:

72.png


3)Java 流套接字通信模型

TCP 协议具有有连接、面向字节流的特征,即传输数据之前要先建立连接,并且数据的传输是基于 IO 流的

具体流程如下:


73.png


4. UDP 数据报套接字编程

1)DatagramSocket API

DatagramSocket 是 UDP Socket,用于发送和接收 UDP 数据报

构造方法:

1683887483814.png



实例方法:

1683887509558.png



2)DatagramPacket API

DatagramPacket 是 UDP Socket 发送和接收的数据报

构造方法:

1683887529427.png

实例方法:

1683887540077.png


3)示例

使用 UDP Socket 套接字完成一个简单的翻译小程序

客户端向服务端发起翻译请求,服务端接收到请求后,对请求进行处理,再向客户端做出响应

服务端

服务端接收到请求之后,在我们人为规定的 map 中进行查询,再对客户端做出响应


public class Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }
    public static void main(String[] args) throws IOException {
        // 得到 socket 对象,并对其绑定端口号
        Log.println("服务器开启并且绑定了 8080 端口");
        DatagramSocket socket = new DatagramSocket(8080);
        // 循环接受和处理请求
        while (true) {
            // 准备接收数据的容器
            byte[] buf = new byte[1024];
            // 包装数据包
            DatagramPacket received = new DatagramPacket(buf, 0, 1024);
            // 接收请求
            Log.println("服务器准备接受请求");
            socket.receive(received);
            Log.println("服务器接受到了请求");
            // 处理请求
            InetAddress address = received.getAddress();    // 得到发起请求方的 IP
            Log.println("对方的地址:" + address);
            int port = received.getPort();      // 得到发起请求方的端口号
            Log.println("对方的端口:" + port);
            int length = received.getLength();      // 得到请求中真实有效的数据长度
            Log.println("真实的数据长度:" + length);
            byte[] realData = Arrays.copyOf(buf, length);   // 得到请求中真实有效的数据
            // 将请求数据转换成 String
            String request = new String(realData, 0, length, StandardCharsets.UTF_8);
            Log.println("请求中的数据:\r\n" + request);
            // 人为规定请求和相应的格式
            // 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束
            if (!request.startsWith("我是请求:\r\n")) {
                Log.println("请求格式出错");
                // 请求出错
                continue;
            }
            if (!request.endsWith("\r\n")) {
                Log.println("请求格式出错");
                // 请求出错
                continue;
            }
            // 得到请求中的英文单词
            String EnglishWord = request.substring("我是请求:\r\n".length(), request.length() - 2);
            Log.println("请求中的英文单词:" + EnglishWord);
            // 进行翻译
            String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
            Log.println("翻译后的中文:" + ChineseWord);
            // 将翻译后的结果进行包装
            String response = String.format(ChineseWord, "%s\r\n");
            byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
            // 包装数据包
            DatagramPacket send = new DatagramPacket(bytes, 0, bytes.length, address, port);
            // 发起响应
            Log.println("准备发送响应");
            socket.send(send);
            Log.println("相应发送成功");
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        DatagramSocket socket = new DatagramSocket(9999);
        while (sc.hasNextLine()) {
            String str = sc.nextLine();
            String send = "我是请求:\r\n" + str + "\r\n";
            byte[] bytes = send.getBytes();
            DatagramPacket request = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLoopbackAddress(), 8080);
            socket.send(request);
            // 接收响应
            byte[] buf = new byte[1024];
            DatagramPacket datagramPacket = new DatagramPacket(buf, 0, 1024);
            socket.receive(datagramPacket);
            int n = datagramPacket.getLength();
            String response = new String(buf, 0, n, StandardCharsets.UTF_8);
            Log.println(response);
        }
    }
}

74.png


5. TCP 流套接字编程

1)ServerSocket API

用于创建 TCP 服务端 Socket 的 API

构造方法:


方法签名 方法说明
ServerSocket(int port) 创建一个服务端流套接字 Socket,并绑定到指定端口


实例方法:

方法签名 方法说明
Socket accept() 等待客户端发起连接,连接成功后返回一个 Socket 对象
void close() 关闭 Socket


2)Socket API

这里的 Socket 是客户端 Socket,或服务端中接收到客户端连接请求后,accept 方法返回的服务端 Socket

不管是客户端还是服务端 Socket,都是双方建立连接后,保存对端信息,以及用来与对方收发数据的

构造方法:


方法签名 方法说明
Socket(String host, int port) 创建一个客户端流套接字,并与对应 IP 的主机,对应端口的进程建立连接


实例方法:

方法签名 方法说明
InetAddress getInetAddress() 返回套接字所连接的 IP 地址
InputStream getInputStream() 返回套接字的输入流
OutputStream getOutputStream() 返回套接字的输出流


3)示例

使用 TCP Socket 套接字完成一个简单的翻译小程序

完成的效果和上文 UDP 的小程序效果一样

不过在 TCP 这里有长短连接的区分,所以有两个版本的代码,在长连接版本中加入了并发编程,使得同一个服务端可以被多个客户端所连接


a. 短连接版本

客户端:

public class 短连接版本Client {
    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 3; i++) {
            Socket socket = new Socket("127.0.0.1", 8080);      // 拨号
            OutputStream os = socket.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
            PrintWriter printWriter = new PrintWriter(writer);
            printWriter.printf("我是请求\r\n%s\r\n", "苹果");
            printWriter.flush();
            InputStream is = socket.getInputStream();
            Scanner scanner = new Scanner(is, "UTF-8");
            String header = scanner.nextLine();
            String word = scanner.nextLine();
            System.out.println(word);
            socket.close();     // 挂电话
        }
    }
}

服务端:

public class 短连接版本Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        while(true) {
            Log.println("等待客户端建立连接");
            Socket socket = serverSocket.accept();
            Log.println("客户端连接成功");
            InetAddress address = socket.getInetAddress();
            Log.println("客户端 IP 地址:" + address);
            int port = socket.getPort();
            Log.println("客户端端口号:" + port);
            InputStream is = socket.getInputStream();
            Scanner sc = new Scanner(is, "UTF-8");
            String header = sc.nextLine();
            String EnglishWord = sc.nextLine();
            Log.println("请求中的英文单词为:" + EnglishWord);
            String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
            Log.println("翻译后的中文为:" + ChineseWord);
            Log.println("准备响应");
            OutputStream os = socket.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(os);
            PrintWriter printWriter = new PrintWriter(writer);
            printWriter.printf("OK!\r\n%s\t\n", ChineseWord);
            printWriter.flush();
            Log.println("响应成功");
            socket.close();
            Log.println("断开连接");
        }
    }
}

75.png

b. 长连接并发版本

在长连接版本中,必须服务端和客户端同时支持长连接才可以进行通信

如果不清楚长连接和短连接,可以去看看我上篇文章,里面有介绍长连接和短连接,HTTP 和 HTTPS,有介绍 HTTP 的长短连接,实质上就是 TCP 长短连接

客户端:


public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        Socket socket = new Socket("127.0.0.1", 8080);
        OutputStream os = socket.getOutputStream();
        OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
        PrintWriter printWriter = new PrintWriter(writer);
        InputStream is = socket.getInputStream();
        Scanner scanner = new Scanner(is, "UTF-8");
        while (sc.hasNextLine()) {
            String str = sc.nextLine();
            printWriter.printf("我是请求:\r\n%s\r\n", str);
            printWriter.flush();
            // 接收响应
            String header = scanner.nextLine();
            String response = scanner.nextLine();
            Log.println(header + "\r\n" + response);
        }
        socket.close();
    }
}

服务端:

public class Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }
    private final static class ServerTask implements Runnable {
        private final Socket socket;
        private ServerTask(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            InetAddress address = socket.getInetAddress();
            Log.println("客户端 IP 地址为:" + address);
            int port = socket.getPort();
            Log.println("客户端端口号为:" + port);
            try {
                InputStream is = socket.getInputStream();
                // 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束
                Scanner sc = new Scanner(is, "UTF-8");
                // 进行响应
                OutputStream os = socket.getOutputStream();
                OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
                PrintWriter printWriter = new PrintWriter(writer);
                while (sc.hasNextLine()) {
                    String header = sc.nextLine();
                    String EnglishWord = sc.nextLine();
                    Log.println("请求中的词为:" + EnglishWord);
                    // 对请求进行处理
                    String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
                    Log.println("翻译后的词:" + ChineseWord);
                    Log.println("准备发起响应");
                    printWriter.printf("OK!\r\n%s\r\n", ChineseWord);
                    printWriter.flush();
                    Log.println("响应成功");
                }
                socket.close();
                Log.println("连接已关闭");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws IOException {
        // 使用多线程完成多用户同时在线的功能
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        ServerSocket serverSocket = new ServerSocket(8080);
        Log.println("绑定端口到 8080");
        while (true) {
            Log.println("等待客户端连接");
            Socket socket = serverSocket.accept();
            Log.println("客户端连接成功");
            ServerTask serverTask = new ServerTask(socket);
            threadPool.execute(serverTask);
        }
    }
}

76.png


目录
相关文章
|
22天前
|
监控 安全 网络安全
云计算与网络安全:保护数据的关键策略
【9月更文挑战第34天】在数字化时代,云计算已成为企业和个人存储、处理数据的优选方式。然而,随着云服务的普及,网络安全问题也日益凸显。本文将探讨云计算环境中的网络安全挑战,并提供一系列策略来加强信息安全。从基础的数据加密到复杂的访问控制机制,我们将一探究竟如何在享受云服务便利的同时,确保数据的安全性和隐私性不被侵犯。
49 10
|
27天前
|
存储 安全 网络安全
云计算与网络安全:守护数据,构筑未来
在当今的信息化时代,云计算已成为推动技术革新的重要力量。然而,随之而来的网络安全问题也日益凸显。本文从云服务、网络安全和信息安全等技术领域展开,探讨了云计算在为生活带来便捷的同时,如何通过技术创新和策略实施来确保网络环境的安全性和数据的保密性。
|
20天前
|
SQL 安全 测试技术
网络安全与信息安全:保护数据的艺术
【9月更文挑战第36天】在数字化时代,网络安全和信息安全已成为维护个人隐私和企业资产的基石。本文深入探讨了网络安全漏洞、加密技术以及安全意识的重要性,旨在为读者提供一份知识宝典,帮助他们在网络世界中航行而不触礁。我们将从网络安全的基本概念出发,逐步深入到复杂的加密算法,最后强调培养安全意识的必要性。无论你是IT专业人士还是日常互联网用户,这篇文章都将为你打开一扇了解和实践网络安全的大门。
29 2
|
1月前
|
小程序 开发者
微信小程序之网络数据请求 wx:request的简单使用
这篇文章介绍了微信小程序中如何使用wx.request进行网络数据请求,包括请求的配置、请求的格式以及如何在开发阶段关闭请求的合法检验。
微信小程序之网络数据请求 wx:request的简单使用
|
1月前
|
缓存 网络协议 网络架构
网络抓包分析【IP,ICMP,ARP】以及 IP数据报,MAC帧,ICMP报和ARP报的数据报格式
本文详细介绍了如何使用网络抓包工具Wireshark进行网络抓包分析,包括以太网v2 MAC帧、IP数据报、ICMP报文和ARP报文的格式,以及不同网络通信的过程。文章通过抓包分析展示了IP数据报、ICMP数据报和ARP数据报的具体信息,包括MAC地址、IP地址、ICMP类型和代码、以及ARP的硬件类型、协议类型、操作类型等。通过这些分析,可以更好地理解网络协议的工作机制和数据传输过程。
网络抓包分析【IP,ICMP,ARP】以及 IP数据报,MAC帧,ICMP报和ARP报的数据报格式
|
18天前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
17 0
|
3月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
42 4
|
3月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
58 2
|
3月前
|
网络协议 程序员 视频直播
|
3月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
28 3