网络编程(UDP\TCP回显服务器)

目录

套接字socket

TCP和UDP特点比较 

特点

比较

UDP回显服务器/客户端的编写

UDP的socket api

回显服务器

客户端

TCP回显服务器/客户端的编写

TCP的socket api

回显服务器

客户端

优化服务器

1.关闭服务器创建的socket对象

2.引入线程池,为多个客户端提供服务 


套接字socket

操作系统提供的网络编程的API称为“socket api”,在传输层中,TCP和UDP两种协议的特点和差异非常大,操作系统中就提供了两套api来分别表示:

流式套接字 -> TCP使用

数据报套接字 -> UDP使用

除此之外操作系统还有其他的关于网络编程的API,比如Unix域套接字,只是本地主机上的进程和进程之间的通信方式,不能跨主机通信,现在很少使用。

TCP和UDP特点比较 

特点

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

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

比较

有连接vs无连接

有连接则是通信双方保存对方的信息,删除信息则断开连接,无连接则是通信双方不需要保存各自信息。在计算机中,各自保存双方的信息,就认为是建立了一个抽象的连接。

可靠传输vs不可靠传输

可靠 != 安全,可靠值要传输的数尽可能的全部传输给对方,在网络通信过程中,可能会存在多种意外情况,比如丢包,丢包的过程是随机的,无法预知。为了对抗丢包,引入了可靠传输特点,TCP具体这一特点,内部提供了一系列机制来实现可靠传输,UDP则是不可靠传输,传输数据时不会关心数据是否到达,接收方是否收到。 

面向字节流vs面向数据报:

TCP和文件操作都是面向字节流的,读写操作非常灵活。UDP面向数据报,传输的基本单位是一个个UDP数据报,每次读写只能读写一个完整的UDP数据报。

全双工vs版双工:

全双工:一条链路能够进行双向通信(TCP/UDP都是),在;一条链路上既可以接收也可以发送。

半双工:一条链路,只能进行单向通信,在Linux中,系统提供的一种软件资源:管道,就是半双工,接收和发送不能同时进行。

UDP回显服务器/客户端的编写

UDP的socket api

java对于系统提供的网络编程api(socket api)进行了进一步封装,进行UDP网络编程代码编写时,需要重点理解两个类:

(1)DatagramSocket:这个类是对操作系统socket概念的封装,系统中的socket可以理解为文件,socket文件可以视为网卡这个设备的抽象表示形式,针对socket文件的读写操作课相当于对网卡这个硬件设备进行读写。其实之前学习的普通文件,就是对硬盘这个硬件设备的抽象,直接操作硬盘不方便,借助文件进行操作就可以很方便的完成。类似于电视机的遥控器,通过遥控器来使用电视剧更加方便。

计算机中对具有”遥控属性“这样概念的叫做句柄(handle)。

(2)DatagramPacket:针对UDP数据报的抽象表示,一个DatagramPacket对象,就相当与一个UDP数据报,一次发送/一次接收就是传输了一个DatagramPacket对象。

回显服务器

Echo称为回显,正常服务器发送不同请求就会有不同响应,回显服务器就是请求发了什么,响应就是啥,这个过程不涉及计算和逻辑业务,是最简单的客户端服务器程序。

 编写服务器程序时,首先需要确定端口号,客户端是主动的一方,服务器是被动的一方,客户端需要找到服务器在哪。

IP地址(服务器所在主机的IP地址),port端口号(一个主机上,有多个程序都要进行网络通信,需要把那个程序用的哪个端口号记录下来,并要确保一个端口号不能被两个或多个进程关联)。

import java.io.IOException;
import java.net.DatagramSocket;public class UdppEchoServer {private DatagramSocket socket = null;public UdppEchoServer(int port) throws IOException {socket = new DatagramSocket(port);}
}

可以看到抛出的一异常可以是IOException,也就是说明网络编程的本质是IO操作。

接下来要让服务器可以不停的处理请求,不停的返回响应:

第一步:读取请求并对请求进行解析,构造一个数据报类的实例,对客户端发送的数据进行接收,放入实例中,对数据进行解析,最后为了方便打印,将数据报中的二进制数拿出来,转换为String类型的数据,String有一个构造方法,通过字节数组来构造。

 DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);socket.receive(requestPacket);String  request = new String(requestPacket.getData(),0, requestPacket.getLength());

