初识Socket通讯编程(一)

一、什么是socket?

  当两台计算机需要通信的时候,往往我们使用的都是TCP去实现的,但是并不会直接去操作TCP协议,通常是通过Socket进行tcp通信。Socket是操作系统提供给开发者的一个接口,通过它,就可以实现设备之间的通信。

二、TCP是如何通信的?

  TCP连接和断开分别会存在3次握手/4此握手的过程,并且在此过程中包含了发送数据的长度(接受数据的长度),无容置疑,这个过程是复杂的,这里我们不需要做深入的探讨。如果有兴趣,可以参考此文章,这里详细的解释了TCP通信的过程:

https://ketao1989.github.io/2017/03/29/java-server-in-action/

三、Socket消息的收发

  在Java中处理socket的方式有三种:

  1. 传统的io流方式(BIO模式),阻塞型;
  2. NIO的方式;
  3. AIO的方式;

  这里只介绍传统的IO流方式的tcp连接,即InputStream和OutputStream的方式读取和写入数据。对于长连接,通常情况可能我们如下做:

//<--------------服务端代码-------------------->
public class SocketReadLister implements Runnable {

    private final int tcpPort=9999;
    private ServerSocket serverSocket;

    @Override
    public void run() {
        try {
            serverSocket = new ServerSocket(this.tcpPort);
            while(true){
                Socket socket = serverSocket.accept();
                //socket.setSoTimeout(5*1000);//设置读取数据超时时间为5s
                new Thread(new SocketReadThread(socket)).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception{
        new Thread(new SocketReadLister()).start();
    }
}

public class SocketReadThread implements Runnable {
    private Socket socket;
    public SocketReadThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        byte[] data = new byte[1024];
        try {
            InputStream is=socket.getInputStream();
            int length=0;
            int num=is.available();
            while((length = is.read(data)) != -1){
                String result = new String(data);
                System.out.println("数据available:"+num);
                System.out.println("数据:"+result);
                System.out.println("length:" + length);
            }
            System.out.print("结束数据读取:"+length);
        }catch (SocketTimeoutException socketTimeoutException){
            try {
                Thread.sleep(2*1000);
            }catch (Exception e) {
                e.printStackTrace();
            }
            run();
        } catch (Exception e){
            e.printStackTrace();
            try {
                socket.close();
            }catch (IOException io){
                io.printStackTrace();
            }
        }
    }
}
//<---------------------客户端代码---------------------------->
public class SocketClient implements Runnable {
    private final int tcpPort=9999;
    private Socket socket;

    @Override
    public void run() {
        String msg = "ab23567787hdhfhhfy";

        byte[] byteMsg = msg.getBytes();

        try {
            socket = new Socket("127.0.0.1", 9999);
            OutputStream out = socket.getOutputStream();
            InputStream inputStream=socket.getInputStream();

            out.write(byteMsg);
            Thread.sleep(10*1000);
            char[] chars=msg.toCharArray();
            String str="";
            /*out.flush();*/
            for(int i=0;i<msg.length();i++) {
                str=chars[i]+"-"+i;
                out.write(str.getBytes());
                Thread.sleep(1*1000);
            }
            byte[] bytes=new byte[8];
            while(true) {
                if(inputStream.available()>0) {
                    if(inputStream.read(bytes)!=-1) {
                        System.out.println(new String(bytes));
                    }
                }
                Thread.sleep(10*1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                socket.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new SocketClient()).start();
    }

}

  正如代码中所示,通常情况下我们在while循环中将is.read(data)) != -1作为判断依据,判断是否继续读取,这种情况下,确实可以将数据完整的读取,但是客户端没有传输数据的时候,read()方法开始阻塞,直到有数据时才继续执行后续代码,使得程序挂起。

  为什么会出现这种情况呢?

  在JDK中,关于read()的说明如下:当读取到流的末尾,没有可读数据的时候,read()方法将返回-1,如果没有数据,那么read()将会发生阻塞。因此,在读取文件流的情况下,这样是完全正确的,但是在网络编程的情况下,socket连接不会断开,那么InputStream的read()将永远不会返回-1,程序将读完数据后,继续循环读取然后发生阻塞。

  在InputStream中,提供了available();此方法是非阻塞的,通过它可以初步的判定socket流中是否有数据,并返回一个预估数据长度的值,但是请注意,这里是预估,并不是准确的计算出数据的长度,所以在JDK说明文档中,有提示使用该方法获取的值去声明 byte[]的长度,然后读取数据,这是错误的做法。这样在每次读取数据之前,都可以先判断一下流中是否存在数据,然后再读取,这样就可以避免阻塞造成程序的挂起。代码如下:

while(true){
    if(is.available()>0){
        is.read(data);
    }
}

  说到read(),在InputStream中提供了3个read的重载方法:read()、read(byte[])、read(byte[],int offset,int len);后面两种读取方法都是基于 read()实现的,同样存在阻塞的特性,那么我们可以思考一下,假定byte[]的长度为1024,撇开while,拿read(byte[])一次性读取来说,当另一端发送的数据不足1024个字节时,为什么这个read(byte[])没有发生阻塞?

  关于这个问题,网上有帖子说,这跟InputStream的flush()有关,但经过测试,我不这么认为。我更加认同https://ketao1989.github.io/2017/03/29/java-server-in-action/中所说的那样,TCP握手期间,会传递数据的长度,当读取完数据,read()返回-1,即使此时没有读取到1024个字节数据,剩下的用0填充,这样就能很好的解释这个问题了。

  Socket既然时网络通讯用,那么由于各种原因,必然会有网络延迟,造成socket读取超时;socket读取超时时,其连接任然是有效的,因此在处理该异常时不需要关闭连接。以下是代码片段:

if (nRecv < nRecvNeed){
    int nSize = 0;
    wsaBuf=new byte[nRecvNeed-nRecv];
    int readCount = 0; // 已经成功读取的字节的个数
    try {
        while (readCount < wsaBuf.length) {
            //Thread.sleep(100);//读取之前先将线程休眠,避免循环时,程序占用CPU过高
            try {
                availableNum=inputStream.available();
                if(availableNum>0){
                    readCount += inputStream.read(wsaBuf, readCount, (wsaBuf.length - readCount));//避免数据读取不完整
                }
            }catch (SocketTimeoutException timeOut){
                System.out.println("读取超时,线程执行休眠操作,2秒后再读取");
                Thread.sleep(2*1000);
            }
        }
    }catch (Exception e){
        System.out.println("读取数据异常");
        e.printStackTrace();
        close();//关闭socket连接
        break;
    }
    nSize=wsaBuf.length;
    nRecv+=nSize;
}

  另外,需要补充说明的是,socket.close()方法执行后,只能更改本端的连接状态,不能将该状态通知给对端,也就是说如果服务端或客户端一方执行了close(),另一端并不知道此时连接已经断开了。

  此外,以上代码还存在一个很严重的问题亟待解决,这也是在开发中容易忽视的地方——程序能正常运行,但CPU占用过高;原因如下:

  当readCount < wsaBuf.length,即数据还未读取完整时,线程会持续不断的从socket流中读取数据,由于这里使用了inputStream.available()来判断使用需要读取数据,当没有数据传输的时候,此处就变成了一个死循环,说到此处,原因就非常明了了,在计算机运行过程中无论他是单核还是多核,系统获取计算机资源(CPU等)都是按照时间分片的方式进行的,同一时间有且只有一个线程能获取到系统资源,所以当遇到死循环时,系统资源一直得不到释放,因此CPU会越来越高,解决的办法是在循环中对程序进行线程休眠一定时间。

原文地址:https://www.cnblogs.com/onedayinMay/p/12203599.html

时间: 2024-10-09 02:58:46

初识Socket通讯编程(一)的相关文章

天地币:所用到的 Android Socket 通讯编程技术试验

1.为了开发"天地币"这个Android手机项目,须要用到Socket编程. 2.天地币是一种类似于比特币的虚拟货币. 3.为了赚取CSDN的C币,须要写篇博客. 4.干脆将调试Socket的项目发出来跟网友分享. 闲话休提,直接上代码,首先是字符串的定义: <? xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name&

[转] C#.Net Socket网络通讯编程总结

1.理解socket1).Socket接口是TCP/IP网络的应用程序接口(API).Socket接口定义了许多函数和例程,程序员可以用它们来开发TCP/IP网络应用程序.Socket可以看成是网络通信上的一个端点,也就是说,网络通信包括两台主机或两个进程,通过网络传递它们之间的数据.为了进行网络通信,程序在网络对话的每一端都需要一个Socket. 2).TCP/IP传输层使用协议端口将数据传送给一台主机的特定应用程序,从网络的观点看,协议端口是一个应用程序的进程地址.当传输层模块的网络软件模块

初识Socket通信:基于TCP和UDP协议学习网络编程

学习笔记: 1.基于TCP协议的Socket网络编程: (1)Socket类构造方法:在客户端和服务器端建立连接 Socket s = new Socket(hostName,port);以主机名和端口号作为参数来创建一个Socket对象. Socket s = new Socket(address,port);以InetAddress对象和端口号作为参数来创建一个Socket对象. 创建Socket对象时可能抛出UnknownHostException或IOException异常,必须捕获它们

socket 网络编程快速入门(一)教你编写基于UDP/TCP的服务(客户端)通信

因为UNIX和Win的socket大同小异,为了方便和大众化,这里先介绍Winsock编程. socket 网络编程的难点在入门的时候就是对基本函数的了解和使用,因为这些函数的结构往往比较复杂,参数大部分都是结构体,令人难以记忆和理解. 但是一旦我们知道这些函数包括其参数的具体含义,socket网络编程也就变得不是那么复杂.这里不赘述 具体函数的详细含义,网络上有很多的文章,同时笔者建议大家参考 MSDN,对返回值,参数等会有更好的理解. 以下均为单线程的简单实例,多线程的请关注下一篇文章. (

socket 网络编程

一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机. 而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构.即通信双方一方作为服务器等待客户提出请求并予以响应.客户则

Java Socket网络编程Server端详解

Socket通信:分为客户端和服务端的socket代码. Java SDK提供一些相对简单的Api来完成.对于Java而言.这些Api存在与java.net 这个包里面.因此只要导入这个包就可以开始网络编程了. 网络编程的基本模型就是客户机到服务器模型.简单的说就是两个进程之间相互通讯,然后其中一个必须提供一个固定的位置,而另一个则只需要知道这个固定的位置.并去建立两者之间的联系,然后完成数据的通讯就可以了.这里提供固定位置的通常称为服务器,而建立联系的通常叫做客户端.了解这个简单的模型,就可以

java socket 网络编程

一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机. 而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构.即通信双方一方作为服务器等待客户提出请求并予以响应.客户则

Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇

转载请注明出处: ,谢谢! 内容提要 本节主要学习网络通信基础,主要涉及的内容是: TCP/IP协议簇基础:两个模型 IPv4协议基础:IP地址分类与表示,子网掩码等 IP地址转换:点分十进制\二进制 TCP/IP协议簇基础 OSI模型 我们知道计算机网络之中,有各种各样的设备,那么如何实现这些设备的通信呢? 显然是通过标准的通讯协议,但是,整个网络连接的过程相当复杂,包括硬件.软件数据封包与应用程序的互相链接等等,如果想要写一支将联网全部功能都串连在一块的程序,那么当某个小环节出现问题时,整只

Java Socket网络编程的经典例子(转)

事实上网络编程简单的理解就是两台计算机相互通讯数据而已,对于程序员而言,去掌握一种编程接口并使用一种编程模型相对就会显得简单的多了,Java SDK提供一些相对简单的Api来完成这些工作.Socket就是其中之一,对于Java而言,这些Api存在与java.net 这个包里面,因此只要导入这个包就可以准备网络编程了. 网络编程的基本模型就是客户机到服务器模型,简单的说就是两个进程之间相互通讯,然后其中一个必须提供一个固定的位置,而另一个则只需要知道这个固定的位置.并去建立两者之间的联系,然后完成