Java实现socket通信详解(UDP/TCP)c/s模式

在实现具体代码前,我们先来简单了解下TCP/UDP协议

TCP在OSI模型中位于传输层在网络层之上,故在端到端传输的基础上将数据以端口号等标识实现进程/终端设备应用的区分,将数据精准的传达。

TCP全称为传输控制协议具有以下特点:

  • 面向有连接的服务可靠的数据传输,即在通信前需建立连接进行一系列特定指令
  • 流量控制:对流量进行监视控制,以接收方的接收窗口反馈而确认
  • 拥塞控制:监视信道,当信道/带宽占用率升高时,限制数据的发送速度,以拥塞窗口反馈信息决策
  • TCP的报文格式:每行总长度32bit

    选项解释
  • 接收窗口:用于判断接收端的数据接收状态,即流量控制,共占用16bit
  • 确认号和序号:使得报文序列有序,于接收端对报文的差错校验(报文丢失,序列不一时快速丢弃/重传,具有回退N步(Go-Back-N)与选择重传),序号则可对冗余数据分组进行辨别,以便更快的进行差错恢复而无需等待超时定时器的响应(这样会使得效率降低)
  • 校验和:进行差错检验

简要概述一下可靠传输的流程(于流水线化下的可靠数据传输):

        一报文划分多个分组,每个分组都有序号,当一个分组到达接收方若成功则返回ACK

确认报文(暂不考虑其他差错)。序号的范围和对缓冲的要求取决于数据传输协议将如何处理

丢失、损坏、乱序及较大分组时如何延时处理等操作,故而就有了回退N步于选择重传协议(简单说一下二者都使用到了滑动窗口协议,通过发送窗口与接收窗口的不同长度则有累积确认(go-back-n)与选择确认(selective-repeat))。

TCP面向连接的重要特性:拥塞控制与流量控制

  • 拥塞控制具有其指定的拥塞避免算法(通过发\收方具有的一些指示器变量反馈信息即检测信道占用等设计的一套算法,这里不再深入)

再来解释一下建立连接的三次握手及关闭连接的四次挥手:

  • 三次握手建立连接:为保证连接的可靠性,首先客户端向服务端发送连接建立报文,后等待对方的ACK回应,再客户端发送ACK确认收到(此时双方都具有了发送并接收报文都成功事件,便可认为本次连接将是可靠的)

UDP协议的特点与通信机制:

  •    此为面向无连接的传输层协议,即无需通信前建立连接,报文格式:
  • 源端口与目的端口号:定位进程,通过udp数据包的形式封装。
  • 校验和与长度:udp校验和进行差错检验(仅进行简单的差错对比,保证端到端原则),因不能保证数据在传输途中的链路都提供差错检验机制,为避免引入比特差错则需自身需有一校验机制,在数据封装前进行校验和计算将结果封装到该字段。长度则为数据报大小。

