代码编织梦想

什么是Socket?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
简而言之,socket是一个应用层之下,传输层之上的接口的接口层
在这里插入图片描述
可以理解为去银行办理业务,我们所在的位置就是应用层,柜台里面为传输层,柜台的窗口就是socket接口。

那么,问题来了,如何在互联网中明确标记一台设备和唯一的一个通信通道呢?
ip地址,用来标识唯一的一台设备。端口(port)号用来标识一个进程。
使用源ip地址+源port号+目标ip地址+目标port号来标识一个唯一的通信通道。

关于TCP和UDP,它们都是传输层的协议,不同的是:1、TCP是可靠的,UDP是不可靠的。可靠并不代表数据一定能够通过网络发送成功,而是发送的数据会尽可能的发送成功,并且即使失败了,对方也有感知。
2、TCP是有连接的,而UDP是无连接的。
3、TCP是面向字节流的,而UDP是面向数据报文的。

简单了解了TCP/UDP协议的不同后,我们使用Java Socket,基于TCP/UDP协议,实现客户端给服务端发送信息,经过处理发送回客户端,客户端进行显示。

1、基于TCP的Socket编程

C/S模型(客户端/服务端)流程图
在这里插入图片描述

1.1、Client端

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9898);
        Scanner console = new Scanner(System.in);
        System.out.print("请输入请求>");
        String request = console.nextLine();
        OutputStream os = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.println(request);
        writer.flush();
        InputStream is = socket.getInputStream();
        Scanner scanner = new Scanner(is, "UTF-8");
        String response = scanner.nextLine();   //对象没有响应,就一直等
        System.out.println(response);
        socket.close();
    }
}

