【JavaEE】网络编程之Socket套接字

什么是网络编程 套接字

程序员写网络程序,主要编写的是应用层代码。真正要发这个数据,需要上层协议调用下层协议,应用层要调用传输层,传输层给应用层提供一组 API ,统称为 socket API 。

简单的说,网络编程套接字就是操作系统给应用程序提供的一组API(叫做socket API)。

系统给提供的 socket API 主要有两组
1.基于 UDP 的 API
2.基于 TCP 的 API

TCP和UDP协议有什么特点呢?

TCP:

1.有连接

使用 TCP 通信的双方,则需要刻意保存对方的相关信息。

2.可靠传输

3.面向字节流

4.全双工

UDP:

1.无连接

使用 UDP 通信的双方,不需要可以保存对端的相关信息

2.不可靠传输

3.面向数据报

4.全双工

1.有连接和无连接

是否需要单独记录下对方的信息,如果需要就是有连接,如果不需要,就是无连接。

有连接:可以理解成,通信双方各自记录了对方信息。 比如打电话就是有连接通信,需要先把连接接受了,才能通信。

无连接:比如我们发短信,直接投递,不需要接受连接,就能通信。当我们需要验证码的时候,临时填一下号码,服务器不需要刻意记录。

2.可靠传输与不可靠传输

可靠:发送方知道接收方是否成功发送数据。

不可靠:消息发了之后,不关注结果。

3.面向字流/数据报

面向字节流:以字节为单位进行传输,读写方式非常灵活。

面向数据报:以一个 UDP 数据报为基本单位进行传输,一个数据报会明确大小,一次发送/接收一个完整的数据报,不能是半个数据报

4.全双工/半双工

全双工:一条路径,双向通信。

半双工:一条路径,单向通信。

那么这里UDP比TCP要简单一点我们先来学习UDP。

一、UDP socket
那么UDP socket中主要涉及到两个类:DatagramSocket 和 DatagramPacket。Datagram是数据报的意思。

1.客户端服务器程序—回显服务

这里回显的意思就是客户端发了个请求,服务器返回一个一模一样的响应。请求是啥,响应就是啥。

这里我们先建立一个network包,在这个包下建立两个类分别是服务器UdpEchoServer和客户端UdpEchoClient
network包

我们先写 UdpEchoServer 的代码:

1.进行网络编程的大前提第一步需要先准备好socket实例。

private DatagramSocket socket = null;

2.此处在构造服务器这边的socket对象的时候,就需要显式的绑定一个端口号port。

前面已经介绍端口号,端口号的作用是用来区分和管理不同端口的 。

抛出异常的原因:

  • 端口号可能已经被占用了。
  • 每个进程能够打开的文件个数是有限的。
public UdpEchoServer(int port) throws SocketException {
        //构造 socket 的同时,指定要关联/绑定的端口
        socket = new DatagramSocket(port);
    }

3.启动服务器,这里我们需要知道服务器是被动接收请求的一方,主动发送请求的是客户端,DatagramPacket 刚才说过是表示一个 UDP 数据报,发送一次数据就是发送 DatagramPacket ,接收一次数据也就是在收一个 DatagramPacket 。

那么这里启动服务器分为三步:
step1:读取客户端发来的请求并解析。

public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //每次循环 做三件事情
            //1.读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //为了接收数据需要先准备好一个空的DatagramPacket对象,由recieve进行填充数据
            //为了方便处理请求 把数据包转成String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

step2:根据请求计算响应。

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

step3:把响应写回到客户端。

             //3.把响应结果写回到客户端
            // 根据 response 字符串,构造一个 DataProgramPacket
            //和请求 packet 不同,此处构造响应的时候,需要制定这个包要发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
                    //requestPacket 是从客户端收来的 getSocketAddress 得到客户端的ip和端口
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }

    //根据请求计算响应
    //由于写的这个是 回显 程序,请求是啥,响应就是啥
    private String process(String request) {
        return request;
    }
  • 这里requestPacket.getLength()这个长度不一定是4096,可能此处的UDP数据报最长是4096,实际的数据可能不够4096。

  • 注意这里send方法的参数,也是DatagramPacket,需要把响应数据先构造成一个DatagramPacket再进行发送。

  • response.getByte()这里的参数也不再是一个空的数组,response是刚才根据请求计算得到的响应。DatagramPacket里面的数据就是String response的数据。

  • requestPacket.getSocketAddress();这个参数的作用就是表示要把数据发给哪个地址+端口。

