基于UDP/TCP实现客户端服务器的网络通信程序

news/2024/4/29 0:59:45/文章来源:https://blog.csdn.net/crazy_xieyi/article/details/128861083

目录:

前言

基于UDP实现客户端服务器的网络通信程序

基于TCP实现客户端服务器的网络通信程序


前言

网络编程的核心是Socket API,它是操作系统给应用程序提供的网络编程API,可以认为是socket api是和传输层密切相关的。在传输层里面,提供了两个最核心的协议,UDP和TCP。因此,socket api 也提供了两种风格(UDP和TCP)。

先来对UDP和TCP做一个简单的总结:

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

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

关于有无连接,可以用打电话和发微信来类比,打电话就是有连接,有无接通可以被感知,而发微信就是无连接,对方是否已经看到消息是无感知的。

关于可靠与不可靠传输,还是可以用打电话和发微信来类比,打电话就是可靠传输,对方接通并回应就是确认收到了,发微信就是不可靠传输,消息发送出去并不知道对方已经确认收到,就是不可靠传输。

面向字节流:数据传输就和文件读写类似,是“流式”的。

面向数据报:数据传输则以一个个的“数据报”为基本单位,一个数据报可能是若干个字节且带有一定格式。

全双工:一个通信通道,可以双向传输,既可以发送也可以接受。比如一根水管,是单向传输的,就是半双工。

基于UDP实现客户端服务器的网络通信程序

使用DatagramSocket这个类,表示一个socket对象。在操作系统中,把这个socket对象也是当成一个文件来处理的,相当于是文件描述符表上的一项。其中普通的文件对应的硬件设备是硬盘,socket文件对应的硬件设备是网卡。一个socket对象就可以和另外一台主机进行网络通信了,如果需要和多个不同的主机通信,则需要创建多个socket对象。

DatagramSocket(): 无参数就是没有指定端口,系统则会自动分配一个空闲的端口

DatagramSocket(int port): 传入了一个端口号,就是让当前的socket对象和这个指定的端口关联起来。从本质上来说,不是进程和端口来建立联系,而是进程中的socket对象和端口建立了联系。

DatagramPacket:表示UDP中传输的一个报文,也是UDP传输数据的基本单位。

receive(DatagramPacket p):此处传入的是一个空的对象,空的报文,receive方法内部会对参数的这个空对象进行内容填充,从而构造出数据。

send(DatagramPacket p): 将构造好的报文发送出去。

下面这里将写一个简单的回显客户端服务器,当然对于服务器来说,对于请求的处理,即业务逻辑的处理是最核心最复杂的,我们这里就回显设置,重点在于理解整个通信过程。

服务器端代码:

public class UdpEchoServer {// 网络编程, 本质上是要操作网卡.// 但是网卡在操作系统内核中,不方便直接操作,使用了"socket" 这样的文件来抽象表示网卡// 因此进行网络通信, 必需要先有一个 socket 对象private DatagramSocket socket = null;// 对于服务器来说, 创建 socket 对象的同时, 要让它绑定上一个具体的端口号,服务器一定要关联上一个具体的端口// 服务器在网络传输中才知道此时客户端的端口, 才能进行通信public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");// 服务器不是只给一个客户端提供服务就完了,需要服务很多客户端,所以需要用到循环while (true){//1.读取客服端发来的请求//receive方法的参数是一个输出型参数, 需要先构造好个空白的 DatagramPacket 对象,交给 receive 来进行填充DatagramPacket requestPack = new DatagramPacket(new byte[4096],4096);socket.receive(requestPack);//把数据拿出来, 构造成一个字符串String request = new String(requestPack.getData(),0,requestPack.getLength());//2.根据请求计算响应,此时为回显服务器,所以写得比较简单,请求和响应相同String response = process(request);//3.把响应写回客户端//send 的参数也是 DatagramPacket,也是需要把这个 Packet 对象构造好//此处构造的响应对象, 不能是用空的字节数组构造了, 而是要使用响应数据来构造DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPack.getSocketAddress());socket.send(responsePacket);//4.打印当前这次请求响应的处理结果System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPack.getAddress().toString(),requestPack.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();}
}

