javaEE初阶 网络编程(socket初识)

目录

一.套接字(socket)

1.前提知识:

(1).IP与端口号

(2).TCP/IP五层协议:

2.概念:

(1).简单认识

(2).socket对象

3.传输层两种重要协议

[1>:TCP协议 与 UDP协议](#1>:TCP协议 与 UDP协议)

2>.特点

(1).连接:通信双方分别存储了对方的关键信息(IP与端口号),可以建立起通信

[(2).可靠传输 与 非可靠传输](#(2).可靠传输 与 非可靠传输)

[(3).面向字节流 与面向数据报](#(3).面向字节流 与面向数据报)

(4).全双工

4.UDP套接字编程

1>:介绍:

2>:方法介绍

3>:回显服务器(echo-server)

(1).代码演示

(2).演示:

5.TCP套接字编程

1>:介绍

2>:方法介绍

3>:回显服务器

(1).代码

(2)运行截图

(3).问题:

a.缓冲区问题

b.内存溢出问题

c.一个服务器对多个客户端

6.发送请求的注意

二.总结

[1.我们可以操控socket对象 进而控制网卡进行网络传输](#1.我们可以操控socket对象 进而控制网卡进行网络传输)

2.对于TCP而言

3.对于UDP而言


一.套接字(socket)

1.前提知识:

(1).IP与端口号

IP地址 :⽤于定位主机的⽹络地址,简单来说,就是看应用运行在哪台电脑上

端口号:标识服务器对应进程的位置,简单来说,就是看应用在电脑的哪个位置

(2).TCP/IP五层协议:

2.概念:

(1).简单认识

套接字是一组专门用于网络编程的一组类和方法

应用层 传送消息到传输层 ,需要调用对应的函数 ,而这些函数由操作系统提供 且由c实现,把这些方法称为套接字(socket) ,为了方便使用,Java将其封装为一组类和对象

(2).socket对象

硬盘用于存储数据 ,而为了方便观察到硬盘使用情况,采用文件的大小表示硬盘的使用

而网卡用于传输数据 ,为了方便控制传输内容,我们可以用socket对象来控制网卡来进行数据传输

3.传输层两种重要协议

:这里只是简单了解,方便后续编程,具体会在新章节讲到

1>:TCP协议 与 UDP协议

TCP与UCP为了网络编程,提供了两组套接字,也就是接下来学习的目标,但在此之前先了解协议特点

2>.特点

TCP: 有连接,可靠传输,面向字节流,全双工

UDP:无需 连接,不可靠传输,面向数据报,全双工

(1).连接 :通信双方分别存储了对方的关键信息(IP与端口号),可以建立起通信

比如:两个认识的人打电话,如何确定对方身份呢?

TCP的做法(有连接) :双方手机互相存储对应的手机号与名字,拨打时看来电显示就可以确定身份,接着接通电话建立连接;删除连接 ,通信双方互相删除对应的关键信息 ,手机上则是按下挂断进行连接的释放

**UDP做法(无连接):**直接拿手机号给你发短信问你是不是那个人,至于对方回复也好,不回复也无所谓,不关心

(2).可靠传输 与 非可靠传输

首先要知道信息 在传输时可能会丢失数据 ,也就是丢包

TCP在传输时 会尽可能的传输数据,防止出现丢包现象,但速度稍微慢点 ,属于可靠传输

UDP在传输时 ,会以尽可能 把数据传递,坏处就是出现丢包 现象,属于不可靠传输

(3).面向字节流 与面向数据报

面向字节流:指数据像水流一样连续传递

面向数据报 :明确要传递的内容信息 ,对方的IP地址与端口号,将这些内容打包为数据报进行传递

(4).全双工

全双工就是支持双方一起通信,不过协议不同会导致细微差距

以打电话为例:TCP全双工就是通信双方同一时间可以同时进行说话或听对方讲话

以发短信为例:UDP全双工就是同一时间,只能有一个人进行消息发送

4.UDP套接字编程

1>:介绍:

DatagramSocket:是UDP类型的socket对象 ,⽤于发送和接收UDP数据报

DatagramPacket:是UDP数据传输的基本单位

2>:方法介绍

因为方法不好单独演示,所以写一个程序来说明

3>:回显服务器(echo-server)

回显服务器相当于网络编程中的hello word,需要写客户端服务器 ,其功能是客户端向服务器发送请求,并打印服务器传回来的响应

请求:客户端向服务器发送的数据

响应:服务器向客户端发送的数据

(1).代码演示

服务器代码:

java 复制代码
//服务器
public class EchoServer {
    // 创建socket对象方便传递读取数据
    private DatagramSocket socket ;

    // 服务器运行在本机上,IP地址也在本机,且由操作系统处理
    // 所以创建服务器时指定服务器的端口号(0-65535)
    // 一般不考虑 0-1023,因为是操作系统留给其他重要服务的端口号
    // 除此之外随便指定,若雨其他进程冲突,则会报错,此时再修改即可
    public EchoServer(int port) throws SocketException {
        // 初始化socket对象,并指定端口号 port
        socket = new DatagramSocket(port);
    }

    /**
     * 对于服务器,核心模版为:
     *          1.读取请求并解析
     *          2.根据请求计算响应
     *          3.把响应传递给客户端
     */
    public void start() throws IOException {
        System.out.println("server begin");
        // 因为不清楚客户端何时发消息,为了确保响应及时,采用 while 循环及时读取信息
        while(true){
            // 1.读取请求并解析
            // 创建 DatagramPacket对象存储从socket读取的数据
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            // 从socket读取取客户端的请求到 datagramPacket 里
            socket.receive(requestPacket);
            // 此时 datagramPacket 存储的为字节数组,而客户端与服务器最好用字符串
            // 将字节数组从 0 线标开始将1024个元素转为字符串
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            // 2.根据请求计算响应
            String response = process(request);
            // 3.把响应传递给客户端
            // 把响应传递给客户端,需要知道: 传递内容 目标IP地址与端口号
            // 所以构造 DatagramPacket 数据报对象存储这些
            // 数据报对象包括:字节数组 数组长度  客户端IP与端口号
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4.打印一下日志
            // 客户端IP  端口号  请求   响应
            System.out.printf("[%s:%d],  request: %s   response: %s \n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);
        }
    }

    /**
     * 将请求通过 process 方法计算响应
     * 不过此时,进行最简单的处理
     */
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        EchoServer echoServer = new EchoServer(9909);
        echoServer.start();
    }
}

客户端代码:

java 复制代码
// 客户端
public class Echo_client {
    // 创建socket对象方便传递读取数据
    private DatagramSocket socket;
    // 存储服务器的 IP 与 端口号
    private String serverIp;
    private int serverPort;

    // 首先客户端要向服务器发送信息,需要存储对应的 IP 与 端口号
    // 我们希期望初始化客户端时就清楚这些,所以构造方法传递对应参数并存储
    // 其次,服务器初始化时给定端口号,由程序员操作,所以端口号不会发生冲突
    // 对于客户端来说不需要指定端口号,因为无法保证指定的与用户的某个程序的端口号相同从而发生冲突
    public Echo_client(String serverIp,int serverPort) throws SocketException {
        // 不指定不代表不需要,采用无参构造方法随机指定一个空闲的端口号
        socket = new DatagramSocket();
        // 保存服务器IP与端口号
        this.serverIp = serverIp;
        this.serverPort = serverPort;
    }

    // 此时五元组已确定,即:协议:UDP协议    源IP:本机   源端口号:随机指定的    目标IP:serverIp    目标端口号:serverPort
    // 可以开始传输数据

    /**
     * 客户端需要做从控制台把字符串发送给服务器,从中读取响应
     * 核心模版为:
     *     1.读取字符串
     *     2.构造UDP数据报,发送请求
     *     3.读取响应
     */
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 方便多次发送请求
        while(true){
            // 1.读取字符串
            System.out.print("请输入请求内容>:");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("程序结束");
                break;
            }
            // 2.构造UDP数据报,发送请求
            // InetAddress.getByName() 获得目标IP
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(this.serverIp),this.serverPort);
            socket.send(requestPacket);
            // 3.读取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            // 4.显示响应
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        // 给定服务器的IP与端口号
        Echo_client echo_client = new Echo_client("127.0.0.1",9909);
        echo_client.start();
    }
}
(2).演示:

4>:简单翻译服务器

因为服务器的模版都是一样,所以只需继承回显服务器在扩招其他功能即可

java 复制代码
public class TranEchoServer extends EchoServer {
    // 存储 英文与翻译键值对
    private static Map<String,String> translate = new HashMap<String,String>();;
    static {
        translate.put("香蕉","banana");
        translate.put("苹果","apple");
        translate.put("你好","hello");
        translate.put("世界","word");
    }
    // 这里不需要给当前翻译服务器端口号,因为它本质是调用回显服务器进行数据传输
    public TranEchoServer(int port) throws SocketException {
        super(port);
    }
    // 这里 读取响应等操作都是一样,所以只需要重写 "请求计算响应" 这个步骤
    @Override
    public String process(String request){
         return translate.getOrDefault(request,"没有该值翻译");
    }

    public static void main(String[] args) throws IOException {
        TranEchoServer tranEchoServer = new TranEchoServer(10000);
        tranEchoServer.start();
    }
}

5.TCP套接字编程

TCP涉及到两种网络模型:

短连接:一个连接,只发送一个响应就关闭

长连接:一个连接,进行多次发送响应后才关闭

1>:介绍

ServerSocket (服务套接字):专门根据客户端的访问 返回一个socket对象
Socket :负责数据传输与接受

2>:方法介绍

3>:回显服务器

(1).代码

服务器代码:

java 复制代码
public class TCPEcho_server {
    // 创建 serversocket 对象,用于与客户端建立连接返回socket对象
    private static ServerSocket serverSocket ;

    // 给定服务器端口号
    public TCPEcho_server(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public  void start() throws IOException {
        System.out.println("server begin");
        while(true){
            // 首先先与客户端进行连接,才能进行后续操作
            // 此时 socket对象草扩双方的IP与端口号
            Socket socket = serverSocket.accept();
            // 接着进行后续连接操作
            processconnection(socket);
        }
    }

    private static void processconnection(Socket socket) {
        // 1.服务器要能哪个客户端进行访问,打印日志
        System.out.printf("[%s : %d]客户端上线\n",socket.getInetAddress().toString(),socket.getPort());
        // 2.获得流对象进行数据交换  读取客户端的请求与发送响应
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
//            //此处读取麻烦了,选择采用更加优秀的做法
//            while(true){
//                byte[] bytes = new byte[1024];
//                int n = inputStream.read(bytes);
//                if(n == -1)break;
//            }
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            // 处理多个请求
            while(true){
                // 3.读取请求
                // 此处采取短连接,当服务器没有读到值,默认本次链接结束

                    if(!scanner.hasNext()){
                    // 代表读取完了
                    System.out.printf("[%s : %d]客户端关闭请求\n",socket.getInetAddress().toString(),socket.getPort());
                    break;
                }
                String request = scanner.next();

                // 4.根据请求计算响应
                String response = process(request);

                // 5.发送响应
                printWriter.println(response);

                // 6.打印日志
                System.out.printf("[%s:%d] req: %s, resp: %s\n", socket.getInetAddress(), socket.getPort(),
                        request, response);
            }


        }catch (IOException e){
            e.printStackTrace();
        }
    }

    private static String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEcho_server tcpEcho_server = new TCPEcho_server(12345);
        tcpEcho_server.start();
    }
}

客户端代码:

java 复制代码
public class TCPEcho_client {
    // 创建 socket 对象用于进行数据交流
    public Socket socket = null;

    // 当 socket 对象赋值时会根据所传输的服务器IP与端口号自动创建连接
    // 给定所连接的服务器的IP与端口号
    public TCPEcho_client(String IP,int port) throws IOException {
        socket = new Socket(IP,port);
    }

    public void start(){
        Scanner scanner = new Scanner(System.in);
        // 创建流对象进行数据传输
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner scannerNewWork = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                // 1.从控制台读取数据
                System.out.printf("输入数据>:");
                String request = scanner.next();
                // 2.发送请求
                writer.println(request);
                // 3.读取响应
                if(!scannerNewWork.hasNext()){
                    break;
                }
                String response = scannerNewWork.next();
                // 4.把响应展示在控制台上
                System.out.println(response);
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TCPEcho_client tcpEcho_client = new TCPEcho_client("127.0.0.1",12345);
        tcpEcho_client.start();
    }
}
(2)运行截图
(3).问题:
a.缓冲区问题

对于TCP协议而言,内置了一个缓冲区 ,当我们进行写入操作时,会先把数据放入缓冲区中,等满了才会读取 ,为了保证数据的快速读取,提供了flush()冲缓冲区方法,会把当前缓冲区数据冲出使其被可读取

对于UDP而言,也有缓冲区,不过在内核里面,应从程序用不到

此时在代码中加入

此时代码就正确了

b.内存溢出问题

对于socket来说,也是个文件,使用时要消耗资源 ,而服务器的serversocket客户端的socket的生命周期随进程结束而结束,这个不用管,但注意:在服务器accept返回的socket对象还没关闭呢

此时只要客户端退出,直接close()关闭socket对象与连接

c.一个服务器对多个客户端

当我们创建多个客户端

可以发现,只有客户端1建立连接了,客户端二不管进行怎么操作在服务器都没显示,这是不正确的


原因 : 创建的客户端在构造方法处就会与服务器建立联系

当客户端1建立联系后,就会与服务器进行数据传输,此时服务器在等待客户端1输入数据,而其他客户端因为服务器在忙碌此时无法建立联系,自然就会在accept()处阻塞,等待服务器空闲时再建立联系


解决方法:

1.加入多线程,让每个线程负责一个客户端

java 复制代码
    public  void start() throws IOException {
        System.out.println("server begin");
        while(true){
            Socket socket = serverSocket.accept();
            // 每次建立连接都的socket,都会分配线程执行当前连接
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    processconnection(socket);
                }
            });
            thread.start();
        }
    }