SocketAddress可以视为一个类,里面包含了IP和端口。

UdpEchoServer完整代码:

package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    //需要先定义一个 socket 对象
    //通过网络通信,必须要使用 socket 对象
    private DatagramSocket socket = null;

    //绑定一个端口,不一定能成功!
    //如果某个端口已经被别的进程占用了,此时这里的绑定操作就会出错
    //同一个主机上,一个端口,同一时刻,只能被一个进程绑定
    public UdpEchoServer(int port) throws SocketException {
        //构造 socket 的同时,指定要关联/绑定的端口
        socket = new DatagramSocket(port);
    }

    //启动服务器的主逻辑
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //每次循环 做三件事情
            //1.读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //为了方便处理请求 把数据包转成String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求计算响应(此时省略这个步骤)
            String response = process(request);
            //3.把响应结果写回到客户端
            // 根据 response 字符串,构造一个 DataProgramPacket
            //和请求 packet 不同,此处构造响应的时候,需要制定这个包要发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    //requestPacket 是从客户端收来的 getSocketAddress 得到客户端的ip和端口
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);
        }
    }

    //这个方法是根据请求计算响应
    //由于咱们写的这个是 回显 程序,请求是啥,响应就是啥
    private String process(String request) {
        return request;
    }

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

启动服务器:
启动服务器

接着写客户端UdpEchoClient的代码。

我们可以发现在客户端这里就不用手动指定端口号了,使用无参版本的构造方法,即让操作系统自己分配一个空闲的端口号。

private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    //客户端启动,需要知道服务器在哪里
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        //对于客户端来说,不需要显示关联端口
        //不代表没有端口,而是系统自动分配了个空闲的端口
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

但是对于服务器来说,必须手动指定端口号,因为后序客户端需要根据这个端口号来访问到服务器(客户端是主动发起请求的一方,需要事先知道服务器的地址和端口)。

OK,那么客户端的代码书写的步骤是什么呢?

step1、先从控制台读取用户输入的字符串

public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while(true){
            //1.先从控制台读取一个字符串过来
            //先打印一个提示符,提示用户要输入内容
            System.out.print("->");
            String request = scanner.next();

step2:把这个用户输入的内容,构造成一个UDP请求,并发送

构造的请求包含两部分信息:

1)数据的内容,request字符串。

  1. 数据要发给谁 服务器的IP+端口。
//2.把字符串构造成 UDP packet,并进行发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);

注意这里又使用到了一种DatagramPacket构造方法,既能构造数据,又能构造目标地址,这个目标地址是IP和端口分开的写法。

step3:从服务器读取响应数据并解析

//3.客户端尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);

step4:把响应结果转化为 String 显示到控制台上

//4.把响应数据转换成 String 显示出来
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.printf("req:%s,resp:%s\n",request,response);

UdpEchoClient完整代码:

package network;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    //客户端启动,需要知道服务器在哪里
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        //对于客户端来说,不需要显示关联端口
        //不代表没有端口,而是系统自动分配了个空闲的端口
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        //通过这个客户端可以多次和服务器进行交互
        Scanner scanner = new Scanner(System.in);
        while(true){
            //1.先从控制台读取一个字符串过来
            //先打印一个提示符,提示用户要输入内容
            System.out.print("->");
            String request = scanner.next();
            //2.把字符串构造成 UDP packet,并进行发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);
            //3.客户端尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            //4.把响应数据转换成 String 显示出来
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.printf("req:%s,resp:%s\n",request,response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
        udpEchoClient.start();
    }
}

注意我们刚才已经写好了客户端的代码,那么在我们写客户端代码的过程中,已经早早的启动服务器了,就是说在写客户端代码的过程中,是没人访问服务器的,这里的服务器就在receive这里,阻塞等待了。

OK那么我们现在启动客户端,输入一个hello。

再点到我们的服务器这边,可以看到已经接收到客户端的请求,这个 64879 就是系统自动给客户端分配的端口。

好啦!今天的知识点涉及较多,下期继续讲解~

拜拜

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值