网络编程套接字(Socket)

简介: 网络编程套接字(Socket)

为什么需要网络编程???    -丰富的网络资源

每天你在b站上刷着喜欢的up主的视频,实质是通过网络,获取到网络上的一个视频资源

与本地打开文件类似,只是视频文件这个资源来源是网络

所谓的网络编程,其实就是从网络上获取各种数据资源

什么是网络编程??

网络编程,指的是网络上的主机,通过不同的进程,以编程的方式实现网络通信(数据传输)

此时的主机也可以发送方和接收方是同一个,只需要保证进程号不同即可

但是一般我们是用于不同主机之间的通信的

Socket 套接字

概念:是由系统提供用于网络通信的技术,是基于TCP/UDP协议的网络通信的基本操作单元

基于Socket套接字的网络程序开发的就是网络编程

下面我们介绍UDP和TCP中是如何实现的

UDP特点: 无连接,面向数据报,不可靠传输,全双工(可双向传输),大小受限(64字节),有接收缓冲区,无发送缓冲区

TCP特点:有连接,面向字节流,可靠传输,全双工,有接收和发送缓冲区,大小不受限

UDP socket api的使用

主要是两个类 DatagramSocket(用于发送和接受数据报)和DatagramPacket(数据报)

此处的api都是封装了操作系统的api

⽅法签名 方法说明
DatagramSocket()
创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端(⼀般⽤于客⼾端)
DatagramSocket(intport) 创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端(⼀般⽤于服务端)

DatagramSocket ⽅法:

⽅法签名 ⽅法说明
void receive(DatagramPacket p) 从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待)
void send(Datagram Packetp) 从此套接字发送数据报包(不会阻塞等待,直接发送)
void close() 关闭此数据报套接字

DatagramPacket

DatagramPacket是UDP Socket发送和接收的数据报。

DatagramPacket 构造⽅法:

注:这里的DatagramPacket是一个输出型参数,传入字节数组后通过引用来修改源数组

⽅法签名 ⽅法说明
DatagramPacket(byte[] buf, int length) 构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length)
DatagramPacket(byte[]buf,int offset,int length,SocketAddressaddress) 构造⼀个DatagramPacket以⽤来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定⻓度(第⼆个参数length)。address指定⽬的主机的IP
和端⼝号

DatagramPacket  ⽅法:

⽅法签名 ⽅法说明
InetAddress getAddress() 从接收的数据报中,获取发送端主机IP地址;或从发
送的数据报中,获取接收端主机IP地址
int getPort()  从接收的数据报中,获取发送端主机的端⼝号;或从
发送的数据报中,获取接收端主机端⼝号
byte[] getData()  获取数据报中的数据

InetSocketAddress

InetSocketAddress ( SocketAddress 的⼦类)构造⽅法:

⽅法签名 ⽅法说明
InetSocketAddress(Inet Addressaddr,int port) 创建⼀个Socket地址,包含IP地址和端⼝号

常见的客户端服务端模型

此处的客户端在发送请求的时候作为发送端

在接收返回响应的时候作为接收端

所以我们说发送端和接收端是一个相对的概念

使用UDP协议实现回显服务器

此时我们想基于UDP实现一个回显服务器,所谓的回显服务器就是客户端发送什么内容,接收端就响应什么内容,下面我们开始具体实现

服务器

对于服务器来说,第一步是要创建一个DatagramSocket对象,因为接下来我们是要操作网卡的,操作网卡是通过socket对象来实现的,你可以认为网卡这个硬件设备被抽象成一个类,实际上操作系统内核将它抽象成文件的形式来表示,socket对象是在内存中的,这里我们只需要针对内存操作就可以影响到网卡

通过网卡发送数据,就是写socket文件

通过网卡接收数据,就是读socket文件

UDP面向数据报,每次发送和接收的基本单位就是一个DatagramPacket

注:服务器一定要绑定一个端口号

端口号:区分不同应用程序的依据,一个进程可以绑定多个端口,一个端口只能被一个进程绑定

public class Udp_server {
    private DatagramSocket socket = null;
    public Udp_server(int port) throws SocketException {
        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. 把响应返回到客户端.
            //    构造一个 DatagramPacket 作为响应对象
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 打印日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        Udp_server server = new Udp_server(9090);
        server.start();
    }
}

客户端

相对来说,这里客户端和服务器的代码就很像了,有一点不同,就是客户端不需要手动指定端口号,这里就是系统自动分配一个空闲的端口号,而服务器的端口往往是固定不变的,服务器用空闲的端口即可.

客户端构造的时候需要指定服务器的端口和ip

注:服务器实在程序员手里的,是可控的,而客户端是在用户手里的,假设你固定端口为8080,但是此时用户的8080端口被其他的应用程序所占用了,这时候程序就g了呀

