关于网络编程

目录

1、InetAdress类

2、Socket套接字

3、UDP数据报套接字编程 

(1)DatagramSocket 类

 (2)DatagramPacket类

 (3)处理无连接问题

UdpEchoServer.java

UdpEchoClient.java

4、TCP流套接字编程

(1)工作流程

Server.java

Client.java 

(2)改进

Server.java

Client.java

 运行结果

(3)相关解析

socket.close()

scanner.next()的工作原理

hasNext()工作原理

Ideal同时运行多个进程设置

 服务器同时处理数个客户端请求


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

1、InetAdress类

Java语言提供了InetAdress类,该类用于处理IP地址主机名称(hostname)的获取

这个类并没有提供构造函数,必须利用该类的静态方法来创建对象

static InetAdressgetByName(String host)
static InetAdress[ ]getAllByName(String host)
static InetAdressgetByAdress(byte[ ] addr)
static InetAdressgetByAdress(String host , byte[ ] addr)
static InetAdressgetLocalHost()返回本地主机的地址
static InetAdressgetLoopbackAdresst()返回回送地址

另外还有些非静态方法用于获取InetAdress对象的信息:

返回值方法名返回数据
byte[ ]getAdress()原始IP地址
String getHostAdress()IP地址字符串
String getHostName()主机名
String getCanonicalHostName()获取此IP地址的完全限定域名

文章链接分享:主机与域名

2、Socket套接字

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

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

针对传输层协议可以将网络编程分为两类:

(1)流套接字:TCP(传输控制协议)网络通信

        有连接、可靠、面向字节流、有接收/发送缓冲区的、大小不限、全双工

        (适用于需要可靠数据传输的场景,如HTTP、FTP等协议的通信)

(2)数据报套接字:UDP(用户数据报协议)网络通信

        无连接、不可靠、面向数据报、全双工

        (适用于不需要可靠传输,但对实时性有要求的场景,如视频会议、在线游戏等)

有无连接:

通信双方不需要建立连接,可以直接发送数据报

而这里使用Socket进行通信时,双方必须先建立一个连接才能进行数据的发送和接收

面向字节流:即传输数据基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次发送

面向数据报:即传输数据是一块一块的,发送一块数据加入100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节

3、UDP数据报套接字编程 

这里要用到2个类:DatagramSocket类、DatagramPacket类

(1)DatagramSocket 类

        此类表示用于发送和接收数据报数据包的套接字

相关方法:

void receive(DatagramPacket p)从此套接字接收数据报(阻塞直到真正接收到数据报)
void send(DatagramPacket p)从此套接字发送数据报
void close()
 (2)DatagramPacket类

上述DatagramSocket 类中我们发现往这个Socket通道中发送接收都是以一个UDP数据报为单位进行的

但是这个数据报DatagramPacket内部是不能自行分配内存空间,需要手动创建空间,需要通过构造方法往里面放一个字节数组

 (3)处理无连接问题

我们知道UDP是无连接的,意味着服务器与客户端双方并不存储对方的地址与端口号,但是若双方都不知道对方在哪儿,这就传输通信不了了呀!

        银行(服务端)是不需要知道取钱的人(客户端)的地址的,但客户端要知道银行的地址才能找上门请求服务的,所以客户端这边是需要记下指定服务端的地址的。

        但若你还请求了办理证件这种服务,是需要后期邮寄上门的,那么你在请求服务时就会把地址告诉银行。

按照这样的逻辑我们来看下在UDP这里是如何处理无连接问题的

既然UDP自身无法记住,那就由我们另外记录下来

客户端把服务端的地址、端口设置为成员变量,在构造方法中进行赋值,这样就记录了服务端的地址与端口,那么在下面一些列请求中就可以使用了(相当于记下了) 

public UdpEchoClient(String ip,int port) throws SocketException {socket =new DatagramSocket();serverIp=ip;serverPort=port;
}public static void main(String[] args) throws IOException {UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090);client.start();
}

而服务端只需要给自己指定端口号即可(给自己定个固定地址,让客户端找上门服务)

 private DatagramSocket socket=null;public UdpEchoServer(int port) throws SocketException {socket=new DatagramSocket(port);}

我们可以发现服务端的端口号是指定的固定的,但是客户端的Socket方法却是让系统自动随机分配一个空闲端口。