第二步:根据请求返回响应,由于是回显服务器,计算请求的方法直接返回就行,返回的响应使用String进行接收。

String response = process(request);
public String process(String request) {return request;
}

第三步:将响应返回给客户端,发送时需要知道接收对象的IP和端口号,可以通过接收的UDP数据报拿到发送客户端的信息,拿到之后放到响应的数据报中使用send()方法发送,最后在服务器上面打印关键信息。

//把响应写回到客户端
//构造UDP数据包
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getSocketAddress());
//发送请求到客户端socket.send(responsePacket);System.out.printf("[%s %d] req = %s, resp =%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);

让程序启动后不断运行,加上while循环,一个完整的UDP回显服务器就此完成了。

客户端

客户端需要有自己的端口号和ip地址,客户端和服务器在一台主机上时就可以写本地环回地址(127.0.0.1),端口号是服务器在创建socket时指定的端口号。

public class UdpEchoClient {private DatagramSocket socket = null;private String serverPort;private String serverIP;public UdpEchoClient(String serverPort,String serverIP) throws SocketException {socket = new DatagramSocket();this.serverPort = serverPort;this.serverIP = serverIP;}
}

 在创建socket对象时并没有指定端口号,这是因为操作系统会自动分配一个端口号,这个自动的端口号每次重启都会不一样。

服务器需要固定端口号,而客户端需要让系统自动分配:

(1)服务器要有固定端口号,是因为客户端需要主动给服务器发请求,如果服务器端口号不是固定的,客户端就会不知道把请求发给谁了。

(2)客户端需要系统自动分配,指定固定的端口号是不行的,指定客户端的端口号,可能会和客户端所在电脑上的其他程序冲突,一旦端口冲突,就会导致程序启动不了。服务器在自己手里,就算端口冲突也是可以调整的,但客户端不在本机上时,端口冲突难以解决。

客户端逻辑在编写时和服务器有相同之处,客户端发送请求到服务器后,使用UDP数据报来接受响应。

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {private DatagramSocket socket = null;private int serverPort;private String serverIP;public UdpEchoClient(String serverIP,int serverPort) throws SocketException {socket = new DatagramSocket();this.serverPort = serverPort;this.serverIP = serverIP;}public void start() throws IOException {System.out.println("客户端启动");Scanner scanner = new Scanner(System.in);while (true) {//输入请求System.out.println("请输入请求: ");String request = scanner.next();//构造请求DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,InetAddress.getByName(serverIP),serverPort);//发送请求socket.send(requestPacket);//构造接收的数据报DatagramPacket responsePack = new DatagramPacket(new byte[4096],4096);//接收服务器返回的结果socket.receive(responsePack);//转换成String类型String response = new String(responsePack.getData(),0, responsePack.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);udpEchoClient.start();}
}

 服务器与客户端同时启动,客户端发送消息,服务器都会回显回来,并在控制台打印相应的ip地址,端口号,请求和响应:

TCP回显服务器/客户端的编写

TCP的socket api

主要涉及两个类:ServerSocket和Socket。

ServerSocket是专门提供给服务器使用的,ServerSocket在 实例化时调用的构造方法中自带端口号,实例化的对象包含一个accept方法,是一个类似接通功能的方法。

Socket是给客户端和服务器都提供服务,通过Socket的构造方法能够和指定的服务器建立连接。

TCP中通过使用InputStream和OutputStream进行文件读写操作,通过两个get方法来获取socket内部流对象。

Socket socket = new Socket();
socket.getInputStream();
socket.getOutputStream();

Tcp是字节流传输,传输基本单位是字节。

ServerSocket和Socket的功能不同:对于服务器来说,需要上来与客户端建立连接,建立连接要使用ServerSocket对象的accept方法,方法的返回值为socket类型;服务器一启动就会执行到建立连接的位置,如果此时没有客户端连接那么accept方法就会进入阻塞状态,直到有客户端连接。也就是说,ServerSocket是用与建立连接使用的,连接后将socket对象交给socket进行处理。

回显服务器

 每次创建一个服务器对象都要创建一个SerrverSocket来连接客户端,创建服务器时要包含 端口号,否则客户端没有端口号就无法连接。

public class TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}
}

 在服务器中创建start方法用来启动服务器,调用start方法后服务器开始不断的执行客户端的请求,不停的处理客户端的请求就需要不停的与客户端建立连接,通过调用process()方法去处理客户端的请求,将和客户端建立连接的信息使用socket接收,传递给process方法:

