网络编程,Java socket

1. 网络基本含义

1.1. 计算机网络

  • 计算机网络按照规模分类
    • 局域网LAN
    • 城域网MAN
    • 广域网WAN
  • 计算机网络按照传输介质分类
    • 同轴电缆网络
    • 双绞线网络
    • 光纤网络
    • 卫星网络
  • 计算机网络按照拓扑结构分类
    • 星形网络
    • 总线网络
    • 环状网络

1.2. 网络通信协议

在这里插入图片描述在这里插入图片描述

  • 数据的封装与拆封
    1. 封装-发送数据
      在这里插入图片描述
    2. 拆封-接受数据
      在这里插入图片描述
  • TCP/IP协议栈
    • 网络层主要协议IP协议
    • 传输层主要协议TCP和UDP
      在这里插入图片描述

1.3. TCP协议和UDP协议

  • TCP:一种面向连接的、可靠的、基于字节流的运输层通信协议
    • 特点
      • 面向连接
      • 点到点的通信
      • 高可靠性:三次握手
      • 占用系统资源多、效率低
    • 生活案例:打电话
    • 应用案例:HTTP、FTP、TELNET、SMTP
      在这里插入图片描述
  • UDP:一种无连接传输层协议,提供面向事物的简单不可靠信息传送服务
    • 特点
      • 非面向连接,传输不可靠,可能丢失
      • 发送不管对方是否准备好,接收方收到也不确认
      • 可以广播发送
      • 非常简单的协议,开销小
    • 生活案例:发送短信,发电报
    • 应用案例:DNS、SNMP

1.4. IP地址和端口

  • IP地址
    • 分类
      • IPV4: 32位地址,以点分十进制表示,如192.168.0.1
      • IPV6: 128位,写成8个16位的无符号整数,每个整数用四个16进制位表示,数之间用冒号:分开
    • 特殊的IP地址
      • 127.0.0.1:本机地址
      • 192.168.0.0–192.168.255.255:私有地址,属于非注册地址,专门为组织机构内部使用
  • 端口port
    • 分类
      • 公认端口:0-1023,比如80端口分配给www,21端口分配给FTP
      • 注册端口:1024-49151,分配给用户进程或应用程序
      • 动态/私有端口:49152–65535
    • IP和端口API
      • InetAddress类:封装计算机的IP地址,没有端口
      • InetSocketAddress类:包含端口,用于socket通信
  • IP和端口的关系
    • 必须同时指定IP地址和端口号才能正确的发送数据
    • IP地址好比电话号码,端口号好比分机号
public class TestInetAddress {
    public static void main(String[] args) throws UnknownHostException {
        // 认识本机的IP地址
        InetAddress ia = InetAddress.getLocalHost();
        System.out.println(ia);
        System.out.println(ia.getHostName());
        System.out.println(ia.getHostAddress());  // 127.0.0.1
        byte[] buf = ia.getAddress();
        System.out.println(buf.length); // 4
        System.out.println(Arrays.toString(buf)); // [127, 0, 0, 1]
        InetAddress ia2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ia2); // www.baidu.com/198.18.3.73
        System.out.println(ia2.getHostAddress()); // 198.18.3.73
        System.out.println(Arrays.toString(ia2.getAddress())); // [-58, 18, 3, 73]
    }
}
public class TestInetSocketAddress {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getByName("www.baidu.com");
        int port = 4321;
        InetSocketAddress isa = new InetSocketAddress(addr, port);
        System.out.println(isa); // www.baidu.com/198.18.3.73:4321
        System.out.println(isa.getAddress()); // www.baidu.com/198.18.3.73
        System.out.println(isa.getPort()); // 4321
    }
}

2. TCP编程

2.1. URL统一资源定位符

  • URL:统一资源定位符,由4部分组成:协议、存放资源的主机域名、端口号和资源文件名
  • URL是指向互联网资源的指针
    • 资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询
public class TestURL {
    public static void main(String[] args) throws IOException {
        URL url = new URL("https://www.google.com/#/api?name=wyb&");
        System.out.println(url.getProtocol()); // https
        System.out.println(url.getHost()); // www.google.com
        System.out.println(url.getDefaultPort()); // 443
        System.out.println(url.getPath()); // /
        System.out.println(url.getQuery()); // null
        System.out.println(url.getRef()); // /api?name=wyb&
        System.out.println(url.getAuthority()); // www.google.com
        System.out.println(url.getContent()); // sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@2f490758
        System.out.println(url.getFile()); // /
    }
}

2.2. Socket套接字

我们开发的网络应用程序都位于应用层,TCP和UDP属于传输层协议,在应用层和传输层之间,使用套接字来进行分离
套接字就像传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或接收远程发来的数据;数据进入这个小口之后,如何传输,是属于网络其他层次的工作
在这里插入图片描述
在这里插入图片描述

  • Socket实际是传输层供给应用层的编程接口。传输层则在网络层的基础上提供进程到进程间的逻辑通道,而应用层的进程则利用传输层向另一台主机的某一进程通信。Socket就是应用层与传输层之间的桥梁
  • 使用Socket编程可以开发客户机和服务器应用程序。可以在本地网络上进行通信,也可通过Internet在全球范围内通信