2.2、Server端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Server {
    private static class ServiceMan extends Thread {
        private final Socket socket;

        ServiceMan(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                //获取输入流
                InputStream is = socket.getInputStream();
                //封装成Scanner
                Scanner scanner = new Scanner(is, "UTF-8");
                //使用\r\n进行分割的方式,读取请求

                //等着第一个Client发送请求
                String request = scanner.nextLine();//nextLine把\r\n已经去掉了
                System.out.println("收到请求:" + request);

                //业务处理
                String response = request;

                //发送响应,也需要使用\r\n跟在后面,进行分割
                OutputStream os = socket.getOutputStream();//得到输出流
                //封装成PrintWriter
                PrintWriter writer = new PrintWriter(
                        new OutputStreamWriter(os, "UTF-8")
                );
                //发送响应
                writer.println(response);//println会帮我们在后面加\r\n
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        //开店
        ServerSocket serverSocket = new ServerSocket(9898);
        ExecutorService threadPool = Executors.newFixedThreadPool(20);
        //循环处理任务
        //主线程只负责接待客人——建立连接的过程
        while (true) {
            
            /*
            Socket socket = serverSocket.accept();
            //所有业务处理的过程,交给工作线程去处理
            new ServiceMan(socket).start();
            */
            
            //更好的做法,引进线程池
            //各个线程之间,没有数据共享(主线程和工作线程共享socket)
            //所以天生是线程安全的
            Socket socket = serverSocket.accept();
            threadPool.execute(new ServiceMan(socket));
        }
    }
}

这里使用了线程池,是因为:
当有多个Client对Server发送请求时:
在这里插入图片描述
Server会先和第一个发送请求的Client建立连接,后序建立连接的所有Client必须等待第一个Client与Server的交互完成,才能与Server进行交互。如果第一个建立连接的Client一直没有发送消息,那么即使后面建立连接的Client向Server发送消息,Server也接收不到,就会进入一个阻塞状态。
多线程的好处就是能够处理这种阻塞状态,并且各个线程之间没有数据共享,所以天生就是线程安全的。

2、基于UDP的Socket编程

C/S模型(客户端/服务端)流程图
在这里插入图片描述

2.1、Server端

1、创建server的socket
2、循环读取请求(request),解析并处理请求,生成响应(response)
3、发送响应

提供给Server一些简单的功能。
version1:回显服务。
发送给服务端什么,就把这个消息发送回去。
version2:翻译服务。
输入英文,返回对应的意思和例句。
version3:轮盘聊天。
多个客户端,其中一个客户端发送消息到服务端,服务端随机将这个消息返回给某一个客户端。

代码如下:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.*;

public class Server {//服务端

    public static void main(String[] args) throws IOException {
              //1.创建server的socket  类似于开饭店
              // 内部会进行本地ip+port的绑定
              // 例子:饭店开张,提供一个大家都认识的地段 ip + port
              // ip虽然没传,但内部会帮我们处理,把所有的ip都会绑定
        try (DatagramSocket socket = new DatagramSocket(9939)) {
            //2.开门迎客,通过循环,处理业务
            while (true) {
            //3.处理一个要求并返回响应
                action(socket);
            }
        }
    }
    /**
     * 处理要求
     */
    private static void action(DatagramSocket socket) throws IOException {
        //1.读取客户端发来的请求
       		 //1.1准备一个字节数组,用来存放一会儿要读到的数据
        byte[] receiveBuffer = new byte[8192];
       		 //1.2把buffer封装成datagram
        DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, 0, 8192);
        	 //1.3读取请求
        socket.receive(receivePacket);
       		 //1.4从receive中返回,就意味着,有人给我发送请求了
       		 //需要将byte[]中的数据进行 字符集编码->String
        String request = new String(receiveBuffer, 0, receivePacket.getLength(), "UTF-8");
        System.out.printf("收到的请求是|%s|%n", request);//收到请求

        //2.进行服务---根据请求,处理业务,并生成响应
            //Version1:回显服务——echo服务
            //客户端发送什么过来,就发送回去什么
            //String response=request;
            
            //Version2:字典查询服务——请求是英文,响应是中文+例句 有道查询
            String response = translate(request);

            //Version3:轮盘聊天——给我发送过请求的ip+port,我会记录下来,
            // 然后再有人给我发送来新的请求时,随机选择一个ip+port发送回去
            //不保证对方还在线,所以不保证对方能收到
            //randomTalk(socket,request, receivePacket.getAddress(), receivePacket.getPort());

        //3.发送响应回去
            //Version1、2对应的发送响应代码块
        byte[] sendBuffer = response.getBytes("UTF-8");
        DatagramPacket sendPacket = new DatagramPacket(sendBuffer, 0,
                sendBuffer.length,
                receivePacket.getAddress(),
                receivePacket.getPort());
        socket.send(sendPacket);

    }
        /*Version3对应的发送响应代码块
    private static class Remote {//Remote用来保存客户端的ip+port
        private InetAddress address;
        private int port;

        private Remote(InetAddress address, int port) {
            this.address = address;
            this.port = port;
        }
    }

    //所有曾经给我发消息的客户端的信息——远端
    private static List<Remote> remoteList = new ArrayList<>();
    private static Random random = new Random();

    private static void randomTalk(DatagramSocket socket,String request, InetAddress address, int port) throws IOException {

        System.out.printf("之前已经有%s个客户端发送了消息%n",remoteList.size());
        if(remoteList.size()>0) {
        //随机一个下标,决定吧这个消息发给他
        int rIndex = random.nextInt(remoteList.size());
        Remote remote = remoteList.get(rIndex);
            System.out.printf("决定发送给%s客户端%n",rIndex);

        //发送消息
        byte[] sendBuffer = request.getBytes("UTF-8");
        DatagramPacket sendPacket = new DatagramPacket(sendBuffer, 0,
                sendBuffer.length,
                remote.address,
                remote.port
        );
        socket.send(sendPacket);
    }
        //发送完毕将自己加入remoteList中
        remoteList.add(new Remote(address,port));
    }
    */
    
    //Version2用到的内部类和一些静态代码块、方法
    private static class Result {//字典
        String chinese;//中文字段
        String sentence;//英文字段
        private Result(String chinese, String sentence) {
            this.chinese = chinese;
            this.sentence = sentence;
        }
    }

    private static Map<String, Result> dictionary = new TreeMap<>();//用来存放字典中的元素
    //静态代码块,用于初始化静态属性
    static {
        dictionary.put("dictionary", new Result("字典", "He threw my dictionary back."));
        dictionary.put("mask", new Result("口罩", "They contrived a mask again"));
    }

    private static String translate(String english) {
        //按最简单的翻译功能实现——提前保存一份字典
        Result result = dictionary.get(english);
        if (result == null) {
            return "不支持的单词.";
        }
        return String.format("%s%n%s%n", result.chinese, result.sentence);
    }
}

2.2、Client端

循环{
1、读取用户输入
2、封装成请求并发送
3、读取并解析响应
4、把结果返回给用户
}

代码如下:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {//客户端
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        try (DatagramSocket socket = new DatagramSocket()) {
            while (true) {
                //读取用户输入
                System.out.print("随便输入什么然后回车>");
                String str = scanner.nextLine();
                //发送请求
                byte[] sendBuffer = str.getBytes("UTF-8");
                DatagramPacket sendPacket = new DatagramPacket(
                        sendBuffer, 0, sendBuffer.length,
                        InetAddress.getByName("127.0.0.1"), 9939
                );
                socket.send(sendPacket);//完成发送
                //接收响应的过程
                byte[] receiveBuffer = new byte[8192];
                DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, 0, receiveBuffer.length);
                socket.receive(receivePacket);
                //真正接收到响应,进行字符集解码处理
                String response = new String(receiveBuffer, 0, receivePacket.getLength(),"UTF-8");
                System.out.printf("From 服务端$|%s|%n", response);
            }
        }
    }
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/qq_41596432/article/details/111093788