原因很简单,试想一下,若同时有数个相同地址相同端口的客户端向同一个服务端发起请求,相同个端口无法区分,那么服务端处理谁呢?所以由系统自动随机分配一个空闲端口可以避免端口冲突!

UdpEchoServer.java

下面我们来写一个简单的回显服务器:(收到什么就返回什么)

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 requestPacket=new DatagramPacket(new byte[4096],4096);//2、接收并解析数据报socket.receive(requestPacket);String request=new String(requestPacket.getData(),0,requestPacket.getLength());//3、计算响应并返回给客户端String response=process(request);DatagramPacket responsePacket=new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//4、打印日志记录此次数据交互的详情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 {UdpEchoServer server=new UdpEchoServer(9090);server.start();}
}
UdpEchoClient.java
public class UdpEchoClient {private DatagramSocket socket=null;private String serverIp="";private int serverPort=0;public UdpEchoClient(String ip,int port) throws SocketException {socket =new DatagramSocket();serverIp=ip;serverPort=port;}public void start() throws IOException {System.out.println("客户端启动!");Scanner scanner=new Scanner(System.in);while (true){//1、从控制台读取数据作为请求System.out.println("请输入要发送的数据");String request=scanner.next();//2、发送请求数据报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);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();}
}

上述只是一个简单的回显服务,若你还想要一个处理复杂点的服务的服务端可以直接继承回显服务端再重写相关方法。比如下面是一个翻译服务:

public class UdpDictServer extends UdpEchoServer{private Map<String,String> dict=new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);//此处可以往这个表里插入几千几万个这样的英文单词dict.put("dog","小狗");dict.put("cat","小猫");dict.put("pig","小猪");}//重写process方法,在重写的方法中完成翻译的过程(翻译的本质就是”查表“)@Overridepublic String process(String request) {return dict.getOrDefault(request,"该词在词典中不存在!");}public static void main(String[] args) throws IOException {UdpDictServer server=new UdpDictServer(9090);server.start();}
}

4、TCP流套接字编程

用到两个关键的类:

  • SeverSocket类:用于创建服务器绑定端口
  • Socket类:该类实现客户端套接字,套接字是两台机器之间通讯的端点
(1)工作流程

UDP中是各自有一个DatagramSocket类,客户端手动存储服务端的地址与端口,通过数据报发送请求到服务端,服务端就可以接收数据报中的请求、客户端的地址端口,这样就又可以通过数据报把响应返回给客户端,客户端就获得响应,完成网络通信。

这个TCP可就牛杯了。

服务端通过ServerSocket创建服务器绑定端口,等待接收客户端在内核已建立好的连接对象。

客户端Socket s=new Socket(String serverIP,serverPort)就可以与服务端建立连接,服务端这边就能直接获得连接对象Socket对象,就可以直接对Socket对象进行一系列操作(读取请求、写入响应)。

也就是说这下服务端和客户端都是直接对同一个Socket对象进行操作!双方都用这一个Socket对象获取字节输入输出流,这样我都可以直接对同一个socket进行读写了!

    Socket是操作系统中的一个概念,本质上是一种特殊的文件,Socket就属于是把“网卡”这个设备给抽象成文件了。往Socket中写数据,就相当于通过网卡发送数据;从socket文件读数据就相当于通过网卡接收数据。

按照流程来一一梳理下连接完之后双方交互的过程

