javaee-网络编程(基础)

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

Socket套接字主要针对传输层协议划分为如下三类:

(1)数据报套接字:使⽤传输层UDP协议

UDP,即UserDatagramProtocol(⽤⼾数据报协议),传输层协议。

以下为UDP的特点(细节后续再学习):

• ⽆连接

• 不可靠传输

• ⾯向数据报

• 有接收缓冲区,⽆发送缓冲区

• ⼤⼩受限:⼀次最多传输64k

(2)流套接字:使⽤传输层TCP协议

以下为TCP的特点:

• 有连接

• 可靠传输

• ⾯向字节流

• 有接收缓冲区,也有发送缓冲区

• ⼤⼩不限

(3)原始套接字

②Java数据报套接字通信基础代码:

服务器端:

java 复制代码
import javax.xml.crypto.Data;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.URLDecoder;

public class demo15UdpEchoServer {
    private DatagramSocket socket=null;

    public demo15UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }

(1)DatagramSocket:UDP 通信的插座 / 通道 ,负责接收 / 发送 UDP 数据包。先定义但不初始化

(2)构造方法:输入端口号(还可以写ip,一般写端口号就足够了,也是最标准、最简单的写法)

java 复制代码
 public void start() throws IOException {
        System.out.println("服务器启动");

        while(true){
            DatagramPacket requestPacket =new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request =new String(requestPacket.getData(),0,requestPacket.getLength());
            String response=process(request);
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
                                                //此处不能用response.length()
            socket.send(responsePacket);//需要指定目的ip目的端口
            System.out.printf("[%s:%d] req:%s, resp:%s\n",requestPacket.getAddress().toString(),responsePacket.getPort(),request,response);
            //打印日志
        }
    }

(1)start方法就是服务器主体代码,用while(true)模拟服务器一直运行的状态

(2)DatagramPacket:创建一个长度为 4096 的字节数组用于接收数据,但是只能接收一次!超出的长度直接舍弃且不会报错,但是4096足够应付绝大多数 UDP 场景

(3)socket.receive用于把数据内容放进 requestPacketbyte[]

(4)String request:把数据包中的数据转为String类型,然后长度是数据包中byte数组的从0开始往后已存入的长度个,否则如果没填满数组会把一堆空字节也传入String

(5)String response=process(request);用来处理转为字符串类型的byte然后用新的resopnse字符串接受

(6)DatagramPacket responsePacket:再次创建一个DatagramPacket用来存放处理完的结果(respoese),然后发出也要用byte模式,长度也要用byte形式的长度所以是response.getBytes().length);然后requestPacket.getSocketAddress()用来指定发送目标

socket.receive(requestPacket) 执行完之后,系统自动在DatagramSocket socket里储存了地址,直接拿就行!(在 requestPacket.getSocketAddress()中**)**

第一次主动发给别人 → 必须手动写 IP(或者 getInetAddress(),这个只包含ip**) + 端口**

接收数据之后,服务器可以直接用getSocketAddress()既包含ip也包含端口

++接收包:只需要空箱子 → 传 byte \[\] + 最大长度++

++发送包:必须装满货 + 写地址 → 传 数据 + 长度 + 地址(++ ip+端口 ++)++

(7)socket.send(responsePacket),就是利用socket把处理完的数据包responsePacket发送出去

java 复制代码
  public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        demo15UdpEchoServer server=new demo15UdpEchoServer(9090);
        server.start();
    }
}

(1)process方法,模拟处理,这里根据需求写返回String即可。这里直接返回一样的用来模拟

(2)mian方法里:demo15UdpEchoServer server=new demo15UdpEchoServer(9090);

因为所有方法都是直接在demo15UdpEchoServer服务器类里psvm外,而且也没有构造多的类来实现,所以实例化demo15UdpEchoServer

(3)server.start(); :用于启动服务器

客户端:

java 复制代码
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class demo16UDPEchoClient {

    private DatagramSocket socket=null;
    private String serverIp;
    private int serverPort;
    //UDP本身不保存对端信息,自己代码保存

    public demo16UDPEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        socket=new DatagramSocket();//不能把serverPort填这里面 客户端一定随机分配端口

    }

(1)DatagramSocket socket=null;用于连接服务器(发送数据)、

(2)private String serverIp;/private int serverPort;储存服务器的ip和端口

(3)demo16UDPEchoClient构造方法:输入服务器的ip和端口,然后实例化socket

java 复制代码
public void start() throws IOException {
        while (true) {
            Scanner cin = new Scanner(System.in);
            System.out.println("请输入要发送的内容");
            String request = cin.next();

            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);

            socket.send(requestPacket);

            DatagramPacket responsePacket = new DatagramPacket(new byte[4086], 4086);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());//开始构造的位置,长度(responsePacket的吗)
            System.out.println(response);
        }
    }