总体与tcp比较而言:udp无连接状态首部开销小,故无需维护连接,因此传输速率/延时也将更快(上限取决于链路带宽),延迟也就更低,也因无差错回复机制对与数据丢失udp将不进行其他操作(如实时多媒体,很多都以udp方式进行传输以保证数据的实时性,然对某一块数据的丢失则进行等待下一块数据的到来,也就将丢失此处信息)


 TCP socket通信图示

 

 TCPClient.java的主例程代码

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
    public static void main(String[] args) {
        BufferedReader readSentence=null;
        DataOutputStream outToServer=null;
        BufferedReader inFromServer=null;
        try{
            readSentence=new BufferedReader(new InputStreamReader(System.in));
            Socket clientSocket=new Socket(InetAddress.getLocalHost(),12345);//客户端socket
            outToServer=new DataOutputStream(clientSocket.getOutputStream());
            String sentence=readSentence.readLine();
            outToServer.writeBytes(sentence);
            inFromServer=new BufferedReader(new 
            InputStreamReader(clientSocket.getInputStream()));
            clientSocket.shutdownOutput();
            String modifiedSentence=inFromServer.readLine();
            System.out.println("Modified data is :"+modifiedSentence);
            clientSocket.close();
            inFromServer.close();
        }catch (IOException e)
        {
            e.printStackTrace();
        }finally {
            try {
                if(readSentence!=null)
                {
                    readSentence.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
            try {
                if(outToServer!=null)
                {
                    outToServer.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

下面就对代码做些简要的解释

这里都使用到了io流,主要的java.net.Socket包为Socket套接字编程所依赖。

        BufferedReader readSentence=null;
        DataOutputStream outToServer=null;
        BufferedReader inFromServer=null;

        readSentence为一字符流这里将其置为null(抛异常使用try-catchf方式,也可直接throws)作为一输入缓冲流,接收键盘输入字符,outToServer则为Socket中获取的输出流(socket通信在stream-pipeline中交互)带着报文段打出去,inFromServer为Socket中的输入流接收服务端发来的报文。

    readSentence=new BufferedReader(new InputStreamReader(System.in));
    Socket clientSocket=new Socket(InetAddress.getLocalHost(),12345);
    outToServer=new DataOutputStream(clientSocket.getOutputStream());
    String sentence=readSentence.readLine();

        InputStreamReader(System.in)获取键盘输入放入到该流中转为字符流(相应的OutputStreamWriter()将流转为字节流) 初始化字符缓冲流readSentence,clientSocket为客户端socket进程需包含服务端主机号及进程端口号(这里用到了InetAddress下的静态方法getLocalHost获取本地ip地址,也可使用InetAddress.getByname(String host)指定主机地址返回为InetAddress对象直接作为对方主机地址)这里使用localhost可行是因为Client与Server进程都在本地,物理网卡都为同一。outToServer由clientSocket中的输出流初始化作为将发至服务器端的字节流。sentence存储键盘中输入的字符。

outToServer.writeBytes(sentence);//将数据写入到socket-stream中
inFromServer=new BufferedReader(new 
InputStreamReader(clientSocket.getInputStream()));//初始化输入流接收服务端数据
clientSocket.shutdownOutput();//暂停clientSocket下的输出流避免再此阻塞
String modifiedSentence=inFromServer.readLine();//接收服务端发来的数据
System.out.println("Modified data is :"+modifiedSentence);//打印发过来的数据
clientSocket.close();//关闭此socket
inFromServer.close();//此也可在finall中使用try-catch方式关闭

       建立了socket客户端后获取该进程的输入输出流与服务端进行交互(对流的操作都因关闭)。 

流程简要汇总

 TCPServer.java代码例程

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
    public static void main(String[] args) {
        ServerSocket welcomeSocket=null;
        BufferedReader readFromClient=null;
        DataOutputStream outToClient=null;
        try {
            welcomeSocket=new ServerSocket(12345);
            while (true){
                Socket clientSocket=welcomeSocket.accept();
                readFromClient=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                String fromClientSentences=readFromClient.readLine();
                clientSocket.shutdownInput();
                outToClient=new DataOutputStream(clientSocket.getOutputStream());
                System.out.println(fromClientSentences);
                outToClient.writeBytes(fromClientSentences+"is ACK");
                clientSocket.shutdownOutput();
                clientSocket.close();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(welcomeSocket!=null){
                    welcomeSocket.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
            try {
                if(readFromClient!=null){
                    readFromClient.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
            try {
                if(outToClient!=null){
                    outToClient.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

       大部分代码都与客户端相同,流程也类似,简单解释下不同点。

 ServerSocket welcomeSocket=null;
 BufferedReader readFromClient=null;
 DataOutputStream outToClient=null;

        这里的ServerSocket为连接前需保证可靠所需socket对象,及通信前进行三次握手,往后的两个流则作为接收与发送报文的流。

 welcomeSocket=new ServerSocket(12345);

       初始化该进程端口号,客户端端口号需与此保持一致即可连接。

Socket clientSocket=welcomeSocket.accept();

        建立一客户端的socket,由ServerSocket下的accept()方法进行侦听客户端socket连接后作为Socket的对象即可与对方交互。往后代码的操作都为获取该连接后的socket对象中的输入输出流进行操作,输入输出与客户端应保持读写对应(该socket-stream为单向流,半双工形式)否则将无法进行数据通信。


UDP通信的代码例程。

UDLClient.java代码部分:

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
    public static void main(String[] args) {
        BufferedReader inFromUser=null;
        DatagramSocket clientSocket=null;
        try{
            inFromUser=new BufferedReader(new InputStreamReader(System.in));
            InetAddress hostid=InetAddress.getLocalHost();
            clientSocket=new DatagramSocket();
            byte[] receiveSentence=new byte[1024];
            String sentence=new String(inFromUser.readLine());
            byte[] sendSentence=sentence.getBytes();
            DatagramPacket sendPackt=new DatagramPacket(sendSentence,0,sendSentence.length,hostid,8991);
            clientSocket.send(sendPackt);
            DatagramPacket recvPacket=new DatagramPacket(receiveSentence,receiveSentence.length);
            clientSocket.receive(recvPacket);
            String modifiedSentence=new String(recvPacket.getData(),0,recvPacket.getLength());
            System.out.println("Modified data is:"+modifiedSentence);
            clientSocket.close();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(inFromUser!=null){
                    inFromUser.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

接下来从头至尾解释一下代码的基本含义:

BufferedReader inFromUser=new BufferedReader(new InputStreamReader(System.in));
DatagramSocket clientSocket=clientSocket=new DatagramSocket();;

       这里依旧使用的了字符缓冲流来接收键盘的输入,而DatagramSocket则则作为使用UDP通信所需的主要api,正如该api名称数据报套接字(使用UDP协议通信传输的报文我们也称之为udp数据报)

 InetAddress hostid=InetAddress.getLocalHost();
 clientSocket=new DatagramSocket();
 byte[] receiveSentence=new byte[1024];
 String sentence=new String(inFromUser.readLine());
 byte[] sendSentence=sentence.getBytes();
 DatagramPacket sendPackt=new 
 DatagramPacket(sendSentence,0,sendSentence.length,hostid,8991);
 clientSocket.send(sendPackt);

       使用InetAddress获取或指定主机id,这里直接使用本地方式 ,而后建立两个字节型数组对象用来发送前与接收数据的存储(这里发送与接收的都为字节流,若sendSentence提取指定大小过大后封装进数据报时该数据报大小则为该字节数组的大小,进行数据打印时其余空间都默认为0,为了简便这里的大小直接以数据的大小而决定sentence.getBytes()),后续则建立一DatagramPacket对象使用其构造器初始化(DatagramPacket(databuffer,offset,end,hostid,port))使用数据报将数据封装指明对方的端口号及主机地址,最后使用DatagramSocket对象下的send方法将该数据报打出。

DatagramPacket recvPacket=new DatagramPacket(receiveSentence,receiveSentence.length);
clientSocket.receive(recvPacket);
String modifiedSentence=new String(recvPacket.getData(),0,recvPacket.getLength());
System.out.println("Modified data is:"+modifiedSentence);
clientSocket.close();

       与发送时相似,再次建立一数据报对象作为接收数据报是的存储空间,这里构造器仅使用了两参数(databuffer,datalegth)使用原本开辟的byte数组,后使用receive方法进行接收即可,最后则将获取的数据转为字符串输出以及关闭该socket。

UDPServer.java代码例程:

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

public class UDPServer {
    public static void main(String[] args) {
        DatagramSocket serverSocket=null;
        try {
            serverSocket=new DatagramSocket(8991);
            while (true){
                byte[] recvSentence=new byte[1024];
                byte[] sendSentence=new byte[1024];
                DatagramPacket receivePacket=new DatagramPacket(recvSentence,0,recvSentence.length);
                serverSocket.receive(receivePacket);
                String data=new String(receivePacket.getData(),0,receivePacket.getLength());
                System.out.println("Receive data:"+data);
                InetAddress IPadd=receivePacket.getAddress();
                int port=receivePacket.getPort();
                data+=" ACK";
                sendSentence=data.getBytes();
                DatagramPacket sendPacket=new DatagramPacket(sendSentence,sendSentence.length,IPadd,port);
                serverSocket.send(sendPacket);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(serverSocket!=null)
            {
                serverSocket.close();
            }
        }
    }
}
DatagramSocket serverSocket=new DatagramSocket(8991);

       首先客户端发送数据指明该端口故需一致(注意到在client端并未直接初始化端口,因udp协议特点仅需将端口号的信息封装进数据报之中即可)。

byte[] recvSentence=new byte[1024];
byte[] sendSentence=new byte[1024];
DatagramPacket receivePacket=new DatagramPacket(recvSentence,0,recvSentence.length);
serverSocket.receive(receivePacket);
String data=new String(receivePacket.getData(),0,receivePacket.getLength());
System.out.println("Receive data:"+data);

       这里的接收与客户端的接收例程类似,都是使用数据报进行封装发送/接收。

InetAddress IPadd=receivePacket.getAddress();
int port=receivePacket.getPort();
data+=" ACK";
sendSentence=data.getBytes();
DatagramPacket sendPacket=new DatagramPacket(sendSentence,sendSentence.length,IPadd,port);
serverSocket.send(sendPacket);

      发送时可直接通过receivePacket下的getAddress()与getPort()方法获取客户端的进程端口与主机地址,前者返回的类型为InetAddress后者则为int型 ,再往后将接收的数据拼接一下指定内容再转为字节型数组,最后依旧使用DatagramPacket进行封装(这里参数可有偏移量也可无需)及DatagramSocket的对象下的send方法将数据报打出。


总结:

        通篇而言无论是udp还是tcp协议的例程实现发送与接收方的代码逻辑及处理流程都类似,都需确定读\写对应。在udp方式中主要使用了DatagramSocket与DatagramPacket进行数据交互(只有一方发送时需指明对方socket进程端口号及主机地址,而接收只需提取建立一DatagramPacket对象进行数据的存储)。而在tcp方式中通篇都使用到了流进行交互(因需建立连接,而该socket-stream-pipeline则为进行可靠通信的管道,一些差错校验及差错恢复都由双方建立的该管道进行,故一旦建立连接则由双方进行维护该连接),若需多个服务端与客户端不同方向的读/写则可使用多线程进行实现。这里只是进行了简单的连接建立,可由此做其他数据交互。

注:tcp实现时使用到的流可以为其他,可按需求而定(因该socket获取的流为所有该输入/输出流的父类)

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 库卡机器人的以太网Socket通信是指在机器人控制器和远程设备之间通过以太网进行数据传输的一种通信方式。以下是库卡机器人以太网Socket通信详解步骤: 1、创建Socket连接:在机器人控制器上使用Socket API创建Socket连接。在Socket连接中,必须指定远程设备的IP地址和端口号。 2、建立数据通道:在Socket连接建立后,需要建立数据通道,通过该通道进行数据的传输。可以使用TCPUDP协议来实现数据传输,两者的区别在于TCP会提供可靠的数据传输,而UDP则更适合实现实时性要求高的数据传输。 3、传输数据:通过Socket连接建立的数据通道,可以进行双向数据传输,即机器人控制器可以向远程设备发送数据,同时也可以接收该设备发送的数据。在数据传输过程中,需要定义数据格式和结构,以确保数据能够被正确解析和处理。 4、关闭连接:当通信完成后,需要使用Socket API关闭Socket连接,以释放占用的资源。 总之,库卡机器人以太网Socket通信需要通过建立Socket连接、建立数据通道、传输数据和关闭连接四个步骤来实现。需要注意的是,在进行Socket通信时,需要确保机器人控制器和远程设备使用相同的协议、IP地址和端口号,否则通信将无法正常进行。 ### 回答2: 库卡机器人的以太网socket通信是其与外部设备进行数据传输的一种方式。其通信步骤如下: 第一步:建立连接。库卡机器人与外部设备首先需要建立连接,以确保双方能够相互交换信息。为此,库卡机器人需要获取外部设备IP地址和端口号,然后通过套接字(socket)函数建立起连接。 第二步:发送数据。建立连接后,库卡机器人就可以向外部设备发送数据。发送数据的步骤一般包括将数据打包、加上校验码等操作,以确保数据的可靠性。 第三步:接收数据。外部设备接收到库卡机器人发送的数据后,会进行处理,并向库卡机器人返回相应的数据。库卡机器人需要通过套接字函数监听,以接收外部设备发送回来的数据。 第四步:处理数据。库卡机器人接收到外部设备发送回来的数据后,需要进行解码、解压等操作,以便于获取有用的信息。然后,在处理完数据后,库卡机器人需要对其进行分析、判断并作出相应的响应。 第五步:断开连接。当通信结束后,库卡机器人需要通过套接字函数关闭连接,以及时释放相关资源。 因此,库卡机器人的以太网socket通信详解步骤依次为:建立连接、发送数据、接收数据、处理数据和断开连接。 ### 回答3: 库卡机器人的以太网socket通信详解步骤可以分为以下几步: 第一步,创建socket连接。在库卡机器人和控制端之间建立socket连接,可以使用TCPUDP协议。在建立连接时,需要指定库卡机器人的IP地址和端口号。 第二步,发送和接收数据。连接建立后,控制端可以向库卡机器人发送消息,这些消息通常包括控制指令、数据信息等等。库卡机器人接收到消息后,会进行相应的处理,并将处理结果发送回控制端。 第三步,协议解析。在进行socket通信时,需要遵循相应的通信协议。库卡机器人通常使用自有的协议进行通信,这些协议包括RC机器人通信协议、openrobotics通信协议等等。控制端需要对接收到的数据进行相应的解析,从而能够理解库卡机器人发送的消息。 第四步,异常处理。在进行socket通信时,可能会遇到连接失败、消息丢失、数据异常等问题。控制端需要进行相应的异常处理,保证通信稳定性和数据可靠性。 总的来说,socket通信是库卡机器人和控制端进行通信的重要手段之一。通过socket通信,控制端可以实时控制库卡机器人的运动、获取感应数据等等,从而实现精准的机器人控制和调度。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒风凋零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值