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与端口号

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

相关推荐
key_Go2 小时前
06.容器存储
运维·服务器·网络·docker
宁雨桥3 小时前
Nginx反向代理配置全流程实战:从环境搭建到HTTPS部署
运维·nginx·https
花开富贵贼富贵3 小时前
Nginx 配置指南:HTTPS 自签名、Location、Rewrite 与状态统计
运维·nginx·https
StevenLdh3 小时前
Docker容器化部署简要指南
运维·docker·容器
東雪蓮☆3 小时前
ELK 企业级日志分析系统实战教程
linux·运维·elk
饺子大魔王的男人3 小时前
Answer+cpolar:企业知识共享的远程协作方案
网络
RoboWizard4 小时前
传输无界 金士顿双接口U盘上新抽电脑
运维·人工智能·缓存·电脑·金士顿
Teamhelper_AR4 小时前
AR技术:轨道交通运维与安全保障的革新力量
运维·安全·ar
JoyCong19984 小时前
远程安全提示再升级!隐私屏开启位置突出、可录入被控锁屏...
服务器·网络·安全