IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP与UDP之Socket编程 -> 正文阅读

[网络协议]TCP与UDP之Socket编程

本文主要讲的是如何用Socket实现TCP和UDP基础上的服务器端和客户端通信。在代码上的通信流程和一些细节以及两者在代码上的区别和原因。

目录

一、Socket

二、TCP方式通信

服务器端

● 创建欢迎套接字socket

●?创建专属套接字socket?

●?读取数据流

●?发送数据流

●?关闭socket

客户端

●?创建socket

●?发数据流

●?收数据流

●?关闭socket

三、UDP方式通信

服务器端

●?创建socket

●?接收数据报

●?发送数据报

●?关闭socket

客户端

●?创建socket

●?发送数据报

●?接收数据报

●?关闭socket

四、UDP和TCP的区别


一、Socket

首先明确,Socket是一个本地的标识,只和本端有关系,其它端是不知道的。

同时Socket标识着本地ip的某个端口和其它端ip的某个端口的一个连接关系,因此这个socket里面有自己的ip和端口。

它就像本端的某一扇门一样,一扇标识的门,我们需要将我们的想要发送的数据从这扇门发出去,同时向本机端口发送的数据也需要通过这扇门收进来。这个是socket最主要的作用,就是收和发的作用,数据发出和接收都需要通过这个socket。当我们需要发出相关数据时,需要调用这个socket发出的相关方法。当我们需要接收数据时,我们也是需要调用这个socket的相关方法来接收的。

并且注意了socket标识的作用,就是当我们发消息给对方的时候,对方知道是我们发的,就是因为我们是通过这个socket发的,因为socket上面有端口号和ip地址。接也是同样道理的,网络这么大,怎么知道是哪个ip地址哪个端口来接呢?就是因为有socket在这里,当有消息发过来的时候,socket可以和发过来的消息对应上,然后通过对应的上的这个socket来接收消息。<---这里主要是udp的,但其实tcp也是一样的,只是没有特别显式而已。


简写步骤

注意这里都是一次性的操作,也就是一个客户端发过去然后收回来,没有实现同一个客户端向服务器多次发送的操作。tcp代码在SocketTest003

二、TCP方式通信

服务器端

● 创建欢迎套接字socket

1、创建欢迎套接字socket,其中需要传入ip地址和端口号,也就是握手的操作。注意这个socket只是一个本地的标识,是等着客户端来连接的。

●?创建专属套接字socket?

2、需要在欢迎套接字的基础调用accept方法,为发送端和自己创建一个专门的套接字socket,不需要传入ip地址和端口号了。然后利用这个专属socket来进行读取和发送的操作。

●?读取数据流

3、需要调用socket的getInputStream方法,然后就可以调用read方法就可以读取到客户端发送过来的数据了。这里我们使用了缓冲流BufferedReader来读取。然后记得最后需要关闭这个缓冲流,但是不能直接close,而是要调用shutdownInput()方法,这样只会关闭这个读的部分而不会直接关闭socket,而close会然后需要注意,read的时候如果没有数据发过来,是会阻塞的。

●?发送数据流

4、需要调用socket的getOutputStream方法,然后调用write方法就可以往外面写出,数据就写出去了,对方如果有调用read方法就接收的到了。这里我们使用了缓冲流BufferedWriter来写出。然后切记要调用一下flush方法将缓存里面的东西刷出去。最后记得需要关闭这个流,但是也不能直接close,而是要调用shutdownOutput()方法,这样只会关闭输出的部分而不会关闭socket。

●?关闭socket

直接调用socket的close方法将它们关闭。

客户端

●?创建socket

创建一个本地标识socket,里面传入ip地址和端口号port。然后就可以利用这个socket进行收数据流和发数据流的工作了。

●?发数据流

调用socket的getOutputStream()方法获取到输出流,然后调用write方法将数据写出去,然后还有很重要的一步就是需要这个输出流关闭掉才能算是真正写出去!但是注意这里不能直接调用writer的close方法,因为这样会直接把socket关闭掉,导致后面的用不了了。而是要先用flush,将缓冲区里面的刷出去,然后调用shutdownOutput()方法将输出流关闭,这样数据才算真正发出去了。

此处疑问:为什么一定要关闭输出流,数据才能发出去啊!?

