飞道的博客

TCP/UDP网络编程

398人阅读  评论(0)

目录

一、常见的客户端服务端模型

二、Socket套接字

1、概念

2、分类

a、流套接字

b、数据报套接字 

c、原始套接字 

三、UDP数据报套接字编程 

四、TCP数据报套接字编程 


一、常见的客户端服务端模型

客户端:用户使用的程序。

服务端:给用户提供服务的程序。

整体流程:

  1. 客户端发送请求到服务端。
  2. 服务端根据客户端的的请求数据,执行相应的业务处理。
  3. 服务端返回响应:发送处理业务的处理结果。
  4. 客户端得到处理结果,展示处理结果。

二、Socket套接字

1、概念

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

2、分类

a、流套接字

流套接字使用的是TCP协议(传输控制协议),TCP协议有如下特点:

  • 有连接。
  • 可靠通信。
  • 面向字节流传输。
  • 全双工。
  • 传输数据的大小不受限制。

全双工指的是通信双方可以同时发送数据进行通信,还有半双工通信:通信双方不能同时发送数据,单工通信就是只能由一方一直发送数据。

b、数据报套接字 

数据报套接字使用的是UDP协议(用户数据报协议),UDP协议有如下的特点:

  • 无连接。
  • 不可靠。
  • 面向用户数据报传输。
  • 全双工。
  • 传输数据的大小有限制,一次最多传输64K。

c、原始套接字 

原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。

三、UDP数据报套接字编程 

UDP编程具有两个核心类:

DatagramSocket:是UDP版本的Socket对象,用于接收和发送数据报。有如下三个方法:

receive:发送数据。

send:发送数据。

close:释放资源。

DatagramPacket:表示一个UDP数据报,每次发送和接收的都是DatagramPacket对象。

服务器端代码实现:

整体流程:

  1. 首先定义一个DatagramSocket对象,用于收发数据。
  2. 定义一个指定端口号的构造方法,在构造方法中完成实例化对象。
  3. 定义start方法中,循环处理请求:定义DatagramPacket对象接收请求,处理请求后,再定义一个DatagramPacket对象发送数据。

  
  1. public class UDPEchoServer {
  2. //首先定义一个Socket对象
  3. private DatagramSocket datagramSocket = null;
  4. //构造方法:指定一个端口号
  5. public UDPEchoServer (int port) throws SocketException {
  6. datagramSocket = new DatagramSocket(port);
  7. }
  8. //启动服务器
  9. public void start () throws IOException {
  10. System.out.println( "服务器已经启动!");
  11. while( true){
  12. //数据报
  13. DatagramPacket requestPacket = new DatagramPacket( new byte[ 1024], 1024);
  14. //接收数据报
  15. datagramSocket.receive(requestPacket);
  16. //将接收到的数据报转成字符串
  17. String request = new String(requestPacket.getData(), 0,requestPacket.getLength(), "UTF-8");
  18. //处理数据,返回响应
  19. String response = process(request);
  20. //将响应返回给客户端
  21. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0,response.getBytes().length,
  22. requestPacket.getSocketAddress());
  23. datagramSocket.send(responsePacket);
  24. //展示
  25. System.out.println( "[客户IP"+responsePacket.getAddress()+ "客户端口"+responsePacket.getPort()+
  26. "请求"+request+ "响应"+response);
  27. }
  28. }
  29. //简单的回显服务来处理数据
  30. public String process (String request){
  31. return request;
  32. }
  33. public static void main (String[] args) throws IOException {
  34. UDPEchoServer udpEchoServer = new UDPEchoServer( 9090);
  35. udpEchoServer.start();
  36. }
  37. }

注意:

服务器的端口号一般是指定的,因为在客户端程序中需要指定服务器端的的端口号。

响应服务可以先别的,这里只是简单的回显服务。

客户端代码实现: 

整体流程:

  1. 首先定义一个DatagramSocket对象,用于收发数据。
  2. 定义一个构造方法指定服务器端的IP地址和端口号,并完成DatagramSocket对象实例。
  3. 定义start方法,循环发送请求,先定义DatagramPacket对象包装请求,发送到指定的服务器端,再定义一个DatagramPacket对象接收响应处理后的数据。

  
  1. public class UDPEchoClient {
  2. //定义Socket对象
  3. DatagramSocket datagramSocket = null;
  4. private int serverPort;
  5. private String serverIP;
  6. //构造方法时传入的是服务器端的IP地址和端口号
  7. public UDPEchoClient (int port,String IP) throws SocketException {
  8. datagramSocket = new DatagramSocket();
  9. serverPort = port;
  10. serverIP = IP;
  11. }
  12. //启动客户端
  13. public void start () throws IOException {
  14. Scanner sc = new Scanner(System.in);
  15. while ( true){
  16. System.out.println( "发送请求>");
  17. String request = sc.next();
  18. //发送UDP请求
  19. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
  20. InetAddress.getByName(serverIP),serverPort);
  21. //发送数据
  22. datagramSocket.send(requestPacket);
  23. //读取数据并进行解析
  24. DatagramPacket responsePacket = new DatagramPacket( new byte[ 1024], 1024);
  25. datagramSocket.receive(responsePacket);
  26. String response = new String(responsePacket.getData(), 0,responsePacket.getLength(), "UTF-8");
  27. //打印响应结果
  28. System.out.println(response);
  29. }
  30. }
  31. public static void main (String[] args) throws IOException {
  32. UDPEchoClient udpEchoClient = new UDPEchoClient( 2580, "127.0.0.1");
  33. udpEchoClient.start();
  34. }
  35. }

注意:
客户端程序一般需要系统自动分配,因为你不知道此时那个端口号没有被占用,而且客户端的个数一般多于服务器端的数目。

