当前位置: 首页 > news >正文

网络编程套接字(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() {@Overridepublic void run() {processConnection(clientSocket);}});}}private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());//循环读取客户端的请求并返clienttry(InputStream inputStream = clientSocket.getInputStream();OutputStream OutputStream = clientSocket.getOutputStream()){//使用Scanner就不使用read了Scanner scanner = new Scanner(inputStream);while(true) {//使用inputStreamif(!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 的现象 效率相对不低的同时系统开销也不高

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

相关文章:

网络编程套接字(Socket)

为什么需要网络编程??? -丰富的网络资源 每天你在b站上刷着喜欢的up主的视频,实质是通过网络,获取到网络上的一个视频资源 与本地打开文件类似,只是视频文件这个资源来源是网络 所谓的网络编程,其实就是从网络上获取各种数据资源 什么是网络编程?? 网络编程,指的是网络…...

C语言第十一弹---函数(下)

​ ✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 函数 1、嵌套调用和链式访问 1.1、嵌套调用 1.2、链式访问 2、函数的声明和定义 2.1、单个文件 2.2、多个文件 2.3、static 和 extern 2.3.1、static…...

Unity读书系列《Unity3D游戏开发》——拓展编辑器(一)

文章目录 前言一、扩展Project视图1、右键扩展菜单(Asset)2、监听事件3、拓展布局 二、扩展Hierarchy视图1、拓展菜单(GameObject)2、拓展布局3、重写菜单 三、扩展Inspector视图1、扩展原生组件2、扩展继承组件 四、扩展Scene视图…...

【Git】项目管理笔记

文章目录 本地电脑初始化docker报错.gitignoregit loggit resetgit statusgit ls-filesgit rm -r -f --cached拉取仓库文件更新本地的项目报错处理! [rejected] master -> master (fetch first)gitgitee.com: Permission denied (publickey).error: remote origin already e…...

中文词性标注工具pkuseg例子(运行结果,不太好)

pkuseg_demo.md pkuseg 预训练模型 预训练模型science 安装 pip3 install pkuseg cd /rot/pkuseg_home/model/wget https://github.com/lancopku/pkuseg-python/releases/download/v0.0.25/science.zip uzip science.zip -d ./science/ ls /rot/pkuseg_home/model/science/…...

获取URL参数:split方法、URLSearchParams方法示例

在JavaScript中,可以使用多种方法来获取URL参数,其中常用的方法有split()和URLSearchParams()。 使用split()方法获取URL参数: split()方法将字符串分割成数组。可以使用split()方法将URL分割成协议、主机、路径和查询字符串等部分。然后可…...

SparkSql---用户自定义函数UDFUDAF

文章目录 1.UDF2.UDAF2.1 UDF函数实现原理2.2需求:计算用户平均年龄2.2.1 使用RDD实现2.2.2 使用UDAF弱类型实现2.2.3 使用UDAF强类型实现 1.UDF 用户可以通过 spark.udf 功能添加自定义函数,实现自定义功能。 如:实现需求在用户name前加上"Name:…...

系统架构15 - 软件工程(3)

