简单实现基于UDP与TCP的回显服务器(一)

简介: 简单实现基于UDP与TCP的回显服务器

前言


我们写网络程序, 主要编写的是应用层代码.

真正要发送这个数据, 还需要上层协议调用下层协议, 也就是应用层调用传输层.

传输层给应用层提供了一组 api, 统称为 socket api, 系统给程序提供的 api 是C 风格的, JDK 针对这些 api 进行封装, 封装成 Java 风格的 api.


提供的 socket api 主要是这两组 :


  1. 基于 UDP 的 api
  2. 基于 TCP 的 api


因为 UDP 与 TCP 的协议差别很大, 所以这两组 api 差别也很大.


那这两个协议都有啥特点呢?


UDP :


无连接 (使用 UDP 的双方不需要刻意保存对端的相关信息)

不可靠传输 (消息发送完了就行, 不关注结果)

面向数据报 (以一个 UDP 数据报为基本单位)

全双工 (一条路径, 双向通信)


TCP :


有连接 (使用 TCP 的双方要刻意保存对端的相关信息)

可靠传输 (发送消息后, 知道对方是否接收到)

面向字节流 (以字节为传输的基本单位, 读写方式非常灵活)

全双工 (一条路径, 双向通信)


UDP 版的回显服务器


需要用到的 api


1.DatagramSocket API


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

主要用到的构造方法 :


DatagramSocket()


创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)


DatagramSocket(intport)


创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)


主要用到的方法 :


void receive(DatagramPacket p)


从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)


void send(DatagramPacketp)


从此套接字发送数据报包(不会阻塞等待,直接发送)


void close()


关闭此数据报套接字


2.DatagramPacket API


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

主要用到的构造方法 :


DatagramPacket(byte[] buf, int length)


构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数 length)


DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)


构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从offset到指定长度(第三个参数length)。address指定目的主机的IP和端口号


主要用到的方法 :


InetAddress getAddress()


从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址.


int getPort()


从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获

取接收端主机端口号


byte[] getData()


获取数据报中的数据


构造UDP发送数据报时,需要传入 SocketAddress ,该对象可以使用InetSocketAddress 来创建.


服务端


public class UdpEchoServer {
    //首先定义一个 socket 对象, 通过 socket 对象来发送读取信息
    private DatagramSocket socket = null;
    //绑定一个端口, 如果绑定的端口被别的进程占用了, 这里就会报错.
    //同一个主机上, 一个端口只能被一个进程绑定.
    public UdpEchoServer(int port) throws SocketException {
        //构造时, 指定要绑定的端口号.
        socket = new DatagramSocket(port);
    }
    //启动服务器的主逻辑
    public void start() throws IOException {
        System.out.println("服务器启动!!!");
        //因为服务器是要时刻读取客户端信息的, 所以使用while循环来重复读取并处理信息.
        while(true) {
            //每次循环都只做三件事
            // 1.读取请求并解析
            //   下面这是构造一个数据包, 就可以理解为一个餐盘, 这个餐盘里指定要放入的数据类型及大小
            //   空的餐盘构建好了就得装东西了
            DatagramPacket requestPacket = new DatagramPacket(new byte[666], 666); 
            //   1kb=1024byte, UDP最多发送64kb(包含UDP首部8byte)
            //   通过 socket 对象来接收信息(也就是填充餐盘)
            socket.receive(requestPacket); //如果没接收到信息就会阻塞等待
            //   为了方便我们处理这个请求, 将数据包转为 String
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            // 2.根据请求来计算响应(这里直接返回了)
            String response = process(request);
            // 3.把响应结果写回客户端
            //   根据 response 来构造一个 DatagramPacket
            //   和之前构造不同, 本次构造相当于将餐盘填满, 注意还要指定发送给谁
            //   requestPacket.getSocketAddress()是获取客户端的IP和端口号(填充的 requestPacket 就包含了客户端的IP和地址)
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    0,response.getBytes().length,requestPacket.getSocketAddress());
            //   把结果发送给客户端
            socket.send(responsePacket);
            //打印一下客户端的IP和端口号, 还有请求和响应
            System.out.printf("[%s : %d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }
    //根据请求计算响应
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer echoServer = new UdpEchoServer(8888);
        echoServer.start();
    }
}


客户端


public class UdpEchoClient {
    //客户端也需要 socket 对象来进行数据交互
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;
    //构造客户端时, 还需要知道服务器在哪, 才能去获取服务
    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        //不同与服务器, 这里没有关联端口, 不代表不需要关联端口
        //而是系统会自动为客户端分配个空闲的端口
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }
    public void start() throws IOException {
        System.out.println("启动客户端!!!");
        //通过 Scanner 来读取用户输入
        Scanner scanner = new Scanner(System.in);
        while(true) {
            // 1.先从控制台读取一个字符串
            //   打印一个提示符, 提示用户输入
            System.out.print("-> ");
            String request = scanner.next();
            // 2.把字符串构造成 UDP packet, 并进行发送
            //   InetAddress.getByName() 确定主机的IP地址
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIP), serverPort);
            socket.send(requestPacket);
            // 3.客户端尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[666],666);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.printf("req: %s, resp: %s\n", request,response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",8888);
        udpEchoClient.start();
    }
}