2.3. TCP编程-一次单向通信

功能:实现多个用户同时登陆,分为一次单向通信、一次双向通信、传输对象、引入多线程来实现

public class LoginServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8800);
        // 2. 使用ServerSocket进行监听
        Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
        // 3. 处理用户的请求
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String result = dis.readUTF();
        System.out.println("这里是服务端:" + result);
        // 4. 关闭资源
        dis.close();
        // is.close();
        // socket.close();
        // serverSocket.close();
    }
}
public class LoginClient {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Sokcet,指明服务端的IP地址和端口号
        Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
        // 2. 向服务器端发起一个新的请求
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("username=wyb&password=xz");
        // 3. 关闭资源
        dos.close();
        // os.close();
        // socket.close();
    }
}

2.4. TCP编程-一次双向通信

public class LoginServer1 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8800);
        // 2. 使用serverSocket进行监听
        Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
        // 3. 处理用户请求
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String result = dis.readUTF();
        System.out.println("这里是服务端:" + result);
        // 4. 给出客户端响应
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("您好,登陆成功");
        // 5. 关闭资源
        dis.close();
        dos.close();
    }
}
public class LoginClient1 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,指明服务器端的IP地址和端口号
        Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
        // 2. 向服务器端发起一个新的请求
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("username=wyb&password=xz");
        // 3. 接受来自服务器端的响应
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String result = dis.readUTF();
        System.out.println("这里是客户端:" + result);
        // 3. 关闭资源
        dis.close();
        dos.close();
    }
}

3. TCP编程

3.1. 编程-传输对象

在一次双向通信基础上进一步优化,在客户端输入用户名和密码,并封状到User对象中

public class User implements Serializable {
    private String userId;
    private String password;

    public String getUserId() {
        return userId;
    }
}
public class LoginServer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 1. 创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8800);
        // 2. 使用ServerSocket进行监听
        Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
        // 3. 处理用户的请求
        InputStream is = socket.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        User user = (User)ois.readObject();
        System.out.println("这里是服务端:" + user);
        // 4. 给出客户端响应
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        if(user.getUserId().indexOf("wyb") >= 0) {
            dos.writeUTF("您好,登陆成功");
        }else {
            dos.writeUTF("您好,登陆失败,请重新输入");
        }
        // 5. 关闭资源
        ois.close();
        dos.close();
    }
}
public class LoginClient2 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,指明服务器端的IP地址和端口号
        Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
        // 2. 从键盘输入用户名和密码,封装到User对象中
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String userId = input.next();
        System.out.println("请输入密码:");
        String password = input.next();
        User user = new User(userId, password);
        // 3. 向服务器发起一个新的请求
        OutputStream os = socket.getOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(user);
        // 4. 接收来自服务器端的响应
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String result = dis.readUTF();
        System.out.println("这里是客户端:" + result);
        // 5. 关闭资源
        dis.close();
        oos.close();
    }
}
  • 使用TCP实现网络登录功能总结
    • 服务器创建ServerSocket,在指定端口监听并处理请求
    • ServerSocket通过accept(),接收用户请求并返回对应的Socket,否则一直处于监听等待状态,线程也被阻塞】
    • 客户端创建Socket,需要指定服务器的ip和端口号,向服务器发送和接收响应
    • 客户端发送数据需输出流(写),客户端获取反馈数据需输入流(读)
    • 服务端反馈数据需输出流(写),服务端获取请求数据需输入流(读)
    • 一旦使用ServerSocket和Socket建立了网络连接后,网络通信和普通IO流操作并没有太大区别
    • 网络通信输出流建议使用DataOutputStream和ObjectOutputStream,与平台无关,输入流相应使用DataInputStream和ObjectInputStream
    • 如果是字符串通信,也可以使用BufferedReader和PrintWriter,简单方便

3.2. TCP编程-引入多线程

  • 实际应用中,服务端一直处于运行状态,并且会出现多个用户同时登录的情况,需要服务端进行处理
  • 引入多线程,服务端收到请求后,开辟一个新的线程,实现对用户请求的处理
