TCP/IP协议以及UDP

接收端和发送端
在⼀次网络数据传输时:
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
注意:发送端和接收端只是相对的,只是⼀次网络数据传输产生数据流向后的概念。

客户端和服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
客户端:获取服务的一方进程,称为客户端。

客户端和服务端交流过程
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

客户端先发送请求到服务端
服务端根据请求数据,执行相应的业务处理
服务端返回响应:发送业务处理结果
客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果). 
一: TCP协议
传输层TCP协议
TCP,即Transmission Control Protocol(传输控制协议),传输层协议

1.1:TCP协议的六大特性
有连接
双全工
可靠传输
面向字节流
有接收缓冲区,也有发送缓冲区
大小不限
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的
情况下,是无边界的数据,可以多次发送,也可以分开多次接收

1.2:Socket
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服
务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据
的。

⽅法签名    ⽅法说明
Socket(String host,int port)    创建⼀个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
Socket 方法:

⽅法签名    ⽅法说明
InetAddress getInetAddress()    返回套接字所连接的地址
InputStream getInputStream()    返回此套接字的输入流
OutputStream getOutputStream()    返回此套接字的输出流
1.3:ServerSocket
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 构造方法:

⽅法签名    ⽅法说明
ServerSocket(int port)    创建⼀个服务端流套接字Socket,并绑定到指定端口
ServerSocket 方法:

⽅法签名    ⽅法说明
Socket accept()    开始监听指定端口(创建时绑定的端口),有客户端连接后,返回⼀个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()    关闭此套接字
1.4:TCP的实现
客户端和服务端:开发时,经常是基于⼀个主机开启两个进程作为客户端和服务端,但真实的场
景,⼀般都是不同主机。
注意目的IP和目的端口号,标识了⼀次数据传输时要发送数据的终点主机和进程
Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议,
也需要考虑,这块我们在后续来说明如何设计应用层协议。
关于端口被占用的问题
如果⼀个进程A已经绑定了⼀个端口,再启动⼀个进程B绑定该端口,就会报错,这种情况也叫端口
被占用。对于java进程来说,端口被占用的常见报错信息如下:

TCP Echo Server服务器
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    // 这个操作就会绑定端⼝号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }
    // 通过这个⽅法来处理⼀个连接的逻辑.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客⼾端上线!\n", clientSocket.getInetAddress().t
// 接下来就可以读取请求, 根据请求计算响应, 返回响应三步⾛了.
// Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
// ⼀次连接中, 可能会涉及到多次请求/响应
            while (true) {
// 1. 读取请求并解析. 为了读取⽅便, 直接使⽤ Scanner.
               Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
// 读取完毕, 客⼾端下线.
                    System.out.printf("[%s:%d] 客⼾端下线!\n", clientSocket.getIne
                    break;
                }
// 这个代码暗含⼀个约定, 客⼾端发过来的请求, 得是⽂本数据, 同时, 还得带有
                String request = scanner.next();
// 2. 根据请求计算响应
                String response = process(request);
// 3. 把响应写回给客⼾端. 把 OutputStream 使⽤ PrinterWriter 包裹⼀下
                PrintWriter writer = new PrintWriter(outputStream);
// 使⽤ PrintWriter 的 println ⽅法, 把响应返回给客⼾端.
// 此处⽤ println, ⽽不是 print 就是为了在结尾加上 \n . ⽅便客⼾端
                writer.println(response);
// 这⾥还需要加⼀个 "刷新缓冲区" 操作.
                writer.flush();
// ⽇志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.ge
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
// 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
TCP Echo Client客服端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class TcpEchoClient {
    private Socket socket = null;
    // 要和服务器通信, 就需要先知道, 服务器所在的位置.
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 这个 new 操作完成之后, 就完成了 tcp 连接的建⽴.
        socket = new Socket(serverIp, serverPort);
    }
    public void start() {
        System.out.println("客⼾端启动");
        Scanner scannerConsole = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
// 1. 从控制台输⼊字符串.
                System.out.print("-> ");
                String request = scannerConsole.next();
// 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
// 使⽤ println 带上换⾏. 后续服务器读取请求, 就可以使⽤ scanner.n
                printWriter.println(request);
// 不要忘记 flush, 确保数据是真的发送出去了!!
                printWriter.flush();
// 3. 从服务器读取响应.
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
// 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

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
42
43
二: UDP协议
传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。

2.1:UDP协议的六大特特性
无连接
双全工
不可靠传输
面向数据报
有接收缓冲区,无发送缓冲区
大小受限:⼀次最多传输64k
对于数据报来说,可以简单的理解为,传输数据是⼀块⼀块的,发送⼀块数据假如100个字节,必须⼀
次发送,接收也必须⼀次接收100个字节,而不能分100次,每次接收1个字节。

2.2:DatagramSocket
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 构造方法

⽅法签名    ⽅法说明
DatagramSocket()    创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端⼝(⼀般⽤于客⼾端)
DatagramSocket(int port)    创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端⼝(⼀般⽤于服务端)
DatagramSocket 方法

⽅法签名    ⽅法说明
void receive(DatagramPacket p)    从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待)
void send(DatagramPacket p)    从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()    关闭此数据报套接字
2.3:DatagramPacket
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:

⽅法签名    ⽅法说明
DatagramPacket(byte[]buf,int length)    构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length)
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address)    构造⼀个DatagramPacket以用来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定长度(第⼆个参数length)。address指定⽬的主机的IP和端⼝号
DatagramPacket 方法:

⽅法签名    ⽅法说明
InetAddress getAddress()    从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()    从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端⼝号
byte[] getData()    获取数据报中的数据构造UDP发送的数据报时
2.4:UDP的实现
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且⼀次发送全部
数据报,⼀次接收全部的数据报。
java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket 作为发送或接收的UDP数据报。对于⼀次发送及接收UDP数据报的流程如下:


UDP Echo Server服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    private DatagramSocket socket = null;
    // 参数是服务器要绑定的端⼝
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    // 使⽤这个⽅法启动服务器.
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
// 反复的, ⻓期的执⾏针对客⼾端请求处理的逻辑.
// ⼀个服务器, 运⾏过程中, 要做的事情, 主要是三个核⼼环节.
// 1. 读取请求, 并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 40
                    socket.receive(requestPacket);
// 这样的转字符串的前提是, 后续客⼾端发的数据就是⼀个⽂本的字符串.
            String request = new String(requestPacket.getData(), 0, requestPacke
// 2. 根据请求, 计算出响应
                    String response = process(request);
// 3. 把响应写回给客⼾端
// 此时需要告知⽹卡, 要发的内容是啥, 要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
// 记录⽇志, ⽅便观察程序执⾏效果.
          System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAd
                    request, response);
        }
    }
    // 根据请求计算响应. 由于是回显程序, 响应内容和请求完全⼀样.
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

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
42
43
UDP Echo Client客服端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;
    // 服务器的 ip 和 服务器的端⼝.
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
// 这个 new 操作, 就不再指定端⼝了. 让系统⾃动分配⼀个空闲端⼝.
        socket = new DatagramSocket();
    }
    // 让这个客⼾端反复的从控制台读取⽤⼾输⼊的内容. 把这个内容构造成 UDP 请求, 发给服务器
// 最终再显⽰在客⼾端的屏幕上.
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客⼾端启动!");
        while (true) {
// 1. 从控制台读取⽤⼾输⼊的内容
            System.out.print("-> "); // 命令提⽰符, 提⽰⽤⼾要输⼊字符串.
            String request = scanner.next();
// 2. 构造请求对象, 并发给服务器.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes()
                    InetAddress.getByName(serverIp), serverPort);
           socket.send(requestPacket);
// 3. 读取服务器的响应, 并解析出响应内容.
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4
                    socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePa
// 4. 显⽰到屏幕上.
                    System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
// UdpEchoClient client = new UdpEchoClient("42.192.83.143", 9090);
        client.start();
    }
}
1
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/smile_sundays/article/details/136081564

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值