public void start() throws IOException {System.out.println("服务器启动");while (true) {Socket socket = serverSocket.accept();possess(socket);}}

 实现process方法,要注意TCP是面向字节流传输,此时进行读写操作时需要使用InputStream和OutputStream,这两个类在使用完后必须回收防止资源泄露,避免数据丢失,使用 try-catch方法里的try with source用法,把对象放到try()中,使用 完毕会自动回收资源。获得数据后可以使用Read类读取请求,但是Read类读取请求后得到的是byte数组,需要进一步转换成字符串 ,这里使用Scanner去读取可以直接转换成String类型:

    private void possess(Socket clientSocket)  {System.out.println("客户端上线");try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);while (true) {String request = scanner.next();}}catch (IOException e) {e.printStackTrace();}}

使用Scanner类读取数据时,读到"空白符"(空白符是一类字符的统称,包括但不限于换行,回车,空格,制表符,翻页符等)才会读取完毕 ,客户端在发送数据时,务必在每个请求的末尾加上空白符。

由于TCP是按照字节来传输的,在实际传输中,应该使若干个字节构成一个“应用层的数据报”,此时就可以通过使用空白符作为“分割符”,来区分不同的数据报。

 将读取的请求交给处理请求的函数,使用的是process函数,这里对请求的处理实际上是直接返回请求(回显服务器):

String request = scanner.next();
String response = possessFun(request);
outputStream.write(response.getBytes());

在读取请求之前,应该判断文件是否有输入 ,可以在进入while循环之后使用if语句判断请求是否有输入,使用scanner的next方法来判断,请求到达后,并且带有明确的分隔符就会返回true,如果TCP断开连接,就会返回false,使用scanner读取到文件末尾或者TCP断开连接就会返回false,否则就会阻塞等待客户端继续发送请求:

Tcp断开连接->阻塞解除返回false。

Tcp没有断开连接->对方们没有发数据过来,阻塞等待。

客户端发送请求 -> 接触阻塞并返回true。

服务器完整代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;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("服务器启动");while (true) {Socket socket = serverSocket.accept();possess(socket);}}private void possess(Socket clientSocket)  {System.out.println("客户端上线");try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);while (true) {if(!scanner.hasNext()) {System.out.println("客户端下线");break;}String request = scanner.next();String response = possessFun(request);outputStream.write(response.getBytes());}}catch (IOException e) {e.printStackTrace();}}private String possessFun(String request) {return request+" \n";}
}

客户端

 客户端在构造方法中应该有服务器的ip地址和端口号,在构造过程中就和服务器建立连接。

    private Socket socket = null;public TcpClientSocket(String address,int  port) throws IOException {socket = new Socket(address,port);}

建立start方法去启动客户端输入请求,同样使用try-catch语句实例化字节流对象,使用Scanner在控制台上输入请求,将接收的语句发送给服务器之前使用‘/n’作为分割符 ,发送给服务器使用OutputStream,传输的单位是字节,将请求转换为byte进行发送。

    public void start() {System.out.println("'客户端启动");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scanner = new Scanner(System.in);Scanner Rescanner = new Scanner(inputStream);while (true) {String request = scanner.next();request += "/n";outputStream.write(request.getBytes());}}

然后如同服务器一样,判断来自服务器的响应是否到达,使用scanner接收响应,在将接收的响应打印到控制台,客户端就完成编写:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;public class TcpClientSocket {private Socket socket = null;public TcpClientSocket(String address,int  port) throws IOException {socket = new Socket(address,port);}public void start() {System.out.println("'客户端启动");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scanner = new Scanner(System.in);Scanner Rescanner = new Scanner(inputStream);while (true) {String request = scanner.next();request += "/n";outputStream.write(request.getBytes());if(!Rescanner.hasNext()) {break;}String response = Rescanner.next();System.out.println(response);}}catch (IOException e) {e.printStackTrace();}}
}

 步骤:

 1.客户端从控制台得到请求。2.将请求发送到服务器3.服务器接收请求4.服务器处理请求5服务器.将响应发送给客户端6.客户端接收响应7.将响应输出在控制台。

