网络编程:通过UDP和TCP分别实现回显服务器

avatar
作者
猴君
阅读量:0

目录

网络编程

封装和分用

通过Socket API进行应用层和传输层之间的交互

//TCP和UDP的区别

网络通信数据的基本单位:

UDP的socket api如何使用

【1】DatagramSocket

【2】DatagramPacket

【服务器端代码】

【客户端代码】

TCP的socket api

【1】ServerSocket

【2】Socket

【服务器端代码】

【客户端代码】


网络编程

IP地址:使用一个32位4字节数字表示地址(一般来说会把IP地址给表示成4个0-255之间的十进制数字,并使用3个点进行分隔)

端口号:区分一个主机上不同的应用程序,端口号是一个整数(2个字节,0-65535)(一个端口号只能被一个程序绑定,但是一个程序可以绑定多个端口)

//0一般不使用,1-1023这个范围的端口号系统留作特殊用途,写的程序不应该占用

封装和分用

描述了网络通信过程中基本的数据传输流程

//一个数据报=报头+载荷(字符串拼接)

1.应用层:根据约定的应用层协议来生成应用层数据报,通过操作系统的api把数据交给传输层(具体是用几个字段,字段的顺序如何,使用什么符合分隔都是可以灵活调整的)

2.传输层:在应用层数据报的基础上拼接上传输层的报头,变成传输层的数据报(传输层典型的协议,TUP,UDP)(包含源端口和目的端口),传输层数据报搞好之后这个数据又会进一步交给网络层

3.网络层

网络层最主要的协议是IP协议(包含源IP和目的IP)

再交给数据链路层进一步打包

4.数据链路层

最主要的协议是以太网(报头中包含源mac地址和目的mac地址)//此处会比之前多一个加报尾的操作

5.物理层

把上述数据转化成2进制的01序列后通过光信号/电信号进行传输

//数据发送出去之后就会经过一系列的交换机和路由器进行转发,A和B一般来说不是直接网线连接的,中间还要经过很多的交换机/路由器设备进行转发,当数据到达B这边之后,B就要针对上述数据进行"分用"(针对上述数据报进行层层的解析)

数据报在网络中间还会经历一定的转发过程;如果经过路由器就会封装分用到网络层,路由器解析到网络层拿到IP地址再决定进一步如何运输,下一步传输的时候又会重新经过数据链路层和物理层的封装;如果经过交换机就会封装分用到数据链路层

通过Socket API进行应用层和传输层之间的交互

传输层提供的网络协议主要是TCP和UDP,这两个协议的特性(工作原理)差异很大,导致使用这两种协议进行网络编程时也存在一定差别,所以系统分别提供了两套API

//TCP和UDP的区别

(1)TCP是有连接的,UDP是无连接的

(连接是抽象的概念,计算机中这种抽象的连接是很常见的,此处的连接本质上是建立连接的双方各自保存对方的信息)

//TCP要想通信就得先建立连接(得对方同意才能通信),UDP想要通信直接发送数据即可(UDP不保存对方的信息,但是我们调用UDP的socket api的时候要把对方的位置啥的给传过去)

(2)TCP是可靠传输的,UDP是不可靠传输的

//可靠传输指的是A在传送消息失败时能感知到,就可以在发送失败的时候采取一定的措施(尝试重传之类的)可靠传输的代价是机制更复杂以及传输效率会降低

(3)TCP是面向字节流的,UDP是面向数据报

(4)TCP和UDP都是全双工的

//一个信道允许双向通信就是全双工(代码中使用一个socket对象就可以发送数据也能接受数据),单向通信就是半双工

网络通信数据的基本单位:

【1】数据报(Datagram)【2】数据包(Packet)【3】数据帧(Frame)【4】数据段(Segment)

UDP的socket api如何使用

【1】DatagramSocket

void receive(DatagramPacket p)

void send(DatagramPacket p)

void close()

//socket本质上是一种特殊的文件,属于是把“网卡”这个设备抽象成了文件,做到了把网络通信和文件操作给统一了

【2】DatagramPacket

创建时得指定一块内存空间DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);

//使用这个类来表示一个UDP数据报,UDP是面向数据报的,每次进行传输都要以UDP数据报作为基本单位

服务器和客户端都需要创建socket对象,但是服务器的socket一般要显式的指定一个端口号,而客户端的socket一般不能显式指定(不显式指定,此时系统会自动分配一个随机的端口)

【服务器端代码】