(1)一样的start方法,一直while(true)用来反复等待客户操作

(2)DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort);

这个就是用户输入的数据包,把输入的String类型的转成byte,和bytes的长度,然后输入服务器的ip和端口(跟服务器端的requestPacket.getSocketAddress()一样)
InetAddress.getByName(serverIp):把字符串格式的 IP(如 "127.0.0.1")变成 Java 能识别的 IP 对象

(3)socket.send(requestPacket);利用socket把数据包传出去

(4)DatagramPacket responsePacket = new DatagramPacket(new byte4086, 4086);

socket.receive(responsePacket);

String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

跟服务器一样的接收方法

(5)System.out.println(response);输出服务器返回的处理结果

java 复制代码
 public static void main(String[] args) throws IOException {
        demo16UDPEchoClient client=new demo16UDPEchoClient("127.0.0.1",9090);
        client.start();
    }
}

(1)方法都写在psvm外,public类里,所以要实例化

(2)跟服务器一样都要调用start

process处理案例:输入中文返回对应英文

java 复制代码
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

public class demo17UdpDictServer extends demo15UdpEchoServer{

    private HashMap<String,String> dict=new HashMap<>();


    public demo17UdpDictServer(int port) throws SocketException {
        super(port);
        dict.put("小猫","cat");
        dict.put("小狗","dog");
        dict.put("兔子","rabbit");
        dict.put("鸭子","duck");
    }
    @Override
    public String process(String request){
        return dict.getOrDefault(request,"未找到该词条");
    }
    public static void main(String[] args) throws IOException {
        demo17UdpDictServer server=new demo17UdpDictServer(9090);
        server.start();
    }
}

(1)使用HashMap:根据一个词,快速找到另一个词。HashMap 就是 Java 里专门干「快速查找」这件事的。/

(2)构造方法:其实是为了传数据给继承的父类

(这里只是为了方便继承父类演示一下处理问题的过程,实际上修改process方法即可)

③TCP流套接字编程

服务器端:

java 复制代码
import java.awt.*;
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 demo18TcpEchoServer {
    private ServerSocket serverSocket=null;

    public demo18TcpEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");
        ExecutorService pool= Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket=serverSocket.accept();
            pool.submit(()->{
                    processConnection(clientSocket);
            });
        }
    }

(1)ServerSocket :用于服务器端,等待客户端连接(客户端用Socket连接),定义但不初始化

(2)构造方法:传入端口,然后初始化ServerSocket的时候要传入端口

客户端要指定 (服务器的)IP + 端口,服务端只需要指定端口。

start方法:

(3)使用多线程:如果不用多线程,那么while循环里processConnection(clientSocket);的时候,因为processConnection(clientSocket);里面还有一个while循环是出不来的,所以只能第一个连接的客户端输入后得到输出,后面的客户端连接的时候由于不是多线程所以无法得到反馈

(4)使用newCachedThreadPool线程池:因为服务器不知道会来多少个客户端,CachedThreadPool 会自动创建线程、自动回收,最适合【短连接、客户端数量不固定】的网络服务。

(5)Socket clientSocket=serverSocket.accept();用于等待一个客户端来连接;没有客户端连接的时候,这行代码会一直阻塞(卡住不动)

这个接受代码放在循环里是可以接收完一个就接收第二个客户端的,但是也是因为如果没有多线程的话,while循环就无法进行到第二次这个代码所以也无法接受第二个客户端

java 复制代码
private void processConnection(Socket clientSocket)     {
        System.out.println("["+clientSocket.getInetAddress()+":"+clientSocket.getPort()+"]"+"客户端上线!");
        try(InputStream inputStream=clientSocket.getInputStream();
        OutputStream outputStream=clientSocket.getOutputStream()){
        Scanner cin=new Scanner(inputStream);
        PrintWriter writer=new PrintWriter(outputStream);
        while(true){
           if(!cin.hasNext()) {//没有下一个数据可读了
              System.out.println("["+clientSocket.getInetAddress()+":"+clientSocket.getPort()+"]"+"客户端下线!");
                    break;
           }
           String request=cin.next();
           String response=process(request);
           writer.println(response);
           writer.flush();
           System.out.println("["+clientSocket.getInetAddress()+":"+clientSocket.getPort()+"] req:"+request+", resp:"+response);
            }
}catch (IOException e){
            throw new RuntimeException(e);
        }finally {
            try {
                clientSocket.close();
            }catch (IOException e){
                throw new RuntimeException(e);
            }
        }
    }

