网络编程套接字1

在应用层中操作系统提供的一组api=>socket api(传输层给应用层提供)。

在传输层中有两个重要的协议:TCP协议和UDP协议,这两个协议差别非常大,编写代码的时候也是不同的风格。

TCP:有链接、可靠运输、面向字节流、全双工

UDP:无连接、不可靠传输、面向数据报、全双工

1.有链接/无连接

这里的连接是抽象的概念,虚拟的/逻辑上的连接。对于TCP来说,TCP协议中,就保存了对端的信息,A和B通信,A和B先建立连接,让A保存B的信息,B保存A的信息(彼此之间知道,谁是和它建立连接的那个),这就是有链接;对于UDP来说,UDP协议本身不保存对方的信息,所以是无连接。

2.可靠传输/不可靠传输

可靠传输就是不保证数据包100%到达,而是尽可能的提高传输成功的概率,如果出现丢包了就能感知到;不可靠传输是把数据发了,就不管了。

3.面向字节流、面向数据报

面向字节流指读写数据的时候,是以字节为单位的,支持任意长度,但会发生粘包问题;面向数据报读写数据的时候,以一个数据报为单位(不是字符),一次必须读写一个UDP数据报,不能是半个,不存在粘包问题。

4.全双工、半双工

全双工一个通信链路支持双向通信(即能读也能写);半双工一个通讯链路只支持单向通信(要么读要么写)。

接下来就开始网络编程,⽹络编程,指⽹络上的主机,通过不同的进程,以编程的⽅式实现⽹络通信(或称为⽹络数据传 输)。

Socket套接字

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

分类:

Socket套接字主要针对传输层协议划分为如下三类: 流套接字:使⽤传输层TCP协议;数据报套接字:使⽤传输层UDP协议;原始套接字 原始套接字⽤于⾃定义传输层协议,⽤于读写内核没有处理的IP协议数据。

UDP数据报套接字编程

计算机中的文件通常是一个广义上的概念,文件还能代替一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的),电脑中的网卡就可以抽象成socket文件,我们操作网卡的过程和操作普通文件差不多,直接操作网卡不好操作,把网卡转换成操作socket文件,socket文件就相当于“网卡的遥控器”

API介绍:

1.  DatagramSocket 是UDP Socket,⽤于发送和接收UDP数据报。

构造方法:就相当于打开文件

e57a14eb3ffc4f11b3b535e8edc69e19.png

a8332c55917b40c4b196ef62cf25d3a5.png

创建socket的时候,就会关联上一个端口号,使用端口号区分主机上不同的应用程序。

DatagramSocket方法:这就相当于读写操作

f50d5ee264cb4de38ba16f7e0df352c8.png

2.DatagramPacket 表示一个完整的UDP数据报

构造方法:

9133555f6ce345c691650ddaa3642867.png

方法:

cc25d846acbd477b8cf74494515f957d.png

992b1353539b4a9490b40517b790b31a.png

代码实现:UDPEchoServe

public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 指定了一个固定端口号, 让服务器来使用.
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        // 启动服务器
        System.out.println("服务器启动");

        while (true) {
            // 循环一次, 就相当于处理一次请求.
            // 处理请求的过程, 典型的服务器都是分成三个步骤的.
            // 1. 读取请求并解析.
            //    DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            //    把读取到的二进制数据, 转成字符串. 只是构造有效的部分.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2. 根据请求, 计算响应. (服务器最关键的逻辑)
            //    但是此处写的是回显服务器. 这个环节相当于省略了.
            String response = process(request);

            // 3. 把响应返回给客户端
            //    根据 response 构造 DatagramPacket, 发送给客户端.
            //    此处不能使用 response.length()
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            //    此处还不能直接发送. UDP 协议自身没有保存对方的信息(不知道发给谁)
            //    需要指定 目的 ip 和 目的端口.
            socket.send(responsePacket);

            // 4. 打印一个日志
            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 server = new UdpEchoServer(9090);
        server.start();
    }
}

代码相关说明:

1.UDPEchoServe在这个类中的Echo是回声的意思,即回显服务器(就是客户端给服务器发送一个数据(请求),服务器返回一个数据(响应))。

2.f463ddd706f549dc97ab0ecb9c98f12f.png 对于服务器来说,客户端什么时候发送请求,发多少个请求我们无法预测,因此服务器中通常都需要一个死循环,持续不断的读取客户端的请求数据。