import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;  public class UdpEchoServer{     //创建一个DatagramSocket对象,后续操作网卡的基础     private DatagramSocket socket=null;     public UdpEchoServer(int port) throws SocketException {     //这么写就是手动指定端口         socket=new DatagramSocket(port);          //这么写就是系统自动分配端口          //socket=new DatagramSocket();     }     public void start() throws IOException {         //通过这个方法来启动服务器         System.out.println("服务器启动");         //一个服务器程序中,经常能看到while true这样的代码         while(true){           //1.读取请求并解析             DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);             socket.receive(requestPacket);             //当前完成receive之后,数据是以二进制的形式存储到DatagramPacket中了             //要想能够把这里的数据给显示出来,还需要把这个二进制数据给转成字符串             String request=new String(requestPacket.getData(),0,requestPacket.getLength());          //2.根据请求计算响应(一般的服务器都会经历的过程)             //由于此处是回显服务器,请求是啥样,响应就是啥样             String response=process(request);          //3.把响应写回到客户端             //搞一个响应对象DatagramPacket,往DatagramPacket里构造刚才的数据,再通过send返回             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(),requese,response);         }     }     public String process(String request){         return request;     }     public static void main(String[] args) throws IOException{         UdpEchoServer server=new UdpEchoServer(9090);         server.start();     } }

【客户端代码】

import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner;  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();         //由于UDP自身不会持有对端的信息,就需要在应用程序里把对端的情况给记录下来         //这里主要记录对端的ip和端口         serverIp=ip;         serverPort=port;     }     public void start() throws IOException {         System.out.println("客户端启动!");         Scanner scanner=new Scanner(System.in);         while(true){             //1.从控制台读取数据作为请求             System.out.print("->");             String request=scanner.next();             //2.把请求内容构造出DatagramPacket对象发给服务器             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);             //4.把响应转换成字符串并显示出来             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();     } }

TCP的socket api

(TCP是字节流的,传输的基本单位是byte)

【1】ServerSocket

给服务器使用的类,使用这个类来绑定端口号

【2】Socket

既会给服务器用,又会给客户端用

【服务器端代码】

import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;  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("服务器启动!");         ExecutorService service= Executors.newCachedThreadPool();         while(true){             //通过accept方法,把内核中已经建立好的连接拿到应用程序中             //建立连接的细节流程都是内核自动完成的,应用程序只需要“捡现成”的             Socket clientSocket=serverSocket.accept();             //此处不应该直接调用processConnection,会导致服务器不能处理多个客户端             //创建新的线程来用是更合理的做法             // 这种做法可行,但是不够好            // Thread t=new Thread(()->{            //processConnection(clientSocket);            //});            //t.start();            //更好一点的办法是使用线程池             service.submit(new Runnable(){                 @Override                 public void run(){                     processConnection(clientSocket);                 }             });            //还有一些其他手段可以来处理线程较多的情况:协程、IO多路复用、IO多路转接         }     }     //通过这个方法来处理当前的连接     public void processConnection(Socket clientSocket){        //进入方法,先打印一个日志,表示当前有客户端连上了         System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());         //接下来进行数据的交互         try(InputStream inputStream=clientSocket.getInputStream();             OutputStream outputStream=clientSocket.getOutputStream()){            //使用try()方式,避免后续用完了流对象忘记关闭            //由于客户端发来的数据可能是“多条数据”,针对多条数据就应该循环处理             while(true){                 Scanner scanner=new Scanner(inputStream);                 if(!scanner.hasNext()){                 //连接断开了循环就应该结束                     System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());                     break;                 }                 //1.读取请求并解析,此处就以next来作为读取请求的方式,next的规则就是读到“空白符”就返回                 String request=scanner.next();                 //2.根据请求计算响应                 String response=process(request);                 //3.把响应写回到客户端                 //也可以把String转成字节数组,写入到OutputStream                 //也可以使用PrintWriter把OutputStream包裹一下,来写入字符串                 PrintWriter printWriter=new PrintWriter(outputStream);                 //此处的println不是打印到控制台了,而是写入到outputStream对应的流对象中,也就是写入到clientSocket里面                 //自然这个数据也就通过网络发送出去了(发给当前这个连接的另外一端)                 //此处使用println带有\n也是为了后续客户端这边可以使用scanner.next来读取数据                 printWriter.println(response);                 //此处还要记得有个操作,刷新缓冲区,如果没有刷新操作可能数据仍然是在内存中而没有被写入网卡                 printWriter.flush();                 //4.打印一下这次请求交互过程的内容                 System.out.printf("[%s:%d] req=%s,resp=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);             }         }catch(IOException e){             e.printStackTrace();         }finally{             try{                 //在这个地方进行clientSocket的关闭                 //processConnection就是在处理一个连接,这个方法执行完毕,这个连接也就处理完了                 clientSocket.close();             }catch(IOException e){                 e.printStackTrace();             }         }     }     public String process(String request){        //此处也是写的回显服务器,响应和请求是一样的         return request;     }     public static void main(String[] args) throws IOException{         TcpEchoServer server=new TcpEchoServer(9090);         server.start();     } }

【客户端代码】

import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner;  public class TcpEchoClient{     private Socket socket=null;     public TcpEchoClient(String serverIp,int serverPort) throws IOException {     //需要在创建Socket的同时和服务器“建立连接”,此时就得告诉Socket服务器在哪里     //具体建立连接的细节不需要利用代码手动干预,是内核自动负责的     //当new这个对象的时候,操作系统内核就开始进行“三次握手”具体细节完成建立连接的过程         socket=new Socket(serverIp,serverPort);     }     public void start(){     //tcp的客户端行为和udp的客户端差不多         Scanner scanner=new Scanner(System.in);         try(InputStream inputStream=socket.getInputStream();             OutputStream outputStream=socket.getOutputStream()){             PrintWriter writer=new PrintWriter(outputStream);             Scanner scannerNetwork=new Scanner(inputStream);             while(true){                 //1.从控制台读取用户输入的内容                 System.out.print("-> ");                 String request=scanner.next();                 //2.把字符串作为请求发送给服务器                 //这里使用println是为了让请求后面带上换行                 //也就是和服务器读取请求的scanner.next呼应                 writer.println(request);                 writer.flush();                 //3.从服务器读取响应                 String response=scannerNetwork.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();     } } 

注意:

在进行运行测试时应该先启动服务器再启动客户端

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!