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)选择合适方向的包装流。基本与节点流一致。当选择了多个包装流后,可以使用流之间的多层嵌套功能,不过流的嵌套在物理实现上是组合关系,因此彼此之间没有顺序。
Java Web 基础(一) 基于TCP的Socket网络编程的更多相关文章
- Python基础笔记系列十三:socket网络编程
本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!!使用python编写一个简易的服务端程序和客户端程序,启动服务端和客户端(监 ...
- Java网络编程三--基于TCP协议的网络编程
ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状体 Socket accept():如果接收到客户端的连接请求,该方法返回一个与客户端对应Socket ...
- 学习笔记——网络编程3(基于TCP协议的网络编程)
TCP协议基础 IP协议是Internet上使用的一个关键协议,它的全称是Internet Protocol,即Internet协议,通常简称IP协议. 使用ServerSocket创建TCP服务 ...
- 基于TCP协议的网络编程
TCP通信协议是一种可靠的传输层协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成虚拟网络链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信.Java使用Socke ...
- Socket网络编程详解
一,socket的起源 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的, 撰写者为Stephen Carr.Steve Crocker和Vi ...
- Socket网络编程基本介绍
一,socket的起源 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的, 撰写者为Stephen Carr.Steve Crocker和Vi ...
- 基于Socket网络编程
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a2011480169/article/details/73602708 博客核心内容: 1.Sock ...
- Java高级程序设计笔记 • 【第4章 网络编程】
全部章节 >>>> 本章目录 4.1 网络基础知识 4.1.1 IP地址 4.1.2 端口号 4.1.3 使用InetAddress 4.1.4 InetAddress 类 ...
- 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...
随机推荐
- 敏捷开发(Agile development)
敏捷开发(Agile development) 敏捷开发是一种以人为核心.迭代.循序渐进的开发方法.在敏捷开发中,软件项目的构建被切分成多个子项目,各个子项目的成果都经过测试,具备集成和可运行的特征. ...
- 微信支付配置参数:支付授权目录、回调支付URL
一.开通微信支付的首要条件是:认证服务号或政府媒体类认证订阅号(一般认证订阅号无法申请微信支付) 二.微信支付分为老版支付和新版支付,除了较早期申请的用户为老版支付,现均为新版微信支付. 三.公众平台 ...
- Golang自带的http包的路由规则问题
1.调用下面的方法开启一个http监听服务http.HandleFunc("/hello/", helloHandler)err := http.ListenAndServe(&q ...
- 关于alibaba.fastjson的一些简单使用
// 把JSON文本parse为JSONObject或者JSONArray public static final Object parse(String text); // 把JSON文本parse ...
- 深入理解jvm jdk1,7(1)
java 虚拟机管理的内存模型包含以下几个运行时数据区域: 程序计数器: 程序计数器是一块较小的内存空间,它可以看成当前线程执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能 ...
- a href="javascript:"与a href="#"
<a href="javascript:;"></a> <a href="#"></a> 这两种写法.这两种写法 ...
- js和jq获取父,兄弟,子节点
1,js获取节点: 父: parentNode 获取已知节点的父节点. 子: childNodes; 得到全部子节点 children 得到全部子节点 firstChild 获得第一个子节点 last ...
- 报错:'byte' does not name a type
这个错误是因为你在.cpp/.h中使用 byte 这个类型,把他修改成int就ok了
- LeetCode Number of 1 Bits 计算1的个数
题意: 提供一个无符号32位整型uint32_t变量n,返回其二进制形式的1的个数. 思路: 考察二进制的特性,设有k个1,则复杂度为O(k).考虑将当前的数n和n-1做按位与,就会将n的最后一个1去 ...
- 无效的 JSON 基元 解决办法
在AJAX中进行如下修改: 加入: dataType: "json", 移除: contentType: 'application/json', 然后检查参数名称,类型是否符合后台 ...