public class UDP_Client {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;
    // 此处 ip 使用的字符串, 点分十进制风格. "192.168.2.100"
    public UDP_Client(String serverIp, int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 要做四个事情
            System.out.print("-> "); // 表示提示用户接下来要输入内容.
            // 2. 从控制台读取要发送的请求数据.
            if (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 3. 构造请求并发送.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            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 {
        UDP_Client client = new UDP_Client("127.0.0.1", 9090);
        client.start();
    }
}

流程梳理

1.服务器启动,启动之后进入while循环,执行到receive进入阻塞状态,此时客户端还没发来任何请求

2.客户端启动,启动之后进入while循环,执行到hasNext,进入阻塞,此时用户没有输入内容

3.用户输入字符串,按下回车,此时next返回内容,构造一个DatagramPacket进行发送send

send执行完毕之后,继续执行到receive阶段,等待服务器的响应数据

4.服务器接收到请求后从receive的阻塞中返回

返回之后根据读到的DatagramPacket,通过process方法构造返回的字符串,再根据返回的字符串构造响应对象DatagramPacket,再进行发送给客户端

在此过程中,客户端也是在阻塞等待的

5.客户端从receive中执行返回,就能得到对应的响应,打印到控制台上,此时再次等待用户输入请求

使用TCP方式实现一个回显服务器

TCP是有链接的,就和打电话一样,需要客户端拨号,服务器来接听

ServerSocket类只能给服务器进行使用,同样抽象成网卡

Socket类对应到网卡,既可以给服务器来使用,也可以给客户端来使用

当服务器这里调用了ServerSocket的accept()方法,,服务器的内核就会配合客户端的工作,来完成连接的建立,这个建立的过程就类似于电话拨号,电话响铃,一直到另一方接通,才能进行后续的通信.在没有客户端来连接的时候,accept方法会进行阻塞,当有多个客户端来连接的时候,accept方法就会执行多次

注:TCP是有连接的,TCP socket就会保存对端的信息

所以这里客户端也无需保存对端的信息了

长短连接

长连接:一次连接服务器与客户端进行多次交互

短连接:一次服务器和客户端连接只进行一次交互

客户端程序

public class TCPEchoClient {
    private Socket socket= null;
    public TCPEchoClient(String serverIP, int port) throws IOException {
        socket = new Socket(serverIP,port);
        //由于TCP是有连接的,所以IP和端口会保存好
        //因此TCPEchoClient就不必保存
    }
    public void start() {
        System.out.println("客户端启动");
        //和UDP类似
        //1.从控制台读取
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerConsole = new Scanner(System.in);
            Scanner scannerNetwork = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true) {
                System.out.print("->");
                if(!scannerConsole.hasNext()) {
                    break;
                }
                String request = scannerConsole.next();
                writer.println(request);
                //2.请求发给服务器
                //确保这里发送出去了
                writer.flush();
                //3.读取响应
                String response = scannerNetwork.next();
                //4.把响应显示出来
                System.out.println(response);
            }
        } catch (IOException e) {
        }
    }
    public static void main(String[] args) throws IOException {
        TCPEchoClient client = new TCPEchoClient("127.0.0.1",9090);
        client.start();
    }
}

服务器程序

public class TCPEchoServer {
    private ServerSocket serverSocket = null;
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public  void  start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true) {
            //通过accept来接听电话,才能通信(置业顾问)
            Socket clientSocket = serverSocket.accept();
//            Thread t = new Thread(()->{
//                processConnection(clientSocket);
//            });
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //循环读取客户端的请求并返client
        try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream OutputStream = clientSocket.getOutputStream()){
            //使用Scanner就不使用read了
            Scanner scanner = new Scanner(inputStream);
            while(true) {
                //使用inputStream
                if(!scanner.hasNext()) {
                    //读取完毕,比如客户端断开连接就算读取完毕
                    System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //读取请求并解析,这里注意隐藏的约定,客户端发来的请求必须带有空白符作为结尾
                String request = scanner.next();
                //计算请求
                String response = process(request);
                //返回给客户端
                //这种方式可以写会但不方便给返回的响应添加换行
//                OutputStream.write(response.getBytes(StandardCharsets.UTF_8),0,response.getBytes().length);
                //也可以给OutputStream套上一层
                //将输出流包装一下,这里的操作也就是将字节流转换为字符流
                PrintWriter printWriter = new PrintWriter(OutputStream);
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req %s,resp : %s\n",clientSocket.getInetAddress(),clientSocket.getPort(),
                        request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally{
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TCPEchoServer server = new TCPEchoServer(9090);
        server.start();
    }
}

此时客户端的用户态应用程序调用一定的api和服务器尝试建立连接,客户端就会发起建立连接的流程,服务器就会配合这里客户端的工作来完成连接的建立

这里内核中的连接并不是决定性的,还需要用户程序进行接听accept操作,才能进行后续的操作

流程梳理

1.服务器启动,阻塞在accept,等待客户端发送数据

2.客户端new socket的操作就是建立了连接

3.服务器从accept返回,进入到processConnection方法中,执行到hasNext之后,等待客户端传输数据,

4.客户端执行到hasNext阶段,等待用户从控制台输入数据

5.用户输入完了数据之后,进行发送

6.服务器读取到数据之后进行处理,然后包装成响应发送给客户端

7.客户端读取到响应并且打印在控制台上

服务器细节补充

这里我们注意到,每接收到一个客户端都会创建一个socket对象,这就会消耗我们的文件描述符表,所以一定要在结束后关闭,要写在finally里面,不然给文件描述符表给占满了程序就崩溃了

serverSocket不能关闭的原因是这个对象的生命周期跟随程序始终,而且只有一个,最后会随着程序的关闭自动销毁.

注:这里的两层while循环会导致服务器一次只能处理一个客户端的请求,这里我们可以采用IO复用,协程,线程池等方案修改即可

IO多路复用:基本在于虽然有多个socket,但是同一时刻活跃的socket只是少数,大部分socket是在等待,就造成了一个线程等待多个socket 的现象 效率相对不低的同时系统开销也不高

不像多线程,虽然效率很高,但是也大大增加了系统开销

相关文章
|
12天前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
14 1
|
15天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
15 1
|
21天前
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
|
24天前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
1月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
|
2月前
|
网络协议
关于套接字socket的网络通信。&聊天系统 聊天软件
关于套接字socket的网络通信。&聊天系统 聊天软件
|
4月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
51 4
|
4月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
32 3
|
4月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
63 2
|
4月前
|
网络协议 程序员 视频直播
下一篇
无影云桌面