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

玛卡巴卡的卡巴卡玛  金牌会员 | 2024-12-14 11:53:35 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 979|帖子 979|积分 2937

目录

套接字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端口号(一个主机上,有多个步伐都要举行网络通信,需要把那个步伐用的哪个端口号记录下来,并要确保一个端口号不能被两个或多个进程关联)。
  1. import java.io.IOException;
  2. import java.net.DatagramSocket;
  3. public class UdppEchoServer {
  4.     private DatagramSocket socket = null;
  5.     public UdppEchoServer(int port) throws IOException {
  6.         socket = new DatagramSocket(port);
  7.     }
  8. }
复制代码
可以看到抛出的一非常可以是IOException,也就是说明网络编程的本质是IO操作。
接下来要让服务器可以不绝的处置处罚请求,不绝的返回相应:
第一步:读取请求并对请求举行解析,构造一个数据报类的实例,对客户端发送的数据举行接收,放入实例中,对数据举行解析,末了为了方便打印,将数据报中的二进制数拿出来,转换为String类型的数据,String有一个构造方法,通过字节数组来构造。
  1. DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
  2. socket.receive(requestPacket);
  3. String  request = new String(requestPacket.getData(),0, requestPacket.getLength());
复制代码
第二步:根据请求返回相应,由于是回显服务器,盘算请求的方法直接返回就行,返回的相应使用String举行接收。
  1. String response = process(request);
  2. public String process(String request) {
  3.        return request;
  4. }
复制代码
第三步:将相应返回给客户端,发送时需要知道接收对象的IP和端口号,可以通过接收的UDP数据报拿到发送客户端的信息,拿到之后放到相应的数据报中使用send()方法发送,末了在服务器上面打印关键信息。
  1. //把响应写回到客户端
  2. //构造UDP数据包
  3. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,
  4.                 requestPacket.getSocketAddress());
  5. //发送请求到客户端
  6. socket.send(responsePacket);
  7. System.out.printf("[%s %d] req = %s, resp =%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);
复制代码
让步伐启动后不断运行,加上while循环,一个完备的UDP回显服务器就此完成了。

客户端

客户端需要有本身的端口号和ip地点,客户端和服务器在一台主机上时就可以写本地环回地点(127.0.0.1),端口号是服务器在创建socket时指定的端口号。
  1. public class UdpEchoClient {
  2.     private DatagramSocket socket = null;
  3.     private String serverPort;
  4.     private String serverIP;
  5.     public UdpEchoClient(String serverPort,String serverIP) throws SocketException {
  6.         socket = new DatagramSocket();
  7.         this.serverPort = serverPort;
  8.         this.serverIP = serverIP;
  9.     }
  10. }
复制代码
 在创建socket对象时并没有指定端口号,这是因为操作体系会主动分配一个端口号,这个主动的端口号每次重启都会不一样。
服务器需要固定端口号,而客户端需要让体系主动分配:
(1)服务器要有固定端口号,是因为客户端需要主动给服务器发请求,如果服务器端口号不是固定的,客户端就会不知道把请求发给谁了。
(2)客户端需要体系主动分配,指定固定的端口号是不行的,指定客户端的端口号,大概会和客户端所在电脑上的其他步伐辩说,一旦端口辩说,就会导致步伐启动不了。服务器在本身手里,就算端口辩说也是可以调解的,但客户端不在本机上时,端口辩说难以解决。
客户端逻辑在编写时和服务器有类似之处,客户端发送请求到服务器后,使用UDP数据报来接受相应。
  1. import java.io.IOException;
  2. import java.net.*;
  3. import java.util.Scanner;
  4. public class UdpEchoClient {
  5.     private DatagramSocket socket = null;
  6.     private int serverPort;
  7.     private String serverIP;
  8.     public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
  9.         socket = new DatagramSocket();
  10.         this.serverPort = serverPort;
  11.         this.serverIP = serverIP;
  12.     }
  13.     public void start() throws IOException {
  14.         System.out.println("客户端启动");
  15.         Scanner scanner = new Scanner(System.in);
  16.         while (true) {
  17.            //输入请求
  18.             System.out.println("请输入请求: ");
  19.             String request = scanner.next();
  20.             //构造请求
  21.             DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
  22.                     InetAddress.getByName(serverIP),serverPort);
  23.             //发送请求
  24.             socket.send(requestPacket);
  25.             //构造接收的数据报
  26.             DatagramPacket responsePack = new DatagramPacket(new byte[4096],4096);
  27.             //接收服务器返回的结果
  28.             socket.receive(responsePack);
  29.             //转换成String类型
  30.             String response = new String(responsePack.getData(),0, responsePack.getLength());
  31.             System.out.println(response);
  32.         }
  33.     }
  34.     public static void main(String[] args) throws IOException {
  35.         UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
  36.         udpEchoClient.start();
  37.     }
  38. }
复制代码
 服务器与客户端同时启动,客户端发送消息,服务器都会回显回来,并在控制台打印相应的ip地点,端口号,请求和相应:

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

TCP的socket api

主要涉及两个类:ServerSocket和Socket。
ServerSocket是专门提供给服务器使用的,ServerSocket在 实例化时调用的构造方法中自带端口号,实例化的对象包含一个accept方法,是一个类似接通功能的方法。
Socket是给客户端和服务器都提供服务,通过Socket的构造方法能够和指定的服务器创建连接。
TCP中通过使用InputStream和OutputStream举行文件读写操作,通过两个get方法来获取socket内部流对象。
  1. Socket socket = new Socket();
  2. socket.getInputStream();
  3. socket.getOutputStream();