  1. 客户端 out.write(request)写入请求;
  2. 服务端 in.read()读取请求,服务端 out.write()写入响应;
  3. 客户端 in.read()读取响应

根据这样的流程先简单模拟下:

Server.java
public class Server {public static void main(String[] args) throws IOException {ServerSocket serverSocket=new ServerSocket(9088);System.out.println("服务端上线");while (true){Socket socket = serverSocket.accept();System.out.println("客户端连接成功");try(InputStream in = socket.getInputStream();OutputStream out=socket.getOutputStream()) {byte buf[]=new byte[1024];int n = in.read(buf);String request=new String(buf,0,n);String response="hello";out.write(response.getBytes());}finally {socket.close();System.out.println("客户端下线");}}}
}
Client.java 
public class Client {public static void main(String[] args) throws IOException {Socket socket=new Socket("127.0.0.1",9088);try(InputStream in = socket.getInputStream(); OutputStream out=socket.getOutputStream()){String request="hello";out.write(request.getBytes());byte buf[]=new byte[1024];int n = in.read(buf);String response=new String(buf,0,n);System.out.println (new String(buf,0,n));}}
}

客户端连续连接两次运行结果如下:

(2)改进
Server.java
public class Server {ServerSocket server=null;public Server(int port) throws IOException {server=new ServerSocket(port);System.out.println("服务端上线");}public void start() throws IOException {while (true){Socket socket=server.accept();System.out.printf("[%s:%d] 客户端上线! \n",socket.getInetAddress(),socket.getPort());Thread t=new Thread(()->{try {processConnection(socket);} catch (IOException e) {throw new RuntimeException(e);}});t.start();}}private void processConnection(Socket socket) throws IOException {try(InputStream in=socket.getInputStream();OutputStream out=socket.getOutputStream()){Scanner sc=new Scanner(in);PrintWriter pw=new PrintWriter(out);while (true){if (!sc.hasNext()){//若没有内容则进入阻塞等待输入内容;若有内容则继续循环处理请求//若客户端断开连接(下线),sc就知道不会再有输入内容,就进入if()跳出循环,请求处理完毕,此线程结束System.out.printf("[%s:%d] 客户端下线! \n",socket.getInetAddress(),socket.getPort());break;}String request=sc.next();String response=process(request);pw.println(response);//服务端写入时末尾加了“\n”,作为客户端读取结束标志pw.flush();System.out.printf("[%s,%d] req=%s resp=%s \n",socket.getInetAddress(),socket.getPort(),request,response);}}finally {socket.close();}}private String process(String request){return request;}public static void main(String[] args) throws IOException {Server server=new Server(9789);server.start();}
}
Client.java
public class Client {Socket socket=null;public Client(String serverIp,int serverPort) throws IOException {socket=new Socket(serverIp,serverPort);}public void start() throws IOException {Scanner scanner=new Scanner(System.in);try(InputStream in = socket.getInputStream(); OutputStream out=socket.getOutputStream()){Scanner sc=new Scanner(in);PrintWriter pw=new PrintWriter(out);while (true){System.out.println("请输入请求->");String request= scanner.next();pw.println(request);//客户端写入时末尾加了“\n”,作为服务端读取结束标志pw.flush();String response=sc.next();System.out.printf("返回响应:");System.out.println(response);}}}public static void main(String[] args) throws IOException {Client client=new Client("127.0.0.1",9789);client.start();}
}
 运行结果

(3)相关解析
socket.close()

前面写过的DatagramSocket、ServerSocket都没有close(),因为这两个在整个程序中都只有一个对象,贯穿整个程序,一旦程序结束这两个对象都会自动被销毁,不存在资源泄漏的问题

但是这里服务端接收到的Socket对象则是在循环中每有一个新的客户来建立连接都会创建一个的

并且这个对象最多时用到该客户退出(断开连接)。此时若有很多个客户都来建立连接就意味着会创建很多个Socket对象,当连接断开此时这个对象就会占据着文件描述表的位置。

而在上面的  

try(InputStream in = socket.getInputStream();OutputStream out=socket.getOutputStream())

只是关闭了Socket对象上自带的流对象,而并没有关闭Socket对象本身

scanner.next()的工作原理

用于读取输入,直到遇到空格、Tab键或Enter键等分隔符为止,返回的是内容是连续的有效字符序列,而不包含任何前导或尾随的空白字符

scanner.next()方法提供了一个简单的方式来读取和处理以空白字符分隔的字符串输入 

        比如输入"abc xyz",则会读取并返回"abc",接着若再次调用next()它将返回"xyz"

        在这个过程中,输入的空格被next()视为分隔符,从而确保了每次调用next()时都会返回一段完整的数据

 

hasNext()工作原理

        用来判断缓冲区内是否还有内容,若还有内容,则返回true;若没有内容,不会返回false,而是堵塞当前程序,并且等待输入内容 

Ideal同时运行多个进程设置

 再点击右上角三角形运行图标就可以多个进程运行同一个代码了

  