public class LoginThread extends Thread {
    private Socket socket;
    public LoginThread(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        ObjectInputStream ois = null;
        DataOutputStream dos = null;
        try {
             ois = new ObjectInputStream(socket.getInputStream());
             dos = new DataOutputStream(socket.getOutputStream());
             // 1. 处理用户的请求
             User user = (User)ois.readObject();
             // 2. 给出客户端响应
             if(user.getUserId().indexOf("wyb") >= 0) {
                 dos.writeUTF("您好,登陆成功");
             }else {
                 dos.writeUTF("您好,登陆失败,请重新登陆");
             }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭资源
            try {
                if(ois != null) {
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(dos != null) {
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class LoginServer3 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8800);
        int i = 0;
        while (true) {
            // 2. 使用ServerSocket进行监听
            Socket socket = serverSocket.accept();
            // 3. 创建一个新的线程来处理请求
            Thread thread = new LoginThread(socket);
            thread.start();
            // 4. 输出请求的次数,客户端的ip地址
            String ip = socket.getInetAddress().getHostAddress();
            System.out.println("这是第" + (++i) + "次访问,对方的ip地址是:" + ip);
        }
    }
}

4. UDP编程

  • 需求:完成在线咨询功能,客户和咨询师在线交流
  • 分析
    • 使用基于UDP协议的Socket网络编程实现
    • 不需要利用IO流实现数据的传输
    • 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地
  • UDP基本概念
    • DatagramSocket:用于发送或接收数据包
    • DatagramPacket:封装要发送的数据和目的地的SocketAddress

4.1. UDP编程-一次单向通信

public class AskServer {
    public static void main(String[] args) throws IOException {
        // 创建一个Socket,指明接收数据的端口
        DatagramSocket socket = new DatagramSocket(8888);
        // 2. 接收数据并输出
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);
        System.out.println(new String(packet.getData(), 0, packet.getLength()));
        // 数据长度
        System.out.println(packet.getLength());
        // 客户端的ip
        System.out.println(packet.getAddress());
        // 客户端接收数据的端口
        System.out.println(packet.getPort());
        // 关闭socket
        socket.close();
    }
}
public class AskClient {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
        DatagramSocket socket = new DatagramSocket();
        // 2. 封装数据并发送
        String str = "在吗?";
        byte[] buf = str.getBytes();
        InetAddress address = InetAddress.getByName("198.18.3.73");
        int port = 8888;
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
        socket.send(packet);
        socket.close();
    }
}

4.2. UDP编程-一次双向通信

public class AskServer1 {
    public static void main(String[] args) throws IOException {
        // 创建一个Socket,指明接收数据的端口
        DatagramSocket socket = new DatagramSocket(8888);
        // 2. 接收数据并输出
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);
        System.out.println(new String(packet.getData(), 0, packet.getLength()));
        // 3. 发送响应消息给客户端
        String str = "在的";
        byte[] buf1 = str.getBytes();
        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length, address, port);
        socket.send(packet1);
        // 关闭socket
        socket.close();
    }
}
public class AskClient1 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
        DatagramSocket socket = new DatagramSocket();
        // 2. 封装数据并发送
        String str = "在吗?";
        byte[] buf = str.getBytes();
        InetAddress address = InetAddress.getByName("198.18.3.73");
        int port = 8888;
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
        socket.send(packet);
        // 3. 接收服务端的响应信息
        byte[] buf1 = new byte[1024];
        DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
        socket.receive(packet1);
        System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
        socket.close();
    }
}

4.3. UDP编程-多次双向通信

public class AskClient1 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
        DatagramSocket socket = new DatagramSocket();
        // 2. 封装数据并发送
        String str = "在吗?";
        byte[] buf = str.getBytes();
        InetAddress address = InetAddress.getByName("198.18.3.73");
        int port = 8888;
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
        socket.send(packet);
        // 3. 接收服务端的响应信息
        byte[] buf1 = new byte[1024];
        DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
        socket.receive(packet1);
        System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
        socket.close();
    }
}
public class AskClient2 {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
        DatagramSocket socket = new DatagramSocket();
        Scanner input = new Scanner(System.in);
        while (true) {
            String str = input.nextLine();
            byte[] buf = str.getBytes();
            InetAddress address = InetAddress.getByName("198.18.3.73");
            int port = 8888;
            DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
            socket.send(packet);
            if("bye".equals(str)) break;
            byte[] buf1 = new byte[1024];
            DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
            socket.receive(packet1);
            System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
        }
        socket.close();
    }
}

在这里插入图片描述

public class UploadClient {
    public static void main(String[] args) throws IOException {
        // 创建一个Socket,指明访问哪个服务器的哪个端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        // 使用Socket向服务器端上传文件
        // 1. 创建一个输入流和输出流
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        bis = new BufferedInputStream(new FileInputStream(new File("/d:readme.txt")));
        bos = new BufferedOutputStream(socket.getOutputStream());
        // 2. 使用输入流和输出流完成文件复制
        // 2.1. 准备一个中转站(水杯)
        byte[] buf = new byte[1024];
        int len = bis.read(buf);
        while (len != -1) {
            bos.write(buf, 0, len);
            len = bis.read(buf);
        }
        // 3. 关闭输入流和输出流
    }
}

文件上传–服务端

public class UploadServer {
    public static void main(String[] args) throws IOException {
        // 创建一个ServerSocket,可以在某个端口监听
        ServerSocket serverSocket = new ServerSocket(8888);
        // 使用创建的serverSocket在指定的端口监听
        Socket socket = serverSocket.accept();
        // 接收来自客户端的文件流,并完成将文件保存到本地
        // 1. 创建一个输入流和输出流
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        bis = new BufferedInputStream(socket.getInputStream());
        bos = new BufferedOutputStream(new FileOutputStream("d: readme1.txt"));
        // 2. 使用输入流和输出流完成文件复制
        // 2.1. 准备一个中转站(水杯)
        byte[] buf = new byte[1024];
        int len = bis.read(buf);
        while (len != -1) {
            bos.write(buf, 0, len);
            len = bis.read(buf);
        }
        // 3. 关闭输入流和输出流
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值