●?收数据流

调用socket的getInputStream()方法获取输入流,然后调用read方法将数据读进来。最后调用shutdownInputStream()方法将输入流关闭。

●?关闭socket

最后调用socket的close方法将socket关闭掉。


三、UDP方式通信

服务器端

●?创建socket

1、首先创建一个socket作为本地的标识,这个socket里面有端口号,有ip地址(操作系统会帮忙赋值)。利用这个socket发出去的packet就会带有这个socket的信息也就是InetAddress和port。

●?接收数据报

2、然后事先准备好一个packet用于接收消息。

3、然后调用socket的receive方法,将packet传进去,packet就有数据了。packet里面有对方发送过来的数据,还有对方的ip地址和相应的端口号。

●?发送数据报

4、准备一个socket,在这个socket里面放置要发送的数据,要发送的地址,包括对方ip及其端口号。

●?关闭socket

5、最后关闭socket就行了

客户端

●?创建socket

1、创建一个属于客户端的socket,里面有端口号和ip地址,所有的收和发都是需要经过这个socket的。这里需要专门指定ip是因为和服务器区别,服务器那里是直接用系统的,而这里是指定的,所以两者不一样。

●?发送数据报

2、和上面发送数据包一样,创建packet,然后packet里面要有数据,对方端口号和对方ip地址。

3、然后调用send方法将packet传进去

●?接收数据报

4、也是和上面一样,需要预先准备好一个packet,然后调用receive方法

●?关闭socket

5、最后关闭socket

四、UDP和TCP的区别

1、TCP是面向连接的,UDP是无连接的,其实也就是要不要事先握手。这就造成TCP连接需要先创建一个“欢迎Socket”。而UDP是不用的。也就是TCP连接需要额外建立一个serverSocket。

2、建立在TCP上的连接,需要专门创建专属的socket来标志服务器端和客户端的连接关系。而UDP是不需要的。TCP需要在serverSocket的基础上建立一个专属的连接。Socket socket = serverSocket.accept( );

3、TCP发送和接收消息,发的和接的是数据流。而UDP发送和接收消息,发的和接的是数据报。流和报的区别需要找篇文章深究一下。而流Stream则需要利用socket来拿到流和发出流,xxx.read( )读进来和xxx.write( )写出去。并且有关闭流的操作,xxx.shutdownInput ( )和xxx.shutdownOutput( )。而报packet就是直接发和收的,socket.send(packet)和socket.receive( packet)。

4、向外发送时是否需要设置端口号和ip地址的区别:

????????4.1 使用TCP时是不用的,因为在创建“欢迎连接”的时候已经设置好了和服务器端连接的信息,然后又在accept那里设置好客户端连接的信息了。因此在发送流的时候无需再专门设置要发向哪里。

? ? ? ? 4.2 而再使用UDP时需要,从图像上面理解就是一去然后就没有了。就是这个消息我发过去,然后就没有了。因此发送数据报的时候,需要在packet那里标注好发往哪里,ip地址和端口号。而相反,TCP就是不用的,从图像上面看就是建立了一个连接,然后可以在这个欢迎连接的基础上的专属连接上面一直发来发去的,不需要指定某个位置。


?写的乱七八糟的,还是保留下来或许有用呢?

?三、UDP

2.1 UDP简介

相比于TCP的面向连接,UDP是无连接的,无连接其实指的就是两个端(在这里就是客户端和服务端)在建立连接之前不需要进行挥手,直接进行连接,因此这个UDP也是不可靠的连接。并且UDP因为是无连接的,所以不需要像TCP连接那样,专门建立一个socket连接来进行握手先。UDP直接就是客户端和服务器端进行连接了。

因此就是服务器端直接创建一个socket,然后这个socket就是守护在这个服务器端的某个端口上面等待客户端的请求,也就是利用这个服务器端的一个socket,就可以完成和所有客户端的连接,利用这个socket来进行对所有请求的收和发的工作。

2.2? 流程

本章节代码在socket_udp_001里面

服务器端

第一步?这里先编写服务器端的代码,首先服务器端需要创建一个socket,其中这个socket需要传入一个port 端口号参数,这样这个socket代表的就是这个主机中这个端口号的门,用于收和发与这个主机上这个端口号上的工作。发出去的数据报需要通过这个socket,收进来的socket也需要通过这个socket。