启动看看效果 :


客户端 :


1edf9156d60c4cbbbd14812a96984026.png

服务端 :

b71395f2f12c442abc3c6949043bfce7.png


UDP 版本的字典客户端和字典服务器


要想实现单词查找功能, 首先要有一个数据结构来对单词进行存储, 一般用哈希表来存储, key 来存储单词, value 存储单词意思.


其实客户端都是一样的, 只是服务器根据请求, 返回的响应不一样了.

所以我们主要就是重写 process 方法.


服务端 :


public class UdpDictServer extends UdpEchoServer{
    private HashMap<String,String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);
        dict.put("pen","笔");
        dict.put("dog","狗");
        dict.put("cat","猫");
        //...可以无限添加, 其实网上的翻译词典什么的,就是样本比这个大
    }
    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"未找到该单词.");
    }
    public static void main(String[] args) throws IOException {
        UdpDictServer udpDictServer = new UdpDictServer(8888);
        udpDictServer.start();
    }
}

客户端 :

d3a6adc5b7534d76ab53abcc523d47e5.png


服务端 :


8f66d64a346a45e2abb92e1c21462b1c.png

相关文章
|
3天前
|
网络安全 数据中心 UED
封UDP封海外的高防云服务器如何选择? - 蓝易云
以上就是选择封UDP封海外的高防云服务器的一些要点,希望对你有所帮助。
14 4
|
6天前
|
网络协议 Dubbo Java
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
11 0
|
6天前
|
Python
Python网络编程基础(Socket编程)UDP服务器编程
【4月更文挑战第8天】Python UDP服务器编程使用socket库创建UDP套接字,绑定到特定地址(如localhost:8000),通过`recvfrom`接收客户端数据报,显示数据长度、地址和内容。无连接的UDP协议使得服务器无法主动发送数据,通常需应用层实现请求-响应机制。当完成时,用`close`关闭套接字。
|
6天前
|
网络协议 Python
python中TCP回声服务器与客户端示例
【4月更文挑战第7天】本示例展示了TCP回声服务器和客户端的工作流程。服务器监听特定端口,接收客户端连接请求,接收数据并回显。客户端连接服务器,发送数据并接收回显。代码示例用Python实现,包括服务器的`socket.bind()`, `socket.listen()`, `socket.accept()`和客户端的`socket.connect()`, `socket.sendall()`, `socket.recv()`。运行示例时,先启动服务器再启动客户端,可观察TCP连接和数据传输过程。了解这些基础对于构建网络应用至关重要。
|
6天前
|
网络协议 网络架构
tcp端口转发服务器--forwardSvr
tcp端口转发服务器--forwardSvr
19 1
|
6天前
|
JSON 网络协议 开发工具
基于Qt实现的TCP端口数据转发服务器
基于Qt实现的TCP端口数据转发服务器
21 0
基于Qt实现的TCP端口数据转发服务器
|
18小时前
|
弹性计算 关系型数据库 MySQL
【阿里云弹性计算】从零搭建:基于阿里云ECS的高性能Web服务部署实践
【5月更文挑战第21天】本文介绍了如何使用阿里云ECS搭建高性能Web服务。首先,注册阿里云账号购买ECS实例,选择合适配置。接着,通过SSH连接实例,更新系统并安装Apache、PHP和MySQL。创建网站目录,上传代码,配置数据库和PHP。然后,启用Gzip压缩和KeepAlive,调整Apache并发连接数以优化性能。此教程为在阿里云上构建高效Web服务提供了基础指南。
19 5
|
1天前
|
弹性计算
阿里云ECS的使用心得
本文主要讲述了我是如何了解到ECS,使用ECS的一些经验,以及自己的感悟心得
|
1天前
|
存储 弹性计算 监控
【阿里云弹性计算】深入阿里云ECS配置选择:CPU、内存与存储的最优搭配策略
【5月更文挑战第20天】阿里云ECS提供多种实例类型满足不同需求,如通用型、计算型、内存型等。选择CPU时,通用应用可选1-2核,计算密集型应用推荐4核以上。内存选择要考虑应用类型,内存密集型至少4GB起。存储方面,系统盘和数据盘容量依据应用和数据量决定,高性能应用可选SSD或高效云盘。结合业务特点和预算制定配置方案,并通过监控应用性能适时调整,确保资源最优利用。示例代码展示了使用阿里云CLI创建ECS实例的过程。
34 5
|
1天前
|
弹性计算 监控 开发工具
【阿里云弹性计算】实战教程:如何高效利用阿里云ECS弹性伸缩应对业务高峰
【5月更文挑战第20天】本文介绍了如何使用阿里云ECS弹性伸缩服务应对业务高峰。通过自动调整云资源规模,弹性伸缩在流量增加时扩展实例,流量减少时收缩实例,实现成本与性能的优化。步骤包括开通服务、创建伸缩组、设定规则和监控指标。文中还提供了一个Python脚本示例,并强调了优化策略,如应用无状态设计、考虑冷却时间和结合云监控。通过实践和调整,企业可以有效应对业务波动。
21 5

热门文章

最新文章