 服务器同时处理数个客户端请求

上面的代码我们给出的解决方案是每来一个客户端就新创建一个线程处理请求,这样的解决办法在数个客户端请求的情况下,会频繁地来进行建立、断开连接,会导致服务器频繁地创建、销毁线程,这样的开销是巨大的!

为了进一步优化,我们可以使用线程池、协程、或者I/O多路复用等手段。

由于作者只会线程池,所以这里就写下线程池:

 public void start() throws IOException {ExecutorService service= Executors.newCachedThreadPool();while (true){Socket socket=server.accept();System.out.printf("[%s:%d] 客户端上线! \n",socket.getInetAddress(),socket.getPort());service.submit(new Runnable() {@Overridepublic void run() {try {processConnection(socket);} catch (IOException e) {throw new RuntimeException(e);}}});}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/846837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么我觉得C/C++好简单?

有几个可能的原因解释为什么您觉得C/C简单。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 适应性&#xff1a;如果…

气膜建筑的膜材更换与维护—轻空间

气膜建筑作为一种新型建筑形式&#xff0c;因其独特的优势和广泛的应用而受到关注。膜材是气膜建筑的核心组成部分&#xff0c;其质量和维护状况直接影响到建筑的使用寿命和性能。本文将详细探讨气膜建筑的膜材使用寿命、维护及更换的重要性。 膜材的使用寿命 气膜建筑的膜材通…

软件设计详细需求分析报告-word(直接套用实际文档)

第3章 技术要求 3.1 软件开发要求 第4章 项目建设内容 第5章 系统安全需求 5.1 物理设计安全 5.2 系统安全设计 5.3 网络安全设计 5.4 应用安全设计 5.5 对用户安全管理 5.6 其他信息安全措施 第6章 其他非功能需求 6.1 性能设计 6.2 稳定性设计 6.3 安全性设计 6.4 兼容性设计…

【随笔】Git 实战篇 -- 开心 commit 之后,发现有一处bug还需要改,只能 reset 撤销然后再次提交 -- git reset --(四十三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

Tween.js在Three.js中的应用:为3D动画添加流畅过渡

前言 在Web开发领域&#xff0c;Three.js已经成为构建精彩3D内容的首选库之一。它让开发者能够轻松地在浏览器中创建和展示复杂的3D场景。然而&#xff0c;要让这些场景栩栩如生&#xff0c;平滑的动画效果是必不可少的。这就引入了Tween.js——一个轻量级但功能强大的JavaScr…

Redis 持久化: RDB和AOF

文章目录 ⛄1.RDB持久化&#x1fa82;&#x1fa82;1.1.执行时机&#x1fa82;&#x1fa82;1.2.RDB原理&#x1fa82;&#x1fa82;1.3.小结 ⛄2.AOF持久化&#x1fa82;&#x1fa82;2.1.AOF原理&#x1fa82;&#x1fa82;2.2.AOF配置&#x1fa82;&#x1fa82;2.3.AOF文件…

CRM系统主要是干什么?CRM系统主要功能和作用

什么是CRM 系统&#xff1f;CRM系统到底是干什么的&#xff1f;不同的企业人员该如何利用CRM去解决他们的问题等等&#xff0c;问题太多了&#xff0c;今天来为大家详细介绍。 干货满满&#xff0c;建议收藏&#xff01;&#xff01; 首先第一个问题&#xff0c;什么是CRM系统…

Linux 服务查询命令(包括 服务器、cpu、数据库、中间件)

Linux 服务查询命令&#xff08;包括 服务器、cpu、数据库、中间件&#xff09; Linux获取当前服务器ipLinux使用的是麒麟版本还是cenos版本Linux获取系统信息Linux查询nignx版本 Linux获取当前服务器ip hostname -ILinux使用的是麒麟版本还是cenos版本 这个文件通常包含有关L…

ctfshow jwt web入门

jwt令牌由三部分组成&#xff0c;由.分割 Header Payload Signatureheader示例 {typ: JWT,alg: HS256 }# typ&#xff1a;声明类型 # alg&#xff1a;声明加密的算法 通常直接使用 HMAC SHA256 需要注意的是因为header部分是固定的所以&#xff0c;生成的base64也是固定的以ey…

20、matlab信号波形生成:狄利克雷函数、高斯脉冲和高斯脉冲序列

1、狄利克雷函数生成波形diric()函数 语法&#xff1a;y diric(x,n) 返回n次的狄利克雷函数对输入数组x的元素求值。 1&#xff09;diric()函数 代码 x linspace(-2*pi,2*pi,301);%定义x取值 d6 diric(x,6); d7 diric(x,7); subplot(2,1,1) plot(x,d6) ylabel(n 6) tit…

湖南源点(市场研究)咨询 有效的市场调研是商业定位的基础

本文由湖南&#xff08;市场调研&#xff09;源点咨询编辑发布 近20年&#xff0c;中国购物中心井喷式的发展&#xff0c;经营面积几何倍的增长&#xff0c;但在现今竞争如此激烈的商业环境中&#xff0c;消费者的消费信心不足&#xff0c;购物中心同质化严重&#xff0c;经营…

flyfish3.0.0配置避坑

1.基础环境准备篇 doc/01-基础环境准备篇.md 云智慧/FlyFish - Gitee.com 使用教程里给出的java环境时&#xff0c;可以显示java版本&#xff0c;但是不能显示Maven的版本 改为&#xff1a; export NODE_HOME/usr/local/node/node-v14.19.3-linux-x64 export PATH$NODE_HOME…

100页2秒?我们为什么需要这样的文档解析速度

近期&#xff0c;TextIn通用文档解析完成最新一版产品迭代&#xff0c;将100页文档解析速度提升至最快2秒以内。 P50&#xff08;百页&#xff09; P90&#xff08;百页&#xff09; P95&#xff08;百页&#xff09; P99&#xff08;百页&#xff09; 平均&#xff08;单页…

记某网关系统通用漏洞的挖掘

前言 本篇文章分享一下通用漏洞挖掘的过程&#xff0c;想要获得通用漏洞证书&#xff0c;首先要求是中危及中危以上的通用型漏洞且所属公司的注册资本大于5000万。挖掘一个漏洞其实不难&#xff0c;个人觉得是目标公司资产的搜集。访问CNVD平台发现某网关系统被师傅提交过任意…

V神的傲慢与偏见

原创 | 刘教链 隔夜BTC&#xff08;比特币&#xff09;小幅回升至5日线67.7k附近。前日5.31教链内参“美核心通胀放缓&#xff0c;利好宽松周期落地”[链接]提到&#xff0c;以太坊创始人Vitalik Buterin&#xff08;V神&#xff09;新发表了一篇长文&#xff0c;主题是关于他“…

H6911 DC2.6-40V升压IC 升24V36V48V60V80V100V10A数转模无频闪LED芯片

H6911 DC2.6-40V升压IC是一款升压恒流LED恒流驱动器&#xff0c;具有多种特点&#xff0c;适用于多种的LED照明应用领域。以下是关于该产品的详细解释&#xff1a; 一、产品概述 H6911是一款专为LED照明设计的升压恒流驱动器。它能在2.6至40V的宽电压范围内稳定工作&#xff0c…

性能优化随笔(一)

在软件开发过程中&#xff0c;一般要先实现功能方面的需求&#xff0c;功能方面的需求开发完毕之后&#xff0c;往往会考虑性能方面的优化。在业务发展的初期&#xff0c;性能往往能满足使用的需求&#xff0c;这时性能优化不是必不可少的。随着业务的发展&#xff0c;软件复杂…

Window11开放端口

&#xff08;1&#xff09;打开控制面板&#xff0c;进入【控制面板\系统和安全\Windows Defender 防火墙】 &#xff08;2&#xff09;点击左侧菜单【高级设置】&#xff0c;进入防火墙设置页面 &#xff08;3&#xff09;根据需要选择【入站规则】或者【出站规则】&#xff…

粒子群算法Java实现

粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种受到自然界群体行为启发的优化算法&#xff0c;由James Kennedy和Russell Eberhart于1995年提出。该算法模拟了鸟类或其他动物群体&#xff08;如鱼群&#xff09;的社会和集体行为&#x…

C++:特殊类设计和四种类型转换

一、特殊类设计 1.1 不能被拷贝的类 拷贝只会放生在两个场景中&#xff1a;拷贝构造函数以及赋值运算符重载&#xff0c;因此想要让一个类禁止拷贝&#xff0c;只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98&#xff1a; 1、将拷贝构造函数与赋值运算符重载只…