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)选择合适方向的包装流。基本与节点流一致。当选择了多个包装流后,可使用流之间的多层嵌套功能,不过流的嵌套在物理实现上是组合关系,所以彼此之间没有顺序。
注明:文章系做者原创,转载请注明出处