客户端代码:

public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp = null;private int serverPort = 0;// 服务器 ip 和 端口 也需要告诉客户端. 才能顺利把消息发个服务器public UdpEchoClient(String serverIp,int serverPort) throws SocketException {socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);while (true){//1.从控制台读取数据System.out.print("=>:");String request = scanner.next();if (request.equals("exit")){System.out.println("goodBye!");break;}//2.构造UDP请求并发送//构造这个 Packet 的时候, 需要把 serverIp 和 port 都传入过来,此处 IP 地址需要填写的是一个 32位的整数形式.//上述的 IP 地址是一个字符串,需要使用 InetAddress.getByName 来进行一个转换DatagramPacket requestPack = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPack);//3.读取服务器的UDP响应并解析DatagramPacket responsePack = new DatagramPacket(new byte[4096],4096);socket.receive(responsePack);String response = new String(responsePack.getData(),0,responsePack.getLength());//4.打印解析的结果System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);client.start();}
}

通信逻辑顺序图:

基于TCP实现客户端服务器的网络通信程序

TCP提供的API主要是两个类:

ServerSocket:专给服务器使用的Socket对象

Socket:既会给服务器使用,也会给客户端使用。

TCP的传输与UDP有所不同,它并不是以数据报为单位进行传输的,而是以字节的方式,流式传输。

 // 使用这个 clientSocket 和具体的客户端进行交流Socket clientSocket = serverSocket.accept();

accept的作用就是接受连接,客户端在构造Socket对象的时候就会指定服务器的IP和端口,如果没有客户端来连接,此时accept就会阻塞。只要任意一个客户端连接上来,服务器这边都会创建一个Socket对象,每次创建一个Socket对象,就要占用一个文件描述符表的一个位置,因此在使用完毕后要进行释放。前面我们写的UDP就不存在这个情况,一方面是因为UDP中的Socket对象的生命周期是跟随整个程序的,声明周期更长,另一方面是Socket数量不是很多。

