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

这篇具有很好参考价值的文章主要介绍了Java实现socket通信详解(UDP/TCP)c/s模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


 TCP socket通信图示

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

 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客户端后获取该进程的输入输出流与服务端进行交互(对流的操作都因关闭)。 

流程简要汇总

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

 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获取的流为所有该输入/输出流的父类)文章来源地址https://www.toymoban.com/news/detail-403392.html

到了这里,关于Java实现socket通信详解(UDP/TCP)c/s模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • socket概述 python中如何使用TCP/UDP协议实现通信-教程

    很多编程语言中,都 使用scoket套接字实现网络通信。 Socket是对TCP/IP协议的封装,Socket本身就是一个调用接口(API),方便程序员用Socket使用TCP/IP协议簇,实现网络通信。 不同编程语言,shiyongSocket通信的语法有所区别,但基本原理类型相似。 它的两种方式,分别是TCP和UDP协

    2024年02月13日
    浏览(42)
  • Java【网络编程2】使用 TCP 的 Socket API 实现客户端服务器通信(保姆级教学, 附代码)

    📕各位读者好, 我是小陈, 这是我的个人主页 📗小陈还在持续努力学习编程, 努力通过博客输出所学知识 📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽 📙 希望我的专栏能够帮助到你: JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统

    2024年02月05日
    浏览(65)
  • 网络通信(Socket/TCP/UDP)

    Socket(又叫套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接协议,客户端的IP地址,客户端的端口,服务器的IP地址,服务器的端口。 一个Socket是一对IP地址和端口。 Socket可以看

    2024年01月22日
    浏览(56)
  • 【socket编程】TCP服务器、UDP服务器、本地套接字【C语言代码实现】

    目录 0. 准备知识 0.1 大小端概念 0.2 网络字节序和主机字节序的转换 0.3 点分十进制串转换(IP地址转换函数) 0.4 IPV4结构体:(man 7 ip) 0.5 IPV6套接字结构体:(man 7 ipv6) 0.6 通用套接字结构体 1. 网络套接字函数 1.1 socket 1.2 connect 1.3 bind 1.4 listen 1.5 accept 1.6 端口复用 2. 包裹函

    2024年02月07日
    浏览(54)
  • C# Socket通信从入门到精通(12)——多个同步UDP客户端C#代码实现

    我们在开发Udp客户端程序的时候,有时候在同一个软件上我们要连接多个服务器,这时候我们开发的一个客户端就不够使用了,这时候就需要我们开发出来的软件要支持连接多个服务器,最好是数量没有限制,这样我们就能应对任意数量的服务器连接,由于我们开发的Udp客户

    2024年02月04日
    浏览(60)
  • C# Socket通信从入门到精通(14)——多个异步UDP客户端C#代码实现

    在之前的文章C# Socket通信从入门到精通(13)——单个异步UDP客户端C#代码实现我介绍了单个异步Udp客户端的c#代码实现,但是有的时候,我们需要连接多个服务器,并且对于每个服务器,我们都有一些比如异步发送、异步接收的操作,那么这时候我们使用之前单个异步Udp客户

    2024年02月03日
    浏览(81)
  • java实现UDP及TCP通信

    简介 UDP (User Datagram Protocol)用户数据报协议, TCP (Transmission Control Protocol) 传输控制协议,是传输层的两个重要协议。 UDP是一种 无连接、不可靠 传输的协议。其将数据源IP、目的地IP和端口封装成数据包,不需要建立连接,每个数据包的大小限制在64KB内;发送不管对方是否准

    2024年02月03日
    浏览(45)
  • tcp/udp socket 网络通信中超时时间的设置

    1.connect函数的超时时间设置只对TCP有效 UDP由于是无连接的connect都会返回success 有两种方法: 第一种方法 默认的socket是阻塞模式 我们只需要设置其为非阻塞模式,然后调用select去查询其状态 代码如下:  第二种是 默认其为阻塞模式  通过setsockopt 函数设置TCP_SYNCNT 值 头文件

    2024年02月15日
    浏览(42)
  • Linux TCP/UDP socket 通信和IO多路复用

    主机字节序 16 位值 == 网络字节序 16 位值 主机字节序 32 位值 == 网络字节序 32 位值 主机字节序的字符串IP地址  == 网络字节序的整形IP地址 将监听的套接字和本地IP和端口进行关联 给监听的套接字设置监听,开始检测客户端链接 等待并接受客户端的连接,阻塞函数,没有客

    2024年02月05日
    浏览(61)
  • 【Java网络编程】基于UDP-Socket 实现客户端、服务器通信

    ​ 哈喽,大家好~我是你们的老朋友: 保护小周ღ   本期为大家带来的是网络编程的 UDP Socket 套接字,基于 UDP协议的 Socket 实现客户端服务器通信 ,Socket 套接字可以理解为是,传输层给应用层提供的一组 API,如此程序,确定不来看看嘛~~ 本期收录于博主的专栏 : JavaEE_保

    2024年02月02日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包