900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程

【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程

时间:2020-12-21 09:11:35

相关推荐

【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程

文章目录

1. 什么是网络编程2. 网络编程中的基本概念3. Socket套接字4 UDP数据报套接字编程4.1 客户端服务器交互流程4.2 UDP版本的回显服务4.3 英译汉服务5. TCP流套接字编程5.1 TCP版本的回显服务5.2 服务器优化5.3 英译汉服务

1. 什么是网络编程

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

当然我们只需要满足进程不同就行,所以即便是同一个主机,只要是不同进程,基于网络来传输数据也属于网络编程。

操作系统把网络编程的一些相关操作封装起来,提供了一组API供程序员来调用 – Socket(套接字)API

2. 网络编程中的基本概念

发送端和接收端

在一次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

收发端:发送端和接收端两端,也简称为收发端。

发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念

请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输:

第一次:请求数据的发送

第二次:响应数据的发送。

客户端和服务器

服务器:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。

客户端:获取服务的一方进程,称为客户端。

常见的客户端服务器模型

客户端先发送请求到服务器服务器根据请求数据,执行相应的业务处理服务器返回响应:发送业务处理结果客户端根据响应数据,展示处理结果

3. Socket套接字

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程

Socket套接字针对传输层协议主要分为两种:流套接字,数据报套接字

流套接字:使用传输层TCP协议

TCP :传输控制协议

特点

有连接可靠传输面向字节流全双工通信

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情

况下,是无边界的数据,可以多次发送,也可以分开多次接收。

数据报套接字:使用传输层UDP协议

UDP:用户数据报协议

特点:

无连接不可靠传输面向数据报全双工通信

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

理解有连接和无连接

有连接类似于打电话,我们和朋友打电话时,双方能听到声音并说话的前提是我电话拨通了,他也接电话了,我俩之间建立连接了,才能说话。

无连接类似于发微信,给朋友发微信不需要建立连接,只需要给他发出去就行了,至于对方什么时候看到就不确定了。

理解可靠传输和不可靠传输

可靠传输和不可靠传输并不是说发送的数据就一定能被对方收到,

可靠传输指的是发送方能知道数据是否被对方接收到了

不可靠传输指的是发送方不能知道数据是否被对方收到了

面向字节流和面向数据报

面向字节流:如果要发送100个字节,可以一次发1个字节,发送100次;也可以一次发10个字节,发送10次。可以非常灵活的完成这里的发送,接收也是如此,发送和接收的最小单位是字节

面向数据报:以一个一个的数据报为基本单位,数据报多大不确定,不同的协议有不同的约定。并且发送的时候,一次至少发送一个数据报,接收的时候,一次至少接收一个数据报

全双工

全双工:双向通信,A和B可以同时发送数据和接收数据

半双工:单向通信,同一时间只能有一方发送数据,要么A给B发,要么B给A发

4 UDP数据报套接字编程

DatagramSocket API

DatagramSocket 描述一个UDPSocket对象,用于发送和接收UDP数据报。

Java标准库中的DatagramSocket对象 就是表示一个Socket文件,socket本质上是一个文件描述符表,网络编程主要是通过网卡来实现的,读取socket文件中的数据,就是读取网卡,向socket文件中写入数据就是写入网卡,我们可以理解为socket文件就是一个遥控器,用来操作网卡,使我们实现网络编程

DatagraSocket构造方法

DatagramSocket方法

DatagramPacket API

DatagramPacket 描述一个UDP数据报

UDP面向数据报,发送接收数据,就是以DatagramPacket对象为单位进行的

DatagramPacket 构造方法

DatagramPacket 方法

构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建

InteSocketAddress API

使用InetSocketAddress(InetAddress addr,int port)方法创建一个Socket地址,包含IP地址和端口号

4.1 客户端服务器交互流程

客户端给服务器发送请求服务器读取请求并解析服务器根据请求计算响应服务器构造响应并返回客户端获得响应并显示

4.2 UDP版本的回显服务

回显服务就是客户端发送什么,服务器就回应什么

服务器:

public class Server {private DatagramSocket socket = null;public Server(int port) throws SocketException {this.socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while (true){DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);socket.receive(requestPacket);String request = new String(requestPacket.getData(),0,requestPacket.getLength());String response = process(request);DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);String log = String.format("[%s;%d],res: %s ,reps: %s",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);System.out.println(log);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {Server server = new Server(9050);server.start();}}

客户端

public class Client {private DatagramSocket socket = null;private String serverIp;private int serverPort;public Client(String serverIp, int serverPort) throws SocketException {this.serverIp = serverIp;this.serverPort = serverPort;socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);while (true) {System.out.println("->");String request = scanner.nextLine();if(request.equals("exit")){System.out.println("exit");return;}DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPacket);DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(),0,responsePacket.getLength());String log = String.format("res: %s ; reps: %s",request,response);System.out.println(log);}}public static void main(String[] args) throws IOException {Client client = new Client("127.0.0.1",9050);client.start();}}

具体分析

服务器中最核心的部分就是process方法,根据请求计算响应,此处我们实现的是回显服务,下面我们做一个简单的英译汉服务器

4.3 英译汉服务

public class DictServer {private DatagramSocket socket = null;private HashMap<String,String> map = new HashMap<>();public DictServer(int port) throws SocketException {socket = new DatagramSocket(port);//对hashmap进行初始化构造,可以构造多个数据map.put("cat","猫");map.put("dog","狗");map.put("hello","你好");}public void start() throws IOException {System.out.println("服务器启动");while(true){DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);socket.receive(requestPacket);String request = new String(requestPacket.getData(),0,requestPacket.getLength());String response = process(request);DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);String log = String.format("[%s,%d] req: %s ; resp: %s",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);System.out.println(log);}}private String process(String request) {//根据用户请求计算响应,英译汉的核心就是查表,构造方法中在map中加入了3个数据//此处使用getOrDefault,key存在就返回对应的value。不存在就返回"我不会"return map.getOrDefault(request,"我不会");}public static void main(String[] args) throws IOException {DictServer server = new DictServer(9090);server.start();}}

除了在process中处理业务的逻辑和回显服务器不同之外,其他部分的代码都基本相同

5. TCP流套接字编程

ServerSocket API

ServerSocket 是创建TCP服务端Socket的API

ServerSocket构造方法:ServerSocket(int port),创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket方法

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket构造方法:Socket(String hose,int port)创建一个客户端流套接字Socket,并与对应IP的主机上对应端口的进程建立连接,也就是与想要交互的服务器建立连接

Socket 方法

5.1 TCP版本的回显服务

服务器

public class Server {private ServerSocket listenSocket = null;public Server(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while(true){Socket clientSocket = listenSocket.accept();processConnection(clientSocket);}}private void processConnection(Socket clientSocket) throws IOException {String log = String.format("[Ip:%s,Port:%d] 客户端上线",clientSocket.getInetAddress(),clientSocket.getPort());System.out.println(log);try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while(true){Scanner scanner = new Scanner(inputStream);if (!scanner.hasNextLine()){log = String.format("[Ip:%s,Port:%d] 客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());System.out.println(log);break;}String request = scanner.next();String response = process(request);PrintWriter writer = new PrintWriter(outputStream);writer.println(response);writer.flush();log = String.format("[%s,%d] 请求: %s ; 响应: %s",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);System.out.println(log);}} catch (Exception e) {e.printStackTrace();}finally {clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {Server server = new Server(9090);server.start();}}

客户端

public class Client {private Socket socket = null;private String serverIp;private int serverPort;public Client(String serverIp, int serverPort) throws IOException {this.socket = new Socket(serverIp,serverPort);this.serverIp = serverIp;this.serverPort = serverPort;}public void start(){Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){while(true){System.out.println("->");String request = scanner.next();if(request.equals("exit")){System.out.println("退出客户端");break;}PrintWriter writer = new PrintWriter(outputStream);writer.println(request);writer.flush();Scanner responseScanner = new Scanner(inputStream);String response = responseScanner.next();String log = String.format("[请求 %s : 响应 : %s]",request,response);System.out.println(log);}} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {Client client = new Client("127.0.0.1",9090);client.start();}}

具体分析

5.2 服务器优化

当有客户端连接时,accetp返回一个Socket对象,得到clinetSocket,并进入processConnection方法,在这个方法中,代码会在Scanner.hasNext()位置阻塞,等待客户端发送请求,客户端发送请求后,Scanner.hasNext()结束阻塞,读取到数据后,进行下面计算响应,返回响应的操作,这一系列操作完成后,又会到Scanner.hasNext()这里阻塞,直到这个客户端退出连接,循环才结束

所以此时,这个服务器同一时间只能与一个客户端建立连接,当有多个客户端同时想要与服务器建立连接时,只能与先请求建立连接的那个客户端建立连接,等到第一个客户端推出连接后,第二个客户端才能与服务器建立连接,发送请求。

解决方案:使用多线程

主线程里面循环调用accept,每次获取到一个连接,就创建一个线程,让这个子线程来处理这个连接

public class TcpThreadEchoServer {private ServerSocket listenSocket = null;public TcpThreadEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while (true){//采用多线程的方式,能够保证accept调用完毕之后能后再次立刻调用accept 每个线程对应一个客户端//主线程循环调用accept,客户端连接后创建一个新线程来处理连接,Socket clientScoket = listenSocket.accept();//创建线程来给客户端提供服务Thread t = new Thread(){@Overridepublic void run() {try {processConnection(clientScoket);} catch (IOException e) {e.printStackTrace();}}};t.start();}}private void processConnection(Socket clientScoket) throws IOException {String log = String.format("[%s,%d]客户端上线",clientScoket.getInetAddress().toString(),clientScoket.getPort());System.out.println(log);try (InputStream inputStream = clientScoket.getInputStream();OutputStream outputStream = clientScoket.getOutputStream()){while (true){//1.读取请求并解析Scanner scanner = new Scanner(inputStream);if(!scanner.hasNextLine()){log = String.format("[%s,%d]客户端下线",clientScoket.getInetAddress().toString(),clientScoket.getPort());System.out.println(log);break;}String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.将响应返回给客户端PrintWriter writer = new PrintWriter(outputStream);writer.println(response);writer.flush();log = String.format("[%s:%d] res %s ; resp %s",clientScoket.getInetAddress().toString(),clientScoket.getPort(),request,response);System.out.println(log);}} catch (Exception e) {e.printStackTrace();}finally {clientScoket.close();}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadEchoServer server = new TcpThreadEchoServer(9090);server.start();}}

总体来看,代码和之前的回显服务器的代码差别不大,只是在处理连接时,加入了多线程。

但是如果有很多的客户端连接又退出,就会导致服务器频繁的创建和销毁线程,这时就需要很大的成本,所以我们直接引入线程池,

public class TcpThreadPoolServer {private ServerSocket listenSocket = null;public TcpThreadPoolServer(int port) throws IOException {this.listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");//创建线程池,将处理连接这个任务循环放入到线程池中ExecutorService executorService = Executors.newCachedThreadPool();while (true){Socket clientSocket = listenSocket.accept();//使用线程池处理连接executorService.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}//其他代码一样,只需要在start方法中加入线程池

5.3 英译汉服务

基于上面改进后的TCP服务器,实现一个翻译服务器。

写了几次的服务器后,我们发现翻译服务器的代码和回显服务器的代码基本差不多,只有process不一样,所以我们可以通过继承的方式来实现代码复用,只需要重写process方法,重新实现 根据请求计算响应的逻辑 就可以了

public class TcpDictServer extends TcpThreadPoolServer{private HashMap<String,String> map = new HashMap<>();public TcpDictServer(int port) throws IOException {super(port);map.put("hello","你好");map.put("cat","猫");map.put("dog","狗");}@Overridepublic String process(String request){return map.getOrDefault(request,"我不会");}public static void main(String[] args) throws IOException {TcpDictServer server = new TcpDictServer(9090);server.start();}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。