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

f6134744664a5ec158660f6be174d06a.png

二、基于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)通信完毕,关闭网络流

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

7bff6cc783dcd3e93e707c8af1ec7cea.png

三、Socket异常类

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

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

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

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

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

四、Java建立Socket通讯

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

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 }

服务器端结果:

e608bdb370ba98f70c2485f8274c41b2.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

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 }

客户端输入:

5571a95a1f3886faa775727a06cedf06.png

服务器端结果:

0612e7f40c7267af0ed53997125cccae.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

六、心得体会

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值