博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java Web 基础(一) 基于TCP的Socket网络编程
阅读量:5948 次
发布时间:2019-06-19

本文共 13903 字,大约阅读时间需要 46 分钟。

一、Socket简单介绍

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

  1)Socket通信分类

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

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

  2)Socket概念理解

  金山词霸中对Socket名词解释:插座、灯座、窝,引申到计算机科学称为"套接字"。至于为什么要翻译成"套接字",可以参考:对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 forget40610  *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 block37             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 forget40658  *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 osw119                 = 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 forget406101  *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 osw126                 = 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)选择合适方向的包装流。基本与节点流一致。当选择了多个包装流后,可以使用流之间的多层嵌套功能,不过流的嵌套在物理实现上是组合关系,因此彼此之间没有顺序

 

 注明:文章系作者原创,转载请注明出处 

转载于:https://www.cnblogs.com/forget406/p/5336748.html

你可能感兴趣的文章
Linux安装oracle出现过的问题和处理参考资料
查看>>
HTML5游戏开发-Box2dWeb应用(二)-碰撞以及各种连接
查看>>
SVG to Canvas在线转换工具
查看>>
我的友情链接
查看>>
python使用递归实现目录遍历
查看>>
android/java模拟登录正方教务系统
查看>>
iOS通过http post上传图片
查看>>
OpenStack Object Storage swift操作和实践
查看>>
伸展树的学习(三):源代码分析
查看>>
ES学习笔记之--ES的集群是如何组建起来的
查看>>
我的Linux生涯之YUM
查看>>
图书清单(看不完的电子书啊)
查看>>
Btrace
查看>>
瘦AP的初始化配置
查看>>
ONOS白皮书下篇之ONOS价值主张
查看>>
VSAN API 探索第 4 部分 – VSAN 磁盘映射
查看>>
从CALSSPATH加载properties文件
查看>>
asp.net GridView激发了未处理的事件“PageIndexChanging”的分析
查看>>
MPLS
查看>>
tar.xz文件如何解压
查看>>