DatagramSocket datagramSocket = new DatagramSocket(7799);
//Constructs a datagram socket and binds it to the specified port 
//on the local host machine. The socket will be bound to the wildcard 
//address, an IP address chosen by the kernel.

第二步?准备收的工作,也就是客户端向这个服务器的这个端口发送消息,这个socket将它们收进来,利用预先准备的数据包来存数据。

因为UDP是数据包的发送和接收方式,所以这里需要先准备一个数据包来接收。然后将这个数据包放进receive方法里面,当有数据发送过来的时候就将收到的数据放在这个数据包的缓存上面,然后我们预先准备的这个数据包里面就有数据了,然后我们就可以读里面的数据了。(因为是缓存的是byte字节,所以我们需要转化为String)。

并且注意,发送过来的数据还包括了发送端的ip地址和端口号,因此这个数据包里面还包括了发送端的ip地址及其端口号。

然后还需要注意,这个receive方法是用来接数据报的,那么也就是说如果没有数据发送过来,那么这个receive方法就会一直处于待命状态,会阻塞住,直到有数据发过来的时候才会执行完这行代码。

 //【2】创建专门用来接收客户端发过来消息的数据包
    byte[] content01 = new byte[1024];
    DatagramPacket packet01 = new DatagramPacket(content01, content01.length);
    Log.d(TAG, "run: wait for request……");
 //【3】接收客户端发送过来的信息 都放在这个packet01里面了
    datagramSocket.receive(packet01);//这里会阻塞
 //【3.1】此处打印出客户端发送过来的消息
    String content_from_client = new String(content01, 0, packet01.getLength());
    Log.d(TAG, "run: 客户端发送过来的消息----->" + content_from_client);

 //DatagramPacket(content01, content01.length)
 //Constructs a DatagramPacket for receiving packets of length length.
 //The length argument must be less than or equal to buf.length.

 //receive( )
 //Receives a datagram packet from this socket. When this method returns, the 
 //DatagramPacket's buffer is filled with the data received. The datagram packet also 
 //contains the sender's IP address, and the port number on the sender's machine.

第三步?第三步就是服务器端向客户端发送消息了。同理,基于UDP就是用数据报packet的方式来收和发的,因此自然这里发消息也是需要用到数据包packet的。但是注意这里的构造数据包的传入参数和上面构造数据包的方式是不一样的,所以两者的作用也是不一样的,一个是收一个是发。

这里就是需要准备一个数据包,放置四个东西:对方ip地址,对方端口号,发送的内容,数据包的长度。然后调用socket的send方法将这个数据包发送出去。

//【4】发送消息给客户端 也是用一个数据包来发的(而不是流的方式)
   byte[] content02 = "你好客户端!".getBytes();
//【4.1】这里在这个发送包这里标明我们要发过去的ip号和端口号
   DatagramPacket packet02 = new DatagramPacket(content02, content02.length, packet01.getAddress(), packet01.getPort());
   Log.d(TAG, "run: 服务器准备发送消息");
   datagramSocket.send(packet02);

//public DatagramPacket(byte[] buf,int length,java.net.InetAddress address,int port)
//Constructs a datagram packet for sending packets of length length to the specified port 
//number on the specified host. The length argument must be less than or equal to buf.length.

//send方法
//Sends a datagram packet from this socket. The DatagramPacket includes information 
//indicating the data to be sent, its length, the IP address of the remote host, and the port 
//number on the remote host.

第四步?第四步就是非常常规的一步了,为了安全起见,需要关闭socket。当然一般因为我们是要让这个服务器的这个socket一直等待请求的,所以不需要直接关闭,除非不想让它请求了。

完整代码

以下是完整代码,这里和上面的变化就是加入了线程和一个while循环。线程是因为这是一个网络操作,肯定是需要联网的,所以需要开一个线程来专门处理这些东西。然后因为我们需要让这个这个服务器socket端口建立起来之后就一直处于待命状态以便不用反复开,因此就是利用一个while循环,每当处理完一个请求响应和发送之后就进行下一次等待循环响应之中。

package com.example.socket_udp_001;

import android.util.Log;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;


