网络编程Socket

网络分层


为了减少网络设计的复杂性

网络编程


通过套接字,达到进程间互相通信的目的。 Socket

科学家们把网络编程的基本模型使用 Java 语言实现好交给程序员,我们只需要在科学家提供的模型 Socket 中进行编写少量的代码就能编写网络程序。

网络编程要解决的问题:

  1. 如何无误的吧数据发送给指定的机器
  2. 如何传输数据
  3. 如何将数据发送给指定的应用

网络传输三要素:

  • ip
  • 端口
  • 协议

InetAddress ip 地址类

传输层协议 TCP


在网络编程中,需要设计出一个高效、可靠的多线程网络程序需要涉及的知识点太广泛了,不是随便什么人都能完成。

所有科学家经过长时间的设计改良后把网络编程封装好,提供出面向传输层协议的API

我们只需要针对不同的传输层协议进行网络编程即可,

  1. 面向无连接的 UDP 协议,传输效率高,数据完整性不可靠
    • 基于数据报包的协议,用于传输少量数据,速度快。
  2. 面向有链接的 TCP 协议,需要经过3次握手,建立连接后才能通讯,传输效率低,数据完整性可靠。
    • 传输可靠,传输大量数据(流模式),速度慢,建立连接需要开销。

在互联网程序中,这两种协议都很常见。

TCP 编程-客户端


三次握手(建立连接),四次挥手(断开连接)

往服务器发送数据,接受服务端返回的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void testCilent() throws Exception {
/**
* 步骤:
* 1. 创建一个连接某个服务端的 Socket 对象
* 2. 从 Socket 中拿到输出流往服务端发送数据
* 3. 调用 Socket对象 shutdownOutput 方法通知服务端数据发送完毕
* 4. 从 Socket 中拿到输入流获取服务端反馈的数据
* 5. 调用 Socket 对象的 shutdownInput 方法告诉服务器数据接收完毕
* 6. 关闭 Socket 对象
*/

// 1. 创建一个连接某个服务端的 Socket 对象
Socket socket = new Socket("127.0.0.1", 8888);

// 2. 从 Socket 中拿出输出流往服务端发送数据
OutputStream out = socket.getOutputStream();
out.write("你好服务器...".getBytes("UTF-8"));

// 3. 调用 Socket 对象的 shutdownOutput 方法告诉服务器数据发送完毕
socket.shutdownOutput();

// 4. 送 Socket 中拿到输入流获取服务端返回的数据
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
len = in.read(buffer);
while (len > 0) {
System.out.println(new String(buffer, 0, len, "UTF-8"));
}

// 5. 调用 Socket 对象的 shutdownInput 方法告诉服务器数据接收完毕
socket.shutdownInput();
socket.close();
}

TCP - 服务端


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 步骤:
* 1. 创建一个服务端对象(ServerSocket)
* 2. 侦听客户端的连接,获取和客户端通信的 Socket 对象
* 3. 从 Socket 获取输入流,接收服务器发送的数据
* 4. 调用 Socket 对象 shutdownInput 方法通知客户端数据接收完毕
* 5. 从 Socket 中拿到输出流往客户端发送数据
* 6. 调用 Socket 对象的 shutdownOutput 方法通知客户端发送数据完毕
* 7. 关闭 Socket 对象
*
* 注意: 服务端一般是长期开启的,因为一旦关闭,就不能给客户端提供服务了
*/
public static void main(String[] args) throws Exception {
// 1. 创建一个服务端对象 (ServerSocket)
ServerSocket ss = new ServerSocket();
// 2. 侦听客户端的连接,获取客户端通信的 Socket 对象
Socket socket = ss.accept();
// 3. 从 Socket 获取输入流,接收客户端发送的数据
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while (len > 0) {
System.out.println(new String(buffer, 0, len, "UTF-8"));
len = in.read(buffer);
}
// 4. 调用 Socket 对象 shutdownInput 方法通知客户端数据接收完毕
socket.shutdownInput();
// 5. 从 Socket 中拿到输出流往客户端发送数据
OutputStream out = socket.getOutputStream();
out.write("你也好!我是服务端...".getBytes("UTF-8"));
// 6. 调用 Socket 对象的 shutdownOutput 方法通知客户端数据发送完毕
socket.shutdownOutput();
// 7. 关闭 Socket 对象
socket.close();
}

TCP 三次握手


TCP 建立连接需要经过3次握手:

  1. 客户端找到服务端后发生链接意向
  2. 服务端收到客户端连接意向后,询问确认
  3. 客户端回复确认。

模拟 Tomcat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package cn.lizhaoloveit.socket;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
public class RequestHandler implements Runnable {
private Socket socket;
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String info = br.readLine();
while (info != null) {
if ("".equals(info)) {
break;
}
System.out.println(info);
info = br.readLine();
}
socket.shutdownInput();
PrintWriter out = new PrintWriter(socket.getOutputStream());
out.println("HTTP/1.1 200 OK");
out.println("Content-Type:text/html;charset=utf-8");
out.println();
out.println("<html><head></head><body>welcome to tomcat</body></html>");
out.flush(); // flush() 则要求立即将缓冲区的数据输出到接收方。
socket.shutdownOutput();
socket.close();

} catch (Exception e) {
// TODO: handle exception
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cn.lizhaoloveit.socket;

import java.net.ServerSocket;
import java.net.Socket;

public class TomcatMock {
public static void main(String[] args) throws Exception {

// 开启一个 Socket服务通信,端口号是 8080,意味着 8080端口被占用
ServerSocket ss = new ServerSocket(8080);
System.out.println("服务器启动成功");
// 一个无限 while 循环
while (true) {
/**
* 获取 Socket 对象,就是客户端和服务器通信链接的句柄
* ss.accept() 本身是个阻塞方法,他会监听端口的数据流,
*
*/
Socket socket = ss.accept();
/**
* RequstHandler 类拿到通信句柄,开启子线程去处理事务
* while(true) 保证应用一直不会结束,socket对象不会被
* 销毁,RequestHandler类 拿到 socket 每当客户端通过
* socket 对服务器发送请求时,就会根据请求作出响应
*/
new Thread(new RequestHandler(socket)).start();
}
}
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/07/07/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8BSocket/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论