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 byte[4086], 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代替本机)和端口 + 启动客户端

相关推荐
云飞云共享云桌面2 小时前
8人SolidWorks研发共享一台服务器——性能算力共享智能按需分配
运维·服务器·网络·数据库·3d·电脑
wanhengidc2 小时前
云手机 操作简单易上手
网络·安全·智能手机
鸠摩智首席音效师2 小时前
什么是 Unix / Linux 中的僵尸进程 ?
linux·服务器·unix
AI流程架构师(预备)2 小时前
用 Docker 部署语音识别服务(funasr)
运维·docker·容器
aodunsoft2 小时前
安全月报 | 傲盾DDoS攻击防御2026年3月简报
网络·安全·ddos
掘根2 小时前
【微服务即时通讯】入口网关子服务
运维·微服务·架构
曦月合一2 小时前
访问服务器json接口,将json字符串解析成json格式的demo
运维·服务器·json
开开心心_Every2 小时前
轻松加密文件生成exe,无需原程序解密
运维·服务器·网络·电脑·excel·consul·memcache
Lxinccode2 小时前
wsl(1) : docker里面的容器访问wsl的服务
运维·docker·容器·wsl容器访问宿主机