每个线程发送"我是客户端X",可以清晰看到都可以与服务器进行连接

此时结构图为

2.引入线程池: 当客户端多了,线程的创建与销毁都伴随着大量的资源消耗,若是可以提前把线程创建好,需要时直接来取,可以明显减少资源消耗

java 复制代码
    public  void start() throws IOException {
        System.out.println("server begin");
        // 不能使用 FixedThreadPool,线程数目固定 
        ExecutorService executorService = Executors.newCachedThreadPool();
        while(true){
            Socket socket = serverSocket.accept();
            executorService.submit(()->processconnection(socket));
        }
    }

3.文件IO多路复用:一个线程负责多个客户端

对于线程池来说,线程数量也是有上线的,所以线程池一般负责短连接,但进入长连接后,每个线程长时间运行,很快就会达到最大线程数量,所以引入文件IO多路复用,让一个线程负责多个客户端,极大缓解这种情况

6.发送请求的注意

对这个代码,采用next() 读取请求,逻辑为:遇到空白符停止读取并返回,那么发送时要使用如图的方法,自动加\n,否则就对方无法正常读取

二.总结

1.我们可以操控socket对象 进而控制网卡进行网络传输

2.对于TCP而言

(1).需要通过accept返回连接后的socket对象 ,通过该对象调用InputStreasm OutputStream流对象进行数据传输

(2).传输数据其实传输到缓冲区 ,要用**flush()**进行冲刷操作读取数据

(3).对于accept返回的socket对象要及时close()释放 ,防止文件描述表满了无法打开文件

(4).一个服务器对多个客户端需要引入多线程

(5).发送请求或响应注意用 println(),方便后续读取

3.对于UDP而言

通过数据报 进行数据传输,需要注意数据报传输时要保存对方的IP与端口号

这里只是简单的编程学习,为后续网络编程打基础,如果对你有帮助,点一个吧~~~

相关推荐
SelectDB2 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
zzzzzz3103 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode3 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220704 天前
如何搭建本地yum源(上)
运维
大树887 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠7 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质7 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz7 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工7 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
网络研究院7 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展