Java Web 基础(一) 基于TCP的Socket网络编程

1、Socket简单介绍java

  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

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

2、基于TCP的Socket通讯理论基础编码

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

  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)通讯完毕,关闭网络流

     通常而言,服务器端的流失不用关闭的,固然在某些条件下(好比服务器须要维护)也是须要关闭的。而客户端通常都须要关闭。

 

3、Socket异常类

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

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

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

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

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

 

4、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 }

服务器端结果:
复制代码

 

5、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的完善版本代码会在后续的文章中更新。
复制代码

 

6、心得体会

  上述代码实现的是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)选择合适方向的包装流。基本与节点流一致。当选择了多个包装流后,可使用流之间的多层嵌套功能,不过流的嵌套在物理实现上是组合关系,所以彼此之间没有顺序。

相关文章
相关标签/搜索