接受调用处理并返回模块

(1)System.out.println(""+clientSocket.getInetAddress()+":"+clientSocket.getPort()+""+"客户端上线!");

用于提示服务器客户端上线。clientSocket.getInetAddress提供客户端的ip,clientSocket.getPort()提供客户端端口

(2)

复制代码
try(InputStream inputStream=clientSocket.getInputStream();
    OutputStream outputStream=clientSocket.getOutputStream()){

InputStream 用来读客户端发来的消息; OutputStream 用来写给客户端的消息。注意之前是new File... 这里是直接用clientSocket对应的Input和Output的api

为什么放在try-catch代码块里?:因为流、Socket 这些 IO 操作随时可能出错,Java 强制要求必须捕获异常,不写 try-catch 编译都过不去

(3)Scanner cin=new Scanner(inputStream); 把字节流包装成扫描器,方便读取字符串

复制代码
PrintWriter writer=new PrintWriter(outputStream); :包装输出流,方便打印字符串 

就是把刚刚try里面的实例名,输入放进Scanner定义的括号里;输出放进PrintWriter里

客户端和服务器之间,只传 0101 的字节,不能直接传字符串。

所以我们需要两个 "翻译工具":

Scanner 把字节 → 字符串

PrintWriter 把字符串 → 字节

(4)while循环: 只要客户端不断开,我就一直等着收消息、回消息。

(5)if(!cin.hasNext()) { : 只需要记住客户端的输出流关闭了 → 客户端断开连接了,才会出发这个即可

(6)String request = cin.next(); 这里的cin并不是真实客户端输入,而是InputStream inputStream=clientSocket.getInputStream()转化过来的字符串(这里的cin已经把二进制转为字符串)

String response=process(request); : 用于处理数据并且用response来获取返回的字符writer.println(response); : 跟cin同理,把字符串 转成 二进制,发送给客户端

writer.flush(); 用于

  • writer 会自动帮你:字符串 → 二进制

  • println(...)把转换后的二进制,通过输出流发给客户端

  • outputStream = 服务器 → 客户端 的网络管道,你把 writer 绑在了这个网络管道上,所以println直接返回客户端

(7)System.out.println(""+clientSocket.getInetAddress()+":"+clientSocket.getPort()+" req:"+request+", resp:"+response);

服务器面板输出用于记录输入和返回的字符串

(8)clientSocket.close(); 挂断连接 + 释放资源,在while循环结束(即客户端下线时)触发

java 复制代码
private String process(String request){
        return request;//代替处理
    }

    public static void main(String[] args) throws IOException {
        demo18TcpEchoServer server=new demo18TcpEchoServer(9090);
        server.start();
    }
}

(1)process方法:用于处理客户端发送的字符串

(2)psvm:实例化(设置端口)+启动服务器

客户端:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class demo19TcpEchoClient {

    private Socket socket=null;

    public demo19TcpEchoClient(String serverIp,int serverPort) throws IOException {
        socket=new Socket(serverIp,serverPort);
    }

(1)定义Socket但不初始化,用于连接客户端

(2)构造方法:输入要连接的服务器的ip 和服务器设置的端口,然后初始化socket的时候传入ip和端口

java 复制代码
public void start() {
        Scanner cin = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            //为了使用方便,套壳
            Scanner cinNet=new Scanner(inputStream);
            PrintWriter writer=new PrintWriter(outputStream);
            while (true) {
                String request = cin.next();
                writer.println(request);//发送给服务器
                writer.flush();//加上刷新缓冲器操作才能真正发出去
                String response=cinNet.next();
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

(1)正常定义Scanner用于输入

(2)try (InputStream inputStream = socket.getInputStream();

OutputStream outputStream = socket.getOutputStream()) {

Scanner cinNet=new Scanner(inputStream);

PrintWriter writer=new PrintWriter(outputStream);

跟服务器一样,inputSteam用于接收,用Scanner包装

outputStream用于输出,用PrintWriter包装

(3)while(true)循环:让客户端一直能发消息、收消息,直到你手动关闭程序

(4) String request = cin.next(); 用于接收字符串

writer.println(request); : 跟服务器同理,用于发送给服务器

writer.flush();: 把缓存区里的消息,强制立刻发送 给服务器 (只需要记住只要用 PrintWrite,后面必须跟一句 flush())

java 复制代码
public static void main(String[] args) throws IOException {
        demo19TcpEchoClient client=new demo19TcpEchoClient("127.0.0.1",9090);
        client.start();
    }

(1)实例化输入服务器ip(个人电脑服务器就是本机,用127.0.0.1代替本机)和端口 + 启动客户端

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