3.702c9f01c0e743fe9ff793eb6c724dc5.png这里的参数是作为“输出的结果”,为输出型参数,在调用该方法之前,先构造空的(但不null),把对象传到receive里面,receive就会把数据从网卡中读取出来,填充到参数中。

4.e384883029da4a4b9ccd211d671156c0.png计算String中字节的个数;response.length()计算String中字符的个数。

5.收到请求的源IP,源端口就是返回响应的目的IP、目的端口。

6.5ceffb9ef4b44ecf956116a1d1e3ed54.png这里用printf打印,用于格式化输出,它可以使用占位符(%d %s %f等)来控制输出格式,而println不可以。

7.socket关闭不需要close,因为此处的socket对象,伴随整个UDP服务器,如果服务器关闭,进程结束就会自动释放PCB的文件描述符表中的所有资源,也不需要手动调用close了。

8.receive会触发阻塞行为,客户端请求发来了,receive才会返回,客户端的请求没来,receive就会一直阻塞。

代码相关解析:

1.socket对象代表网卡文件,读这个文件,相当于从网卡收文件,写这个文件等于让网卡发数据。

16262032869e43ebaf1ff0c6a269b700.png

2.

e54d7cf1639240e68e207bcaffe078fb.png

在该循环中主要做三件事:1. 读取请求并解析2. 根据请求, 计算响应. (服务器最关键的逻辑),但是此处写的是回显服务器. 这个环节相当于省略了.3. 把响应返回给客户端。

3.读取请求并解析

50f6b6ecf06c450fa1e881399e2b1561.png

构造DatagramPacket对象,DatagramPacket就代表UDP数据包;然后调用receive,理解输出型参数;把UDP数据包载荷取出来,构造成一个String,通过56b6b9835b794553bd52bcd6b4d46881.png,拿到DatagramPacket中的字节数组,d03de213e8e14e83ab6bfdb04cb9004e.png拿到有效数组的长度,根据字节数组,构造出一个String846591c5f2ad47d3be775ab675dee56b.png.

4.根据请求计算响应

fe0b131eb78d4bf0a2843078ad3508e4.png

ebb2d069fd1b49f8b65b8f26e98246f6.png

5.

75fc3fe25750481f935a4da3f504a67d.png

41a1d7b699294c119e09d513a2d456c5.png拿到字符串中的字节数组,f26eb3264f40469aaa94af734e139f8e.png拿到字节数组的长度,而不是使用字符串长度,5c2af221ee6542469a86e538175b7139.png拿到客户端的IP和端口号。2203656a04984667b3ca60eb720c917e.png构造响应数据报,afd6e2e63e7a423e88290f41f8a5fa0d.png把构造好的数据报发送出去,前提是数据包中包含了目的IP和目的端口。

6.f4a91bffc9814436b5ec4f54a9ae46db.png

记录这次的客户端/服务器的交互过程。

UDPEchoClient 

public class UdpEchoClient {
    private DatagramSocket socket = null;

    // UDP 本身不保存对端的信息, 就自己的代码中保存一下
    private String serverIp;
    private int serverPort;

    // 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 从控制台读取用户输入的内容.
            System.out.println("请输入要发送的内容:");
            if (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
            //    构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            // 3. 发送数据报
            socket.send(requestPacket);
            // 4. 接收服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 5. 从服务器读取的数据进行解析, 打印出来.
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

代码相关说明:

1.

59ce9f52042d4991975d1ddfa8067939.png

在这里DatagramSocket方法,一定不能填写servePort,必须使用无参数版本。

客户端访问服务器serveip是目的IP,serveport是目的端口,源IP客户端所在的主机IP,源端口应该是随机搞一个端口(操作系统随机分配空闲窗口),不推荐客户端使用固定端口。如果客户端是固定端口,很可能客户端运行的时候,这个端口被别的程序占用,就会使当前这个程序运行失败。

2.

13fb9dc076554bfa9a3d8bbb8dbfa919.png

构造请求的数据报:1.载荷2.目的IP 目的端口,serverIp按照字符串的方式来构造。

3.9b68690c1231410c8a29a8385f592ac5.png

构造client对象指定服务器的IP和端口,这里的127.0.0.1是一个特殊的IP,环回IP,表示当前这个主机,无论你主机的IP真实是啥,都可以使用127.0.0.1代替,类似于this,必须客户端和服务器在同一个主机上才可以用该IP访问,如果是不同主机,就要填写其他IP。

其他:

SMTP:简单邮件协议

FTP:文件传输协议

UDP:用户数据报协议

TELNET:Internet远程登录服务的标准协议

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值