软件过程模型 瀑布模型特点缺点 原型化模型特点两个阶段不同类型注意 螺旋模型V 模型特点 增量模型特点 喷泉模型基于构件的开发模型(CBSD)形式化方法模型敏捷模型特点“适应性” (adaptive) 而非“预设性” (predictive)“面向人的” (People-oriented) 而非“面向过程的” (P…...

两个近期的计算机领域国际学术会议(软件工程、计算机安全):欢迎投稿

近期,受邀担任两个国际学术会议的Special session共同主席及程序委员会成员(TPC member),欢迎广大学界同行踊跃投稿,分享最新研究成果。期待这个夏天能够在夏威夷檀香山或者加利福尼亚圣荷西与各位学者深入交流。 SERA…...

(二十一)Flask之上下文管理第二篇(细细扣一遍源码)

每篇前言: 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入…...

Java项目:基于SSM框架实现的企业员工岗前培训管理系统(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm821基于ssm框架实现的企业员工岗前培训管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格…...

深入了解Redis:选择适用于你的场景的持久化方案

自然语言处理的发展 文章目录 自然语言处理的发展强烈推荐前言:Redis提供了几种主要的持久化方案:RDB快照持久化:工作原理: AOF日志文件持久化:混合持久化: 总结强烈推荐专栏集锦写在最后 强烈推荐 前些天…...

【Git配置代理】Failed to connect to github.com port 443 问题解决方法

前言: 在学习代码审计时,有时会需要使用git去拉取代码,然后就出现了如下错误 看过网上很多解决方法,觉得问题的关键还是因为命令行在拉取/推送代码时并没有使用VPN进行代理。 解决办法 : 配置http代理:…...

python提取word文档内容的示例

一、微软Word历史、背景: Word 的特异功能就是把那些应该写成简单的 TXT 或 PDF 格式的文件,变成了既大又慢且难以打开的怪兽,它们经常在系统切换和版本切换中出现格式不兼容,而且因为某些原因在文件内容已经定稿后仍处于可编辑的…...

MarkDown快速入门-以Obsidian编辑器为例

直接上图,左右对应。 首先是基础语法。 # 标题,几个就代表几级标题;* 单个是序号,两个在一起就是斜体;- [ ] 代表任务,注意其中的空格; 然后是表格按钮代码 | 使用中竖线代表表格&#xff0c…...

【计算机网络】协议,电路交换,分组交换

定义了在两个或多个通信实体之间交换的报文格式和次序,以及报文发送和/或接收一个报文或其他事件所采取的动作.网络边缘: 端系统 (因为处在因特网的边缘) 主机 端系统 客户 client服务器 server今天大部分服务器都属于大型数据中心(data center)接入网(access network) 指将端…...

加速应用开发:低代码云SaaS和源码交付模式如何选

随着数字化转型的加速,企业对于快速开发和交付高质量应用的需求也越来越迫切。为了满足这一需求,开发者们开始探索采用低代码平台进行软件开发工作,以加速应用开发过程。 目前,市场上的低代码产品众多,但基本可分为简单…...

ATT汇编

指令后缀 AT&T格式的汇编指令有不同的后缀 其中 b表示byte,字节 w表示word,字/两字节 l表示long,32位系统下的long是4字节 q表示quad,意味四重,表示4个字/8字节 寄存器用途 参见 AT&T的汇编世界 - Gemfield…...

java split 拆分字符串

今天突然把java里split 跟,kotlin中的split 弄混了 kotlin中split 的用法跟python 中的split 用法是一样的,java中由于返回值是String[] 的数组,所以 在使用的时候需要注意下返回值如果要获取里面的内容,还是需要遍历下里面的内…...

【InternLM 大模型实战】作业与笔记汇总

笔记1:https://blog.csdn.net/weixin_42567071/article/details/135375937 笔记2:https://blog.csdn.net/weixin_42567071/article/details/135423120 作业2:https://github.com/xiaomile/InternLM-homework/tree/main/%E4%BD%9C%E4%B8%9A1 笔…...

解析PreMaint在石油化工设备预测性维护领域的卓越表现

石油化工行业一直在寻找能够确保设备高效运行的先进维护解决方案。在这个领域,PreMaint以其卓越的性能和创新的技术引起了广泛关注。 一、为何选择预测性维护? 传统的维护方法,基于固定的时间表,无法灵活应对设备的真实运行状况。…...

C++面试宝典第25题:阶乘末尾零的个数

题目 给定一个整数n,返回n!(n的阶乘)结果尾数中零的个数。 示例 1: 输入:3 输出:0 解释:3! = 6,尾数中没有零。 示例 2: 输入:5 输出:1 解释:5! = 120,尾数中有1个零。 解析 这道题主要考察应聘者对于数学问题的分析和理解能力,以及在多个解决方案中,寻求最优…...

PCIE 4.0 Equalizaiton(LTSSM 均衡流程)

1. 均衡 在Tx端有FFE(Feed Forward Equalizer,前馈均衡器);在Rx端有:CTLE(Continuous Time Linear Equalizer,连续时间线性均衡器)和DFE(Decision Feedback Equalizer&a…...

[libwebsockets]lighttpd+libwebsockets支持ws和wss配置方法说明

libwebsockets介绍 libwebsockets是一款轻量级用来开发服务器和客户端的C库。它不仅支持ws,wss还同时支持http与https,可以轻轻松松结合openssl等库来实现ssl加密。 官方参考链接: https://libwebsockets.org/ lighttpd版本 lighttpd/1.4.59 (ssl) - a light and fast w…...

常用软件安装

服务器版本为Centos7.8 x86_64 1.yum下载提速 1.wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 2. yum clean all 3.yum makecache2.jdk yum install java-1.8.0-openjdk* -y # yum update 时自动更新jdk版本 1.yum -y install …...

翻译: GPT-4 Vision静态图表转换为动态数据可视化 升级Streamlit 三

GPT-4 Vision 系列: 翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式二 1. 将任何静态图表转换为动态数据可视化 ChatGPT Vision 不仅可以将涂鸦变成功能齐全的 Streamlit 应用程序,还…...

Qt QPlainTextEdit高亮显示当前行

Qt QPlainTextEdit高亮显示当前行 文章目录 Qt QPlainTextEdit高亮显示当前行摘要错误的代码正确的代码QTextEdit::ExtraSelection 关键字: Qt、 QPlainTextEdit、 QTextBlock、 ExtraSelection、 GPT 摘要 今天要在说一下GPT,当下如果你还不会用G…...

Linux编辑器vim(含vim的配置)

文章目录 前言vim的基本概念vim基本操作进入vim模式切换退出vim vim指令vim命令模式指令vim底行模式命令 简单vim配置 前言 本篇文章,小编将介绍Linux编辑器–>vim以及vim的配置。 vim的基本概念 正常/普通/命令模式(Normal mode) 控制屏幕光标的移动&#xf…...

Oracle DG环境下的秘钥管理

今天有朋友问到1)DG环境下的秘钥管理需要注意什么,2)秘钥管理对DG的日志同步有影响吗? 对于2)的回答是明确的,没有影响。秘钥的管理和DG的redo log shipping完全是两套机制。在最新版的Oracle Key Vault常…...

【Sql Server】新手一分钟看懂在已有表基础上增加字段和说明

欢迎来到《小5讲堂》,大家好,我是全栈小5。 这是《Sql Server》系列文章,每篇文章将以博主理解的角度展开讲解, 特别是针对知识点的概念进行叙说,大部分文章将会对这些概念进行实际例子验证,以此达到加深对…...