一次连接只能处理一个客户端的请求,那么同时有非常多的客户端发出请求,也就是高并发,此时可以用多线程来解决,为了避免线程频繁的创建和销毁,也可以用线程池。

  while (true){// 使用这个 clientSocket 和具体的客户端进行交流Socket clientSocket = serverSocket.accept();//多线程版本/*   Thread t = new Thread(()->{processConnection(clientSocket);});t.start();*///线程池版本threadPool.submit(()->{processConnection(clientSocket);});}

但是对于特别量大的客户端,多线程和线程池也是不行的,可以多开服务器可以解决,但是毕竟成本高,还有一种方式就是IO多路复用。大概逻辑就是:给一个线程安排一个集合,这个集合里面放了一堆连接,这个线程就负责监听这个集合,哪一个连接有数据来了,这个线程就先处理哪个连接。IO多路复用本质上来说,就是充分利用等待的时间来做别的事情。

服务器端代码:

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("启动服务器!");// 此处使用 CachedThreadPool, 使用 FixedThreadPool的时候线程数是固定的,不太合适ExecutorService threadPool = Executors.newCachedThreadPool();while (true){// 使用这个 clientSocket 和具体的客户端进行交流Socket clientSocket = serverSocket.accept();//多线程版本/*   Thread t = new Thread(()->{processConnection(clientSocket);});t.start();*///线程池版本threadPool.submit(()->{processConnection(clientSocket);});}}private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d]客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());// 基于上述 socket 对象和客户端进行通信try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){// 由于要处理多个请求和响应, 也是使用循环来进行while (true){//1.读取请求Scanner scanner =new Scanner(inputStream);if (!scanner.hasNext()){System.out.printf("[%s:%d]客户端下线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());break;}//此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述换行符/空格/其他空白符String request = scanner.next();//2.根据请求构造响应String response = process(request);//3.返回响应结果PrintWriter printWriter = new PrintWriter(outputStream);// 此处使用 println 来写入,让结果中带有一个 \n 换行. 方便对端来接收解析printWriter.println(response);// flush 用来刷新缓冲区, 保证当前写入的数据是真正发送出去了printWriter.flush();//4.打印当前这次请求响应的处理结果System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();}finally {try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

客户端代码:

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp,int serverPort) throws IOException {socket = new Socket(serverIp,serverPort);}public void start(){System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){while (true){//1.从控制台读取数据System.out.print("=>:");String request = scanner.next();if (request.equals("exit")){System.out.println("goodBye!");break;}//2.构造请求并发送PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();//3.读取服务器响应Scanner respScanner = new Scanner(inputStream);String response = respScanner.next();//4.回显System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();}
}

总结

在上面代码中,关于有无连接,面向字节流和面向数据报,以及全双工都能在代码中体现。但是,在TCP中的可靠传输是在代码层面感知不到的。从TCP诞生的意义来说,就是为了解决可靠传输的问题的。关于TCP能够保证可靠传输问题 ,后面再来总结。

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

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

相关文章

想成为年薪30万的自动化测试工程师,你必须突破的几个状态

现阶段的软件测试行业,最火的词莫过于自动化,行业里对自动化测试人才需求量越来越大,薪资越来越高,同时对员工的要求也越来越高。软件测试入行很容易,但是要在这行做好并不容易,行业里工资高的摸不到天花板…

pyqt5:python读取二进制文件(音频PCM文件)显示波形

文章目录1.使用ffmpeg生成PCM文件1.1 用 ffprobe 查看文件信息1.2 用 ffmpeg 命令转换1.3 用ffplay 测试播放PCM文件2.python读取PCM文件显示波形2.1 函数numpy.fromfile2.2 数据类型dtype说明有个项目需要输出10-50Hz的低频信号驱动线圈,考虑使用音频功放硬件&…

JDK9 新特性详解,2017-09-21 正式发布

1、Java9 新特性之目录结构 包含 [jdk8](https://so.csdn.net/so/search?qjdk8&spm1001.2101.3001.7020) 及以前的 jdk 版本,所有目录结构以及目录含义如图:jdk9 之后,目录结构发生变化如图:这个新特性只要了解下就可以了&am…

ChatGPT的火爆出圈,你对它有几分了解?

文章目录1.ChatGPT是什么?2.ChatGPT能做什么?2-1.什么是自然语言模型?3.ChatGPT带来的评价4.了解完ChatGPT之后,你会有什么反思?4-1.为什么微软不自己研发ChatGPT?4-2.Elon Musk为什么退出OpenAI公司&#…

深度学习——注意力机制(笔记+代码)

1.从心理学的角度出发 人类根据随意线索(随着意志,主动的,有意识)和不随意线索(无主动,潜意识)选择注意点 第一眼看到红色咖啡杯比较突出和易见就是潜意识的不随意线索 随着意识想主动读书&…

谁说菜鸟不会数据分析,不用Python,不用代码也轻松搞定

作为一个菜鸟,你可能觉得数据分析就是做表格的,或者觉得搞个报表很简单。实际上,当前有规模的公司任何一个岗位如果没有数据分析的思维和能力,都会被淘汰,数据驱动分析是解决日常问题的重点方式。很多时候,…

TypeScript快速入门

TypeScript快速入门1.TypeScript介绍1.1.TypeScript为什么要为JS添加类型支持1.2.TypeScript相比JS优势2.TypeScript初体验2.1.安装编译TS的工具包2.2.编译并运行TS代码2.3.简化运行TS代码3.TypeScript常用类型3.1.类型注解3.2.常用基础类型3.3.原始类型 number/string/boolean…

MG996R舵机介绍

舵机简介舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。在高档遥控玩具,如飞机、潜艇模型,遥控机器人中已经得到了普遍应用。舵机主要是由外壳、电路板、驱动马达、减速器与位置…

【c语言技能树】文件

Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…

NAS系列 硬件选择

转自我的博客文章https://blognas.hwb0307.com/nas/3224,内容更新仅在个人博客可见。欢迎关注! 前言 经过《NAS系列 为什么你需要一台NAS》的简单介绍,如果你也决定像我一样组装一台自己的NAS,那么就千万不要错过本文喔&#xff…

负载均衡反向代理下的webshell上传+apache漏洞

目录一、负载均衡反向代理下的webshell上传1、nginx 负载均衡2、搭建环境3、负载均衡下的 WebShell连接的难点总结难点一、需要在每一台节点的相同位置都上传相同内容的 WebShell难点二、无法预测下次的请求交给哪台机器去执行。难点三、下载文件时,可能会出现飘逸&…

【3】深度学习之Pytorch——如何使用张量处理表格数据集(葡萄酒数据集)

张量是PyTorch中数据的基础。神经网络将张量输入并产生张量作为输出,实际上,神经网络内部和优化期间的所有操作都是张量之间的操作,而神经网络中的所有参数(例如权重和偏差)也都是张量。 怎样获取一条数据、一段视频或…

Springboot + RabbitMq 消息队列

前言 一、RabbitMq简介 1、RabbitMq场景应用,RabbitMq特点 场景应用 以订单系统为例,用户下单之后的业务逻辑可能包括:生成订单、扣减库存、使用优惠券、增加积分、通知商家用户下单、发短信通知等等。在业务发展初期这些逻辑可能放在一起…

openGL学习之GLFW和GLAD的下载和编译

背景:为什么使用GLFW和GLADOPenGL环境 目前主流的桌面平台是GLFW和GLAD之前使用的GLUT和Free GLUT已经基本淘汰了,所以记录一下如何下载GLFW和GLAD并且编译.GLFW下载:An OpenGL library | GLFW复制到你想存放的位置,我这里就存放到C盘Libaray文件夹下了,这里是我存放…

中国区注册使用ChatGPT指南(OpenAI‘s services are not available in your country)

ChatGPT又火了,各大平台热搜提到手软。暴增的访问量,即使强如ChatGPT,也表示顶不住了。Openai表示服务器已满负荷,ChatGPT暂无法提供服务由于目前ChatGPT未在中国开放,所以国内目前是无法注册使用ChatGPT。但我经过一番…

『 MySQL篇 』:MySQL表的聚合与联合查询

基础篇 MySQL系列专栏(持续更新中 …)1『 MySQL篇 』:库操作、数据类型2『 MySQL篇 』:MySQL表的CURD操作3『 MySQL篇 』:MySQL表的相关约束4『 MySQL篇 』:MySQL表的聚合与联合查询目录一. 聚合查询1.1 聚合函数1.2 GROUP BY子句…

Python将字典转换为csv

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…

MySQL篇02-三大范式,多表查询

数据入库时,由于数据设计不合理,会存在数据重复、更新插入异常等情况, 故数据库中表的设计遵循的设计规范:三大范式1.第一范式(1NF)要求数据库的每一列都是不可分割的原子数据项,即原子性。强调的是列的原子性,即数据库中每一列的…

攀升MaxBook P2电脑U盘重装系统方法教学

攀升MaxBook P2电脑U盘重装系统方法教学。攀升MaxBook P2电脑是一款性价比非常高的笔记本。有用户购买了这款电脑后,想要将系统进行重装。今天和大家分享一个U盘重装系统的方法,学会这个方法后以后就可以自己轻松去重装电脑系统了。接下来一起看看具体的…

相机坐标系的正向投影和反向投影

1 、正向投影: 世界坐标系到像素坐标系 世界3D坐标系(x, y, z) 到图像像素坐标(u,v)的映射过程 (1)世界坐标系到相机坐标系的映射。 两个坐标系的转换比较简单,就是旋转矩阵 平移矩阵,旋转矩阵则是绕X, Y&#xff…