剑指offer Java版 面试题18. 删除链表的节点-爱代码爱编程

题目描述 在 O(1) 时间内删除链表节点。在给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。 链表节点与函数定义如下: public class ListNode { int val; ListNode next = null; } void deleteNode(ListNode head, ListN

java基础语法 简单整理了一点-爱代码爱编程

跟狂神老师学习java 学习视频地址:狂神说java 基础语法简单整理一点 注释 标识符 关键字 注释 分为: 单行注释 //注释内容 多行注释 /* 注释内容*/ 文档注释 /** 注释内容 */ 标识符 所有的标识符都必须以字母(A-Z或a-z),美元符($)或下划线(_)开头 关键字 数据类型 分为:基本数据类型和引用类型

Java实现优化版【快速排序】+四度优化详解-爱代码爱编程

参考书目:《大话数据结构》 一:快速排序 1.基本思想:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。 2.核心问题:枢轴的获取。选取一个关键字,通过调整使得它左边的值都比它小,右边的值都比它大,则这样的关键字称为枢轴值(pivotkey),

项目 6 统计雇员薪水-爱代码爱编程

项目 6 统计雇员薪水 请编写一个 Java 应用程序,使用两个数组分别保存雇员的姓名和雇员的薪水(标准金额数字,包含两位小数)。程序提示用户输入雇员的人数,然后依次提示输入雇员的姓名和薪水。当输入雇员姓名后,姓名应该随即出现在后面的提示输入薪水信息中 (具体格式参考后面的程序运行效果图) 。 当所有的雇员信息都输入完成后,程序给出功能菜单供用户选择操

薪酬管理系统(java)-爱代码爱编程

薪酬管理系统(java) 前言一、总体架构二、不同模块功能流程介绍1.用户计算工资2.修改用户信息/创建用户3.录入工作量4.参数设置总结 前言 该系统是基于Java语言,模拟学院老师的薪资建立的系统 例如:该系统分成了不同的模块,登录界面,注册界面,登录后分为了用户信息、工资查询、工资发放、用户管理、工作量录入、参数设置模块。 提示:下面

国产达梦数据库8 jdbc maven包,使用官方提供的包制作,方便大家使用-爱代码爱编程

dameng-maven 国产达梦数据库8 dm8 jdbc Maven包,使用官方提供的包制作,方便大家使用 下载地址: Gitee dameng-maven 使用说明 Maven<dependency> <groupId>dm</groupId> <artifactId>dm-con

高级计算机网络(习题三加解析)-爱代码爱编程

个性不要个体;独立不要孤立;自由不要自私;浪漫不要散漫 路漫漫其修远兮,吾将上下而求索—屈原 离骚 文章介绍: 这是计算机网络老师布置的课后作业,参考文章:习题一 , 习题二 , 持续更新… 题目都很新型,网上很难能够找出所有答案,今天分享出来,希望能够帮助有需要的人,一起学习进步! # 本文章分享由小亮子整理汇总,如有转载,请注明出处!!

虚拟机与电脑主机网络配置-爱代码爱编程

下面主要分享记录我的虚拟机网络配置过程以及遇到网络不通问题的解决办法: 1、配置:在虚拟机上将网络连接配置成NAT模式,当然桥接模式也行,我是使用NAT模式,如下图: 然后再在终端中查看网络地址等信息,虚拟机中使用如下命令: ifconfig 其中ens33为我们使用到的网卡,虚拟机的IP地址为192.168.229.128,当然每个人的不一样。 再

网站常见错误代码解释-爱代码爱编程

转载自:http://www.llidc.com/news/27.html 一般的错误都在这里了 404错误提示—找不到文件或者目录不存在 403错误提示–找不到默认首页 505错误提示–服务器内部错误 1xx-信息提示 这些状态代码表示临时的响应。客户端在收到常规响应之前,应准备接收一个或多个1xx响应。 100-继续。 101-切换协议。2xx

Docker 网络-爱代码爱编程

一.网络基础 Docker 使用到的与 Linux 网络有关的技术分别有:网络名称空间、Veth、Iptables、网桥、路由 1.什么是网络名称空间 为了支持网络协议栈的多个实例,Linux 在网络协议栈中引入了网络名称空间(Network Namespace)这些独立的协议栈被隔离到不同的命名空间中处于不同的命名空间的网络协议栈是完全隔离的,彼此

华三 h3c super vlan配置-爱代码爱编程

Super vlan配置   想在此基础上、SWB上面配置静态路由,实现PC和SWB的互通 [SWA]vlan 2 [SWA-vlan2]port g1/0/1 [SWA]vlan 3 [SWA-vlan3]port g1/0/2 [SWA]vlan 10 [SWA-vlan10]supervlan----------super vlan