优化服务器

1.关闭服务器创建的socket对象

serverrSocket不需要特别关闭,因为生命周期是伴随整个服务器进程。客户端的socket也是如此,但是服务器用于接收客户端信息的socket就必须关闭,服务器会对应多个客户端,如果使用完毕后不关闭当前资源文件得不到释放,就会引起文件资源泄露。在服务器代码中加上finally语句释放socket对象。

2.引入线程池,为多个客户端提供服务 

主线程处理accept,每次接收一个accept就创建一个线程来服务,这里使用可扩容的线程池:

    public void start() throws IOException {System.out.println("服务器启动");//自动扩容线程池ExecutorService pool = Executors.newCachedThreadPool();while (true) {Socket socket = serverSocket.accept();pool.submit(new Runnable() {@Overridepublic void run() {try {possess(socket);} catch (IOException e) {throw new RuntimeException(e);}}});}}

                                                文章到这里就结束了,感谢观看。 

 

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

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

相关文章

leetcode 之 二分查找(java)(3)

文章目录 5. 81. 搜索旋转排序数组 II6. 378、有序矩阵中第k个小的元素 5. 81. 搜索旋转排序数组 II 题目描述: 已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。 在传递给函数之前,nums 在预先未知的某个下标 k&#…

(长期更新)《零基础入门 ArcGIS(ArcMap) 》实验三----学校选址与路径规划(超超超详细!!!)

目录 实验三 学校选址与道路规划 3.1 实验内容及目的 3.1.1 实验内容 3.1.2 实验目的 3.2 实验方案 3.3 操作流程 3.3.1 环境设置 3.3.2 地势分析 (1)提取坡度: (2)重分类: 3.3.3 学校点分析 (1)欧氏距离: (2)重分类: 3.3.4 娱乐场所点分析 (1)欧氏距离…

【Delphi】modbus-TCP 协议库

在日常开发中,也会遇到使用modbus的部件,比如温度控制器、读卡器等等,那么使用Delphi开发,也就必须遵守modbus-TCP协议,如果自己使用TCP控件写也没有问题,不过如果有开源的三方库,别人已经调试过…

对载入的3dtiles进行旋转、平移和缩放变换。

使用 params: {tx: 129.75845, //模型中心X轴坐标(经度,单位:十进制度)//小左ty: 46.6839, //模型中心Y轴坐标(纬度,单位:十进制度)//小下tz: 28, //模型中心Z轴坐标(高…

计算机网络-网络安全

网络安全介绍 端口扫描 安全包括那些方面: 数据存储安全、应用程序安全、操作系统安全、网络安全、物理安全、用户安全教育 一、网络安全问题概述 1. 计算机网络面临的安全性威胁 计算机网络上的通信面临以下的四种威胁: 截获——从网络上窃听他人…

GEE Download Data——气温数据的下载

GEE数据下载第二弹!今天我们来分享气温数据的下载。 一、数据介绍 气温数据我们要用到的是MODIS数据产品,MOD11A2 V6.1 产品提供 1200 x 1200 公里网格内 8 天平均陆地表面温度 (LST)。 MOD11A2 中的每个像素值都是该 8 天内收集的所有相应 MOD11A1 LST 像素的简单平均值。…

【第 1 章 初识 C 语言】1.8 使用 C 语言的 7 个步骤

目录 1.8 使用 C 语言的 7 个步骤 1.8.1 第 1 步:定义程序的目标 1.8.2 第 2 步:设计程序 1.8.3 第 3 步:编写代码 1.8.4 第 4 步:编译 1.8.5 第 5 步:运行程序 1.8.6 第 6 步:测试和调试程序 1.8.…

docker部署RustDesk自建服务器

客户端: Releases rustdesk/rustdesk GitHub 服务端: 项目官方地址:GitHub - rustdesk/rustdesk-server: RustDesk Server Program 1、拉取RustDesk库 docker pull rustdesk/rustdesk-server:latest 阿里云库: docker pu…

rk2118--RT-Thread 消息队列

1、概述 什么是 RT-Thread 消息队列 RT-Thread 消息队列是一种用于在任务或中断服务例程(ISR)之间传递消息的机制。它允许一个任务或ISR发送数据到消息队列中,而另一个任务可以从消息队列中接收这些数据。消息队列提供了一种异步通信的方式&…

Scala的模式匹配(3)

package hfd.test32 import scala.io.StdInobject Test34_3 {def main(args: Array[String]): Unit {//从控制台读入一个数字aval aStdIn.readInt()// if (a>0 && a<3){println("[0,3")} // else if(a>4 &&a<8){println("[4…

通义灵码走进北京大学创新课堂丨阿里云云原生 10 月产品月报

云原生月度动态 云原生是企业数字创新的最短路径。 《阿里云云原生每月动态》&#xff0c;从趋势热点、产品新功能、服务客户、开源与开发者动态等方面&#xff0c;为企业提供数字化的路径与指南。 趋势热点 &#x1f947; 通义灵码走进北京大学创新课堂&#xff0c;与 400…

代码随想录第十四天|二叉树part02--226.翻转二叉树、101.对称二叉树、104.二叉树的最大深度、111.二叉树的最小深度

资料引用&#xff1a; 226.翻转二叉树&#xff08;226.翻转二叉树&#xff09; 101.对称二叉树&#xff08;101.对称二叉树&#xff09; 104.二叉树的最大深度&#xff08;104.二叉树的最大深度&#xff09; 111.二叉树的最小深度&#xff08;111.二叉树的最小深度&#xff09;…

二阶线性微分方程的幂级数解法

内容来源 常微分方程(第四版) (王高雄,周之铭,朱思铭,王寿松) 高等教育出版社 考虑二阶齐次线性微分方程 d 2 y d x 2 p ( x ) d y d x q ( x ) y 0 \frac{\mathrm{d}^2y}{\mathrm{d}x^2} p(x)\frac{\mathrm{d}y}{\mathrm{d}x}q(x)y0 dx2d2y​p(x)dxdy​q(x)y0 满足初值条…

Java基础面向对象(String类)

String 特点 是内存中常量, 值在内存中一旦创建, 不可改 更改String类型引用的值本质上是将引用指向了一个新的字符串地址 String s1 "abc";String s2 s1;//引用s1的地址赋值给了s2 ​s2 "edf";//让s2指向新字符串 ​System.out.println("s1: &q…

【系统架构设计师】真题论文: 论软件质量保证及其应用(包括解题思路和素材)

更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2017年 试题4)解题思路论文素材参考软件质量保证定义和重要性软件质量保证在软件开发生命周期中的应用真题题目(2017年 试题4) 软件质量保证 (Software Quality Assurance. SQA) 是指为保证软件系统或…

LeetCode763. 划分字母区间(2024冬季每日一题 23)

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍然是 s 。 返回一个表示每个字符串片段的长度的列表。 示例 1&a…

【C++】深入优化计算题目分析与实现

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;第一题&#xff1a;圆的计算我的代码实现代码分析改进建议改进代码 老师的代码实现代码分析可以改进的地方改进代码 &#x1f4af;第二题&#xff1a;对齐输出我的代码实现…

6.824/6.5840 Lab 3: Raft——Part 3B3C

芙蓉花又栖满了枝头 奈何蝶难留 漂泊如江水向东流 望断门前隔岸的杨柳 寂寞仍不休 我无言让眼泪长流 ——山外小楼夜听雨 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 在完成Lab之前&#xff0c;务必把论文多读几遍&#xff0c;力求完全理解Leader选举、…

LeetCode - #150 逆波兰表达式求值

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 我们社区陆续会将顾毅&#xff08;Netflix 增长黑客&#xff0c;《iOS 面试之道》作者&#xff0c;ACE 职业健身教练。&#xff09;的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新…

使用go实现一个简单的rpc

什么是rpc, rpc是干什么的?几种协议的压测数据对比:tcphttp 使用tcp实现一个简单的rpc服务 什么是rpc, rpc是干什么的? rpc的作用就是实现远程的服务调用 工作流程: 客户端携带服务信息(服务名,方法名)数据 去请求服务端,服务端拿到数据,解析后执行对应的方法,将结果返回给客…