复制代码
Tcp是字节流传输,传输基本单元是字节。
ServerSocket和Socket的功能不同:对于服务器来说,需要上来与客户端创建连接,创建连接要使用ServerSocket对象的accept方法,方法的返回值为socket类型;服务器一启动就会实行到创建连接的位置,如果此时没有客户端连接那么accept方法就会进入壅闭状态,直到有客户端连接。也就是说,ServerSocket是用与创建连接使用的,连接后将socket对象交给socket举行处置处罚。
回显服务器

 每次创建一个服务器对象都要创建一个SerrverSocket来连接客户端,创建服务器时要包含 端口号,否则客户端没有端口号就无法连接。
  1. public class TcpEchoServer {
  2.     private ServerSocket serverSocket = null;
  3.     public TcpEchoServer(int port) throws IOException {
  4.         serverSocket = new ServerSocket(port);
  5.     }
  6. }
复制代码
 在服务器中创建start方法用来启动服务器,调用start方法后服务器开始不断的实行客户端的请求,不绝的处置处罚客户端的请求就需要不绝的与客户端创建连接,通过调用process()方法去处置处罚客户端的请求,将和客户端创建连接的信息使用socket接收,传递给process方法:
  1. public void start() throws IOException {
  2.         System.out.println("服务器启动");
  3.         while (true) {
  4.             Socket socket = serverSocket.accept();
  5.             possess(socket);
  6.         }
  7.     }
复制代码
 实现process方法,要注意TCP是面向字节流传输,此时举行读写操作时需要使用InputStream和OutputStream,这两个类在使用完后必须回收防止资源泄漏,避免数据丢失,使用 try-catch方法里的try with source用法,把对象放到try()中,使用 完毕会主动回收资源。获得数据后可以使用Read类读取请求,但是Read类读取请求后得到的是byte数组,需要进一步转换成字符串 ,这里使用Scanner去读取可以直接转换成String类型:
  1.     private void possess(Socket clientSocket)  {
  2.         System.out.println("客户端上线");
  3.         try(InputStream inputStream = clientSocket.getInputStream();
  4.             OutputStream outputStream = clientSocket.getOutputStream()) {
  5.             Scanner scanner = new Scanner(inputStream);
  6.             while (true) {
  7.                 String request = scanner.next();
  8.             }
  9.         }catch (IOException e) {
  10.             e.printStackTrace();
  11.         }
  12.     }
复制代码
使用Scanner类读取数据时,读到"空白符"(空白符是一类字符的统称,包罗但不限于换行,回车,空格,制表符,翻页符等)才会读取完毕 ,客户端在发送数据时,务必在每个请求的末端加上空白符。
由于TCP是按照字节来传输的,在实际传输中,应该使若干个字节构成一个“应用层的数据报”,此时就可以通过使用空白符作为“分割符”,来区分不同的数据报。
 将读取的请求交给处置处罚请求的函数,使用的是process函数,这里对请求的处置处罚实际上是直接返回请求(回显服务器):
  1. String request = scanner.next();
  2. String response = possessFun(request);
  3. outputStream.write(response.getBytes());
复制代码
在读取请求之前,应该判断文件是否有输入 ,可以在进入while循环之后使用if语句判断请求是否有输入,使用scanner的next方法来判断,请求到达后,并且带有明白的分隔符就会返回true,如果TCP断开连接,就会返回false,使用scanner读取到文件末端或者TCP断开连接就会返回false,否则就会壅闭等待客户端继续发送请求:
   Tcp断开连接->壅闭解除返回false。
  Tcp没有断开连接->对方们没有发数据过来,壅闭等待。
  客户端发送请求 -> 打仗壅闭并返回true。
  服务器完备代码:
  1. 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 {
  2.         System.out.println("服务器启动");
  3.         while (true) {
  4.             Socket socket = serverSocket.accept();
  5.             possess(socket);
  6.         }
  7.     }    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地点和端口号,在构造过程中就和服务器创建连接。
  1.     private Socket socket = null;
  2.     public TcpClientSocket(String address,int  port) throws IOException {
  3.         socket = new Socket(address,port);
  4.     }
复制代码
创建start方法去启动客户端输入请求,同样使用try-catch语句实例化字节流对象,使用Scanner在控制台上输入请求,将接收的语句发送给服务器之前使用‘/n’作为分割符 ,发送给服务器使用OutputStream,传输的单元是字节,将请求转换为byte举行发送。
  1.     public void start() {
  2.         System.out.println("'客户端启动");
  3.         try(InputStream inputStream = socket.getInputStream();
  4.             OutputStream outputStream = socket.getOutputStream()) {
  5.             Scanner scanner = new Scanner(System.in);
  6.             Scanner Rescanner = new Scanner(inputStream);
  7.             while (true) {
  8.                 String request = scanner.next();
  9.                 request += "/n";
  10.                 outputStream.write(request.getBytes());
  11.             }
  12.         }
复制代码
然后犹如服务器一样,判断来自服务器的相应是否到达,使用scanner接收相应,在将接收的相应打印到控制台,客户端就完成编写:
  1. 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;
  2.     public TcpClientSocket(String address,int  port) throws IOException {
  3.         socket = new Socket(address,port);
  4.     }    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就创建一个线程来服务,这里使用可扩容的线程池:
  1.     public void start() throws IOException {
  2.         System.out.println("服务器启动");
  3.         //自动扩容线程池
  4.         ExecutorService pool = Executors.newCachedThreadPool();
  5.         while (true) {
  6.             Socket socket = serverSocket.accept();
  7.             pool.submit(new Runnable() {
  8.                 @Override
  9.                 public void run() {
  10.                     try {
  11.                         possess(socket);
  12.                     } catch (IOException e) {
  13.                         throw new RuntimeException(e);
  14.                     }
  15.                 }
  16.             });
  17.         }
  18.     }
复制代码
文章到这里就竣事了,感谢观看。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表