zoukankan      html  css  js  c++  java
  • Java Web 基础(一) 基于TCP的Socket网络编程

    一、Socket简单介绍

      Socket通信作为Java网络通讯的基础内容,集中了异常、I/O流模式等众多知识点。学习Socket通信,既能够了解真正的网络通讯原理,也能够增强对I/O流模式的理解。

      1)Socket通信分类

        (一)基于TCP的Socket通信:使用流式套接字,提供可靠、面向连接的通信流。

        (二)基于UDP的Socket通信:使用数据报套接字,定义一种无连接服务,数据之间通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。

      2)Socket概念理解

      金山词霸中对Socket名词解释:插座、灯座、窝,引申到计算机科学称为"套接字"。至于为什么要翻译成"套接字",可以参考:https://www.zhihu.com/question/21383903/answer/18347271z对Socket历史较为详细考证。

      Socket曾经被翻译为"软插座",表明此处说的插座不是实际生活中的那种插座(硬插座),而是在计算机领域抽象出来的接口。如果在客户端插座和服务器端插座之间连一条线(也就是数据交互的信道),那么客户端就能够与服务器端进行数据交互。

    二、基于TCP的Socket通信理论基础

      基于TCP/IP协议的网络编程,就是利用TCP/IP协议在客户端和服务器端之间建立通信链接实现数据交换。 具体的编程实现步骤如下:

      1)服务器端创建其提供服务的端口号,即服务器端中提供服务的应用程序接口名称。

         服务器端ServerSocket: ServerSocket serverSocket = new ServerSocket(int port, int backlog);  ServerSocket作用是向操作系统注册相应协议服务,申请端口并监听这个端口是否有链接请求。其中port是端口号,backlog是服务器最多允许链接的客户端数。注册完成后,服务器分配此端口用于提供某一项进程服务。

      2)服务器端(Server)和客户端(Client)都创建各自的Socket对象。

          服务器端Socket:  Socket socket = serverSocket.accept();  服务器端创建一个socket对象用于等待客户端socket的链接(accept方法是创建一个阻塞队列,只有客户端socket申请链接到服务器后,服务器端socket才能收到消息) 。如果服务器端socket收到客户端的链接请求,那么经过"三次握手"过程,建立客户端与服务器端的连接。如果连接不成功,则抛出异常(详见模块三)。

              客户端Socket: Socket socket = new Socket(String host, int port);  客户端创建按一个socket对象用于链接具体服务器host的具体服务端口port,用于获得服务器进程的相应服务。

      经过三次握手后,一个Socket通路就建立起来。此时,服务器端和客户端就可以开始通讯了。

      3)服务器端和客户端打开链接到Socket通路的I/O流,按照一定协议进行数据通信。

        协议就是指发送与接受数据的编码格式(计算机网络中为:语义、同步)。简单说就是输入和输出的流必须匹配。

        开启网络输入流:网络输入流指的是从socket通道进入计算机内存的流。    socket.getInputStream();  返回值InputStream 输入字节流

        开启网络输出流:网络输出流指的是从计算机内存走出到socket通道的流。 socket.getOutputStream(); 返回值OutputStream 输出字节流

        为了通讯方便,往往将低级流包装成高级流进行服务端与客户端之间的交互。

      4)通信完毕,关闭网络流

         一般而言,服务器端的流失不用关闭的,当然在某些条件下(比如服务器需要维护)也是需要关闭的。而客户端一般都需要关闭。

    三、Socket异常类

      网络通讯中会遇到很多种错误,比如通讯中断、服务器维护拒绝访问等等。下面稍微总结一下Socket通讯中常见的异常类。

      1)java.net.SocketTimeoutException套接字超时异常。常见原因:网络通路中断,链接超时;

      2)java.net.UnknowHostException未知主机异常。常见原因:客户端绑定的服务器IP或主机名不存在;

      3)java.net.BindException绑定异常。常见原因:端口被占用;

      4)java.net.ConnectException连接异常。常见原因:服务器未启动,客户端申请服务;服务器拒绝服务,即服务器正在维护;

    四、Java建立Socket通讯

       1)服务器端与客户端建立连接

    复制代码
     1 package day05;
     2 
     3 import java.io.IOException;
     4 import java.net.ServerSocket;
     5 import java.net.Socket;
     6 
     7 /**
     8  * 服务器端
     9  * @author forget406
    10  *
    11  */
    12 public class Server {
    13     
    14     private ServerSocket serverSocket;
    15     
    16     /** 在操作系统中注册8000端口服务,并监听8000端口  */
    17     public Server() {
    18         try {
    19             /* public ServerSocket(int port, int backlog) 
    20              *  port表示端口号,backlog表示最多支持连接数  */
    21             serverSocket = new ServerSocket(8000, 3);
    22         } catch (IOException e) {
    23             e.printStackTrace();
    24         }
    25     }
    26     
    27     /** 与客户端交互  */
    28     public void start() {
    29         try {
    30             System.out.println("等待用户链接...");
    31             /* 创建Socket对象: public Socket accept() 
    32              * 等待客户端链接,直到客户端链接到此端口  */
    33             Socket socket = serverSocket.accept();
    34             System.out.println("链接成功,可以通讯!");
    35         } catch (IOException e) {
    36             // TODO Auto-generated catch block
    37             e.printStackTrace();
    38         }
    39     }
    40     
    41     public static void main(String[] args) {
    42         Server server = new Server();
    43         server.start();
    44     }
    45 }
    46 
    47 ==============================================
    48 
    49 package day05;
    50 
    51 import java.io.IOException;
    52 import java.net.Socket;
    53 import java.net.UnknownHostException;
    54 
    55 /**
    56  * 客户端
    57  * @author forget406
    58  *
    59  */
    60 public class Client {
    61     
    62     private Socket socket;
    63     
    64     /** 申请与服务器端口连接  */
    65     public Client() {
    66         try {
    67             /* 请求与服务器端口建立连接
    68              * 并申请服务器8000端口的服务*/
    69             socket = new Socket("localhost", 8000);
    70         } catch (UnknownHostException e) {
    71             e.printStackTrace();
    72         } catch (IOException e) {
    73             e.printStackTrace();
    74         }
    75     }
    76     
    77     /** 与服务器交互  */
    78     public void start() {
    79         
    80     }
    81     
    82     public static void main(String[] args) {
    83         Client client = new Client();
    84         client.start();
    85     }
    86 }

    服务器端结果:
    复制代码

    五、Java实现C/S模式Socket通讯

      1)客户端向服务器端发送消息(单向通信):服务器只能接受数据,客户端只能发送数据。这是由于socket绑定了从客户端到服务器的一条通信通路。

    复制代码
      1 package day05;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.io.InputStreamReader;
      7 import java.net.ServerSocket;
      8 import java.net.Socket;
      9 
     10 /**
     11  * 服务器端
     12  * @author forget406
     13  *
     14  */
     15 public class Server {
     16     
     17     private ServerSocket serverSocket;
     18     
     19     /** 在操作系统中注册8000端口服务,并监听8000端口  */
     20     public Server() {
     21         try {
     22             /* public ServerSocket(int port, int backlog) 
     23              *  port表示端口号,backlog表示最多支持连接数  */
     24             serverSocket = new ServerSocket(8000, 3);
     25         } catch (IOException e) {
     26             e.printStackTrace();
     27         }
     28     }
     29     
     30     /** 与客户端单向交互  */
     31     public void start() {
     32         System.out.println("等待用户链接...");
     33         try {
     34             /* 创建Socket对象: public Socket accept() 
     35              * 等待客户端链接,直到客户端链接到此端口  */
     36             Socket socket = serverSocket.accept();
     37             System.out.println("用户链接成功,开始通讯!");
     38             
     39             /* 服务器开始与客户端通讯  */
     40             while(true) {
     41                 // 开启服务器socket端口到服务器内存的网路输入字节流
     42                 InputStream is                    
     43                     = socket.getInputStream();  
     44                 // 在服务器内存中将网络字节流转换成字符流
     45                 InputStreamReader isr  
     46                     = new InputStreamReader(
     47                         is, "UTF-8"
     48                     );
     49                 // 包装成按行读取字符流
     50                 BufferedReader br
     51                     = new BufferedReader(isr);
     52                 
     53                 /* 中途网络可能断开
     54                  * 1)Windows的readLine会直接抛出异常
     55                  * 2)Linux的readLine则会返回null*/
     56                 String msg = null;
     57                 if((msg = br.readLine()) != null) {
     58                     System.out.println("客户端说:" +
     59                                         msg
     60                                       );
     61                 }
     62                 
     63             }
     64             
     65         } catch (IOException e) {
     66             System.out.println("链接失败");
     67             e.printStackTrace();
     68         }
     69     }
     70     
     71     public static void main(String[] args) {
     72         Server server = new Server();
     73         server.start();
     74     }
     75 }
     76 
     77 ===========================================
     78 
     79 package day05;
     80 
     81 import java.io.IOException;
     82 import java.io.OutputStream;
     83 import java.io.OutputStreamWriter;
     84 import java.io.PrintWriter;
     85 import java.net.Socket;
     86 import java.net.UnknownHostException;
     87 import java.util.Scanner;
     88 
     89 /**
     90  * 客户端
     91  * @author forget406
     92  *
     93  */
     94 public class Client {
     95     
     96     private Socket socket;
     97     
     98     /** 申请与服务器端口连接  */
     99     public Client() {
    100         try {
    101             /* 请求与服务器端口建立连接
    102              * 并申请服务器8000端口的服务*/
    103             socket = new Socket("localhost", 8000);
    104         } catch (UnknownHostException e) {
    105             e.printStackTrace();
    106         } catch (IOException e) {
    107             e.printStackTrace();
    108         }
    109     }
    110     
    111     /** 与服务器单向交互  */
    112     public void start() {
    113         try {
    114             // 开启客户端内存到客户端socket端口的网络输出流
    115             OutputStream os 
    116                 = socket.getOutputStream();
    117             // 将客户端网络输出字节流包装成网络字符流
    118             OutputStreamWriter osw
    119                 = new OutputStreamWriter(os, "UTF-8");
    120             // 将输出字符流包装成字符打印流
    121             PrintWriter pw 
    122                 = new PrintWriter(osw, true);
    123             // 来自键盘的标准输入字节流
    124             Scanner sc = new Scanner(System.in);
    125             while(true) {
    126                 // 打印来自键盘的字符串(字节数组)
    127                 pw.println(sc.nextLine());
    128             }
    129             
    130         } catch (IOException e) {
    131             e.printStackTrace();
    132         }
    133     }
    134     
    135     public static void main(String[] args) {
    136         Client client = new Client();
    137         client.start();
    138     }
    139 }

    客户端输入:
    服务器端结果:
    复制代码

       2)客户端与服务器端双向通信:客户端与服务器交互,能够实现服务器对客户端的应答,这更像是P2P模式。此时,双方socket端口均绑定来回一对通信通路。

    复制代码
      1 package day05;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.IOException;
      5 import java.io.InputStreamReader;
      6 import java.io.OutputStreamWriter;
      7 import java.io.PrintWriter;
      8 import java.net.ServerSocket;
      9 import java.net.Socket;
     10 import java.util.Scanner;
     11 
     12 /**
     13  * 服务器端
     14  * @author forget406
     15  *
     16  */
     17 public class Server {
     18     
     19     private ServerSocket serverSocket;
     20     
     21     /** 在操作系统中注册8000端口服务,并监听8000端口  */
     22     public Server() {
     23         try {
     24             /* public ServerSocket(int port, int backlog) 
     25              *  port表示端口号,backlog表示最多支持连接数  */
     26             serverSocket = new ServerSocket(8000, 3);
     27         } catch (IOException e) {
     28             e.printStackTrace();
     29         }
     30     }
     31     
     32     /** 与客户端单向交互  */
     33     @SuppressWarnings("resource")
     34     public void start() {
     35         System.out.println("等待用户链接...");
     36         try {
     37             /* 创建Socket对象: public Socket accept() 
     38              * 等待客户端链接,直到客户端链接到此端口  */
     39             Socket socket = serverSocket.accept();
     40             System.out.println("用户链接成功,开始通讯!");
     41                 
     42             /* 服务器接收客户端数据  */
     43             InputStreamReader isr 
     44                 = new InputStreamReader(
     45                     socket.getInputStream(),
     46                     "UTF-8"
     47                 );
     48             BufferedReader br 
     49                 = new BufferedReader(isr);
     50             String msgReceive = null;
     51             String msgSend    = null;
     52             
     53             /* 服务器向客户端发送数据  */
     54             OutputStreamWriter osw
     55                 = new OutputStreamWriter(
     56                     socket.getOutputStream(), 
     57                     "UTF-8"
     58                 );
     59             PrintWriter pw 
     60                 = new PrintWriter(osw, true);
     61             Scanner sc = new Scanner(System.in);
     62             
     63             while(true) {            
     64                 if((msgReceive = br.readLine()) != null) {
     65                     System.out.println("客户端说:" + msgReceive);
     66                 }
     67                 
     68                 if((msgSend = sc.nextLine()) != null) {
     69                     pw.println(msgSend);
     70                 }
     71             }
     72             
     73         } catch (IOException e) {
     74             System.out.println("链接失败");
     75             e.printStackTrace();
     76         }
     77     }
     78     
     79     public static void main(String[] args) {
     80         Server server = new Server();
     81         server.start();
     82     }
     83 }
     84 
     85 ============================================
     86 
     87 package day05;
     88 
     89 import java.io.BufferedReader;
     90 import java.io.IOException;
     91 import java.io.InputStreamReader;
     92 import java.io.OutputStreamWriter;
     93 import java.io.PrintWriter;
     94 import java.net.Socket;
     95 import java.net.UnknownHostException;
     96 import java.util.Scanner;
     97 
     98 /**
     99  * 客户端
    100  * @author forget406
    101  *
    102  */
    103 public class Client {
    104     
    105     private Socket socket;
    106     
    107     /** 申请与服务器端口连接  */
    108     public Client() {
    109         try {
    110             /* 请求与服务器端口建立连接
    111              * 并申请服务器8000端口的服务*/
    112             socket = new Socket("localhost", 8000);
    113         } catch (UnknownHostException e) {
    114             e.printStackTrace();
    115         } catch (IOException e) {
    116             e.printStackTrace();
    117         }
    118     }
    119     
    120     /** 与服务器单向交互  */
    121     @SuppressWarnings("resource")
    122     public void start() {
    123         try {
    124             /* 客户端向服务器发送数据  */
    125             OutputStreamWriter osw
    126                 = new OutputStreamWriter(
    127                     socket.getOutputStream(), 
    128                     "UTF-8"
    129                 );
    130             PrintWriter pw 
    131                 = new PrintWriter(osw, true);
    132             Scanner sc = new Scanner(System.in);
    133             
    134             /* 客户端接收服务器数据  */
    135             InputStreamReader isr 
    136                 = new InputStreamReader(
    137                     socket.getInputStream(),
    138                     "UTF-8"
    139                 );
    140             BufferedReader br 
    141                 = new BufferedReader(isr);
    142             String msgReceive = null;
    143             String msgSend    = null;
    144             
    145             while(true) {
    146                 if((msgSend = sc.nextLine()) != null) {
    147                     pw.println(msgSend);
    148                 }
    149                 if((msgReceive = br.readLine()) != null) {
    150                     System.out.println("服务器说:" + msgReceive);
    151                 }            
    152             }
    153             
    154         } catch (IOException e) {
    155             System.out.println("链接失败!");
    156             e.printStackTrace();
    157         }
    158     }
    159     
    160     
    161     public static void main(String[] args) {
    162         Client client = new Client();
    163         client.start();
    164         
    165     }
    166 }

    PS: 只是初步实现,有些bug没有改进。类似QQ的完善版本代码会在后续的文章中更新。
    复制代码

    六、心得体会

      上述代码实现的是C/S模型的简化版本,即P2P模式---客户端与服务器端一对一进行交互通信。事实上,服务器可以并行与多台客户机进行数据收发与交互,这需要运用到Java多线程的知识,这将会在后续文章中分析。

      I/O流模式的选取原则:

      1. 选择合适的节点流。在Socket网络编程中,节点流分别是socket.getInputStream和socket.getOutputStream,均为字节流。

       1.1)选择合适方向的流。输入流socket.getInputStream、InputStreamReader、BufferedReader;输出流socket.getOutputStream、OutputStreamWriter、PrintWriter。

         1.2)选择字节流和字符流。网络通信在实际通信线路中传递的是比特流(字节流);而字符流只会出现在计算机内存中。

      2. 选择合适的包装流。在选择I/O流时,节点流是必须的,而包装流则是可选的;节点流类型只能存在一种,而包装流则能存在多种(注意区分:是一种或一对,而不是一个)。

       2.1)选择符合功能要求的流。如果需要读写格式化数据,选择DataInputStream/DataOutputStream;而BufferedReader/BufferedWriter则提供缓冲区功能,能够提高格式化读写的效率。

           2.2)选择合适方向的包装流。基本与节点流一致。当选择了多个包装流后,可以使用流之间的多层嵌套功能,不过流的嵌套在物理实现上是组合关系,因此彼此之间没有顺序。

  • 相关阅读:
    Sql诊断之Explain
    Cenos7安装docker环境以及docker-compose
    uniapp苹果内购获取不到苹果的iap支付通道
    iOS云打包如何设置通用链接等Capabilities配置
    iOS应用id,套装id,appid,BundleID申请教程
    利用Appuploader在window上申请IOS开发所需要的证书及描述文件
    浅析GET和POST请求的本质区别以及关于get请求的长度限制到底是多少的问题
    iOS苹果开发者组织账号申请时的坑
    apache开源项目--HIVE
    [Unit testing Java] Unit testing Junit Controller
  • 原文地址:https://www.cnblogs.com/huangwentian/p/9196862.html
Copyright © 2011-2022 走看看