public class Server01 {

    private static final String TAG = "Server01";
    boolean flag = true;

    //创建服务器端的监听端口
    //数据报端口 不同于TCP需要创建一个欢迎端口在创建一个专属端口,
    //服务器端只需要有一个端口即可以响应所有的客户端请求
    public void createServerSocket() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //【1】创建服务器端数据包socket,用于等待客户端的请求
                //     守护在7799端口上,只要又客户端向这个端口发送请求,就是执行相关动作
                DatagramSocket datagramSocket = null;
                try {
                    datagramSocket = new DatagramSocket(7799);
                } catch (SocketException e) {
                    e.printStackTrace();
                }

                //【插曲】服务器只需要开启一次,然后就可以一直接收请求了
                // 因为UDP是无连接的,所以确实每一次都是需要创建一个包来接收发送过来的数据,
                // 不能将它们当成同一个连接,每一个都是不同的,UDP是无连接的!
                while (flag) {
                    try {
                        //【2】创建专门用来接收客户端发过来消息的数据包
                        byte[] content01 = new byte[1024];
                        DatagramPacket packet01 = new DatagramPacket(content01, content01.length);
                        Log.d(TAG, "run: wait for request……");

                        //【3】接收客户端发送过来的信息 都放在这个packet01里面了
                        Log.d(TAG, "run: 准备接收消息");
                        datagramSocket.receive(packet01);//这里会阻塞
                        //【3.1】此处打印出客户端发送过来的消息
                        String content_from_client = new String(content01, 0, packet01.getLength());
                        Log.d(TAG, "run: 客户端发送过来的消息----->" + content_from_client);

                        //【4】发送消息给客户端 也是用一个数据包来发的(而不是流的方式)
                        byte[] content02 = "你好客户端!".getBytes();
                        //【4.1】这里在这个发送包这里标明我们要发过去的ip号和端口号
                        DatagramPacket packet02 = new DatagramPacket(content02, content02.length, packet01.getAddress(), packet01.getPort());
                        Log.d(TAG, "run: 服务器准备发送消息");
                        datagramSocket.send(packet02);

                    } catch (SocketException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                //【5】关闭连接
                datagramSocket.close();
            }
        }).start();
    }
}

????????????????????????????????????????????????????????????????????????客户端

客户端其实和服务器端差不多,只不过客户端是先发然后再收的。原理也是一样,需要创建一个socket本地标识,利用这个socket来收和发。注意也是一样,发出去的时候利用packet包来发,收的时候利用packet来收。

因为socket中带有自己的ip地址和端口号,因此利用这个socket来发时,包里面其实就自动携带了ip地址和端口号了。然后收也是一样的,因为有ip地址和端口号来标识,所以当别人发过来的时候能够对应上。

完整代码如下:

package com.example.socket_udp_001;

import android.util.Log;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class Client {

    private static final String TAG = "Client";

    //客户端接口
    public void createClientSocket(){
        new Thread(()->{
            try {
                //【1】创建客户端的端口 注意是一个本地标识
                int port = 7788;
                InetAddress addr = InetAddress.getByName("127.0.0.1");
                DatagramSocket datagramSocket = new DatagramSocket(port,addr);

                //【2】创建一个数据包 用来发送数据给服务器
                byte[] content01 = "hello Server".getBytes();
                DatagramPacket packet01 = new DatagramPacket(content01,content01.length,addr,7799);
                Log.d(TAG, "createClientSocket: 准备发送数据给服务器……");
                datagramSocket.send(packet01);//android.os.NetworkOnMainThreadException

                //【3】创建一个数据包 用来收服务器发送过来的数据
                byte[] content02 = new byte[1024];
                DatagramPacket packet02 = new DatagramPacket(content02,content02.length);
                Log.d(TAG, "createClientSocket: 准备接收来自服务器的数据……");
                datagramSocket.receive(packet02);
                //【3.1】打印出收到的数据
                String content_from_server = new String(content02,0,packet02.getLength());
                Log.d(TAG, "createClientSocket:服务器发送过来的数据----> "+content_from_server);

                //【4】关闭socket
                datagramSocket.close();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-23 11:49:02  更:2021-09-23 11:49:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年6日历 -2024/6/27 1:45:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码