在客户端的构造方法中需要指定服务器端的IP地址和端口号。

在启动程序时,先启动服务器端,再启动应用器端。

例如:将回显服务改变成数据+收到。 


  
  1. public class UDPEChoServer2 extends UDPEchoServer {
  2. public UDPEChoServer2 (int port) throws SocketException {
  3. super(port);
  4. }
  5. public String process (String request){
  6. return request+ "收到";
  7. }
  8. public static void main (String[] args) throws IOException {
  9. UDPEChoServer2 udpeChoServer2 = new UDPEChoServer2( 2580);
  10. udpeChoServer2.start();
  11. }
  12. }

 运行结果:

四、TCP数据报套接字编程 

TCP编程的核心类:

ServerSocket:是服务器端的流套接字,创建实例对象时,可以绑定指定的端口,

accept方法:用于监听指定端口,有客户端连接的话就会返回一个Socket对象,基于该Socket对象与客户端建立连接,否则就会发生阻塞等待。

Socket:客户端与服务器端建立连接后,该类对象用于通信双方进行收发数据。创建实例对象时要绑定服务器端的IP地址和端口号。

有以下常用方法:

getInetAddress:返回套接字所连接的地址。

getInputStream:返回此套接字的输入流。

getOutputStream:返回此套接字的输出流。

服务器端代码实现

整体流程:

  1. 定义一个ServerSocket对象。
  2. 定义指定端口号的构造方法。
  3. 定义start方法来启动,创建Socket对象建立连接。
  4. 定义processConnection方法循环处理请求,先得到请求进行处理之后将结果返回给客户端。

  
  1. public class TCPEchoServer {
  2. //定义一个服务器对象
  3. ServerSocket serverSocket = null;
  4. public TCPEchoServer (int port) throws IOException {
  5. serverSocket = new ServerSocket(port);
  6. }
  7. //开始
  8. public void start () throws IOException {
  9. System.out.println( "服务器已经启动");
  10. while( true){
  11. //利用Socket对象来建立连接
  12. Socket clientSocket = serverSocket.accept();
  13. processConnection(clientSocket);
  14. }
  15. }
  16. private void processConnection (Socket clientSocket) {
  17. System.out.println( "[" + clientSocket.getInetAddress().toString() + " "+clientSocket.getPort()+ "]已经建立连接");
  18. try( InputStream inputStream = clientSocket.getInputStream()){
  19. try( OutputStream outputStream = clientSocket.getOutputStream()){
  20. //读取请求
  21. Scanner sc = new Scanner(inputStream);
  22. //循环处理请求
  23. while( true){
  24. if(!sc.hasNext()){
  25. System.out.println( "[" + clientSocket.getInetAddress().toString() + "]已经断开连接");
  26. break;
  27. }
  28. String request = sc.next();
  29. //对请求做出响应
  30. String response = process(request);
  31. //将响应返回给客户端
  32. PrintWriter printWriter = new PrintWriter(outputStream);
  33. printWriter.println(response);
  34. //刷新缓存区,使客户端尽快得到响应结果
  35. printWriter.flush();
  36. }
  37. }
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. private String process (String request) {
  43. return request;
  44. }
  45. public static void main (String[] args) throws IOException {
  46. TCPEchoServer tcpEchoServer = new TCPEchoServer( 9087);
  47. tcpEchoServer.start();
  48. }
  49. }

客户端代码实现

整体流程

  1. 定义一个Socket对象。
  2. 定义一个指定服务器的IP地址和端口号的构造方法,并完成对Socket对象的实例。
  3. 定义start方法,循环发送请求,然后接收响应。

  
  1. public class TCPEchoClint {
  2. Socket socket = null;
  3. public TCPEchoClint (int serverPort,String serverIP) throws IOException {
  4. //与指定的服务器端建立连接
  5. socket = new Socket(serverIP,serverPort);
  6. }
  7. //启动
  8. public void start (){
  9. System.out.println( "客户端已经连接到服务器端");
  10. Scanner sc = new Scanner(System.in);
  11. try( InputStream inputStream = socket.getInputStream()) {
  12. try( OutputStream outputStream = socket.getOutputStream()){
  13. //循环发送请求
  14. while( true){
  15. String request = sc.next();
  16. //发送请求
  17. PrintWriter printWriter = new PrintWriter(outputStream);
  18. printWriter.println(request);
  19. printWriter.flush();
  20. Scanner scanner = new Scanner(inputStream);
  21. String response = scanner.next();
  22. System.out.println( "response:"+response);
  23. }
  24. }
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. public static void main (String[] args) throws IOException {
  30. TCPEchoClint tcpEchoClint = new TCPEchoClint( 9087, "127.0.0.1");
  31. tcpEchoClint.start();
  32. }
  33. }

但是这样实现服务器端代码存在BUG, 当再启动一个客户端就连接不到服务器,为什么呢?

 

 

原因分析:在服务器端的start方法中,利用while循环来建立连接,并调用processConnection方法,但是在processConnection方法中又有一个while循环处理请求,这样只要一个客户端请求未结束,别的客户端就连接不到服务器端,这显然不符合实际。

解决方案:在start方法中创建线程,使多个客户端能并发连接到服务器端。


  
  1. public void start () throws IOException {
  2. System.out.println( "服务器已经启动");
  3. while( true){
  4. //利用Socket对象来建立连接
  5. Socket clientSocket = serverSocket.accept();
  6. Thread t = new Thread(
  7. ()-> processConnection(clientSocket)
  8. ) ;
  9. t.start();
  10. }
  11. }

转载:https://blog.csdn.net/m0_62404808/article/details/128772267
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场