socket异常分析

概述

java.net.BindException:Address already in use: JVM_Bind

问题原因

端口冲突。
这个问题出现的原因是服务器的端口不够用了,服务器总共有(0-65536)个端口。
异常的原因是服务端new ServerSocket(port)新建socket时,这个port已经占用并进行监听了。
此时可以使用netstat -an命令,查看这个端看到状态发现为Listending。出现这样的问题,只需要找一个没有被占用的端口就可以解决这个问题。

解决方案

查询端口对应的进程信息

使用netstat -anp | grep 2181 (这里的端口号,替换成被占用的端口号,比如tomcat 8080等)

[root]# netstat -anp|grep 7777
tcp        0      0 0.0.0.0:17777           0.0.0.0:*               LISTEN      484/nginx: worker p
tcp6       0      0 :::7777                 :::*                    LISTEN      24452/java
tcp6       0      0 192.168.109.145:7777    192.168.109.144:53537   TIME_WAIT   -

最后一排就是进程id(pid)。

查询进程信息

使用ps -ef | grep 7777

[root]# ps -ef|grep 24452
root     19401 17271  0 16:50 pts/3    00:00:00 grep --color 24452
root     24452  2199  7 Sep06 ?        02:01:53 /usr/java/jdk1.8/bin/java -jar xxx-SNAPSHOT.jar

这样可以看到这个进程到底是什么。
pts那个是只想ps -ef|grep 7777生成的,就好像每次执行jps的时候,就有jps这个进程。

杀掉进程

首先要确保这个进程可以被杀掉,千万不能谁便kill 进程。
kill -9 pid

kill -9 24452

编程注意事项

要正确区分长、短连接。
所谓的长连接就是一经建立就永久保持。
短连接使用的场景是使用完立即关闭:准备数据–>建立连接–>发送数据–>关闭连接。

java.net.SocketException: Connection refused: connect

问题原因

连接拒绝。
该异常发生在客户端进行new Socket(ip, port)操作时,原因是指定ip的地址不能找到(也就是ping 不通),或者ip存在但是指定的端口程序没有启动。
总之:TCP连接请求被拒绝是由于目标服务器上无对应的监听套接字(IP && PORT)。

解决方案

  1. 在客户端ping一下服务器看是否能ping通,如果能ping通(如果服务端把ping禁掉了需要使用其他方式),则在服务器上看指定的端口是否有程序启动,监听是否正常。

编程注意事项

这个问题主要是对长连接的维护。
所谓的维护包括两个方面,首先是检测对方的主动断连(即调用socket的close方法),其次是检测对方的宕机、异常退出、网络不同。

  • 检测对方断连
    一方主动断连,另一方如果再进行读操作,则此时的返回值为-1,一旦检测到对方断连,则应该主动关闭己方的连接(调用Socket的close方法)。
  • 检测对方宕机、异常退出、网络不通
    这些场景的检测通常用的方法是“心跳”。
    也就是双方周期性的发送数据给对方,同时也从对方接收“心跳”,如果联系几个周期都没有收到对方心跳,则可以判断对方或者宕机或者异常退出或者网络不通,此时也需要主动关闭己方连接。客户端可延迟一定时间后重新发起连接,也就是重试,可能可以正常建立连接了。

java.net.SocketException: Socket is closed

问题原因

sokcet被关闭。
该异常在客户端和服务器均有可能发生。
异常的原因是自己主动把socket关闭了(调用socket的close方法),然后自己再对网络连接进行读写操作。

解决方案

排查一下是否自己误操作把socket主动关闭了。

java.net.SocketException: (Connection reset或者Connect reset by peer:Socket write error)

问题分析

就是连接异常断开后,读和写操作引发的异常。
主要是对端的socket关闭了,本端没有感知到,继续对连接进行读写导致的。
该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个:

  1. 对端的socket被关闭(主动关闭或者因为异常退出而引起的关闭),本端没有感知到,发送第一个数据包引发该异常(connect reset by peer)。
  2. 对端退出,但退出时并未关闭该连接,本端如果继续从连接中读取数据则抛出异常(connection reset).

解决方案

服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭。

服务器关闭了某些连接,客户端感知不到继续读或者写都会报异常。
如果知道实际连接服务器的并发客户端数并没有超过服务器的承载量,则有可能是中了病毒或者木马,引起网络流量异常。
解决方法:可以使用netstat -an 命令查看网络连接情况。

客户端异常关闭导致

  1. 客户端关掉了浏览器或者浏览器按了stop,而服务器还在给客户端发送数据。
  2. 接收端主动关闭了,而服务端还在给客户端发送数据。
    这两种情况一般不会影响服务器,但是如果对异常信息没有特别处理,会在服务器的日志文件中重复出现改异常,造成服务器日志文件过大,影响服务器的运行。
    解决方法:可以对一起异常的部分,使用try … catch捕获异常,然后不输出或者只输出一句提示信息,避免输出全部异常信息。

防火墙的问题

如果网络连接通过防火墙,而防火墙一般都会有超时机制,在网络连接长时间不传输数据时,会关闭这个TCP的会话,关闭后客户端或服务器在读写都会导致异常。
解决方法:如果关闭防火墙,解决了问题,需要重新配置防火墙,或者自己编写程序实现TCP的长连接。实现TCP长连接,需要自己定义心跳协议,每隔一段时间,发送一次心跳协议,双方维持连接。

java.net.SocketException:Broken pipe

问题原因

该异常在客户端和服务器均有可能发送。
在上面的java.net.SocketException:Connect reset by peer:Socket write error异常产生后,如果继续写数据则抛出该异常。
也就是对端关闭了socket,本地继续写数据导致该异常。

java.net.SocketTimeoutException

问题分析

这个异常比较常见,socket超时。
一般有两个地方会抛出这个异常。

  1. connect的时间,这个超时时间由connect(SocketAddress endpoint, int timeout)中的timeout来决定,超过这个时间还连接不上服务器,就好报连接SocketTimeoutException。
  2. 读取socket中数据超时,这个超时时间由setSocketTimeout(int timeout)决定,超过这个读取时间,就会报SocketTimeoutException异常。

解决方案

connet,socketTimeout这两个时间按实际业务场景合理的设置即可。

java.net.SocketException:Too many open files

问题原因

操作系统中打开的文件的最大句柄数受限所致,常常发生在很多个并发用户访问服务器的时候。
因为为了执行每个用户的应用,服务器都要打开很多文件(new一个socket就需要一个文件句柄),这就会导致打开文件的句柄的缺乏。

解决方案

  • 尽量把类打成jar包,因为一个jar包只消耗一个文件句柄,如果不打包,一个类就消耗一个文件句柄。
  • java的GC不能关闭网络连接打开的文件句柄,因为如果没有执行socket.close(),则文件句柄将一直存在,不能被关闭。
  • 考虑设置socket的最大打开数来控制这个问题。
  • 操作系统做相关的设置,增加最大文件句柄数量,ulimit -a 可以查看系统目前资源现在,ulimit -n xxx可以修改,但是这个修改只对当前窗口有效。

Processing of multipart/form-data request failed. Unexpected EOF read on the socket异常分析

问题原因

当从输入流读取数据时,返回的读取字节数如果是-1的话,对于文件IO就表示文件读完了,也就是到了"end of file";如果是一个网络IO就表示输入流被关闭了。
再想想HTTP的知识,对于使用长连接的情况下,服务器通过网络输入流读取数据时,是通过请求头Content-Length来判断请求体是否被读取完的,意思就是服务器先会去解析请求头,
通过Content-Length就会知道请求体数据量的大小,然后再从输入流当中读取相应字节数的数据作为这个请求的请求体。
可以分析得出在正常情况下这个nRead是不应该为-1的,就是还有数据可以读出来,也就是还没有达到Content-Length所指定的数据量,然后输入流就关闭了。
就是服务器读数据的过程中读到了异常的文件结束标识(end of file),可能是客户端上传过程主动取消上传,或者网络不稳定导致数据流中断。
错误日志如下:
org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Unexpected EOF read on the socket,可以看出在copy文件流的过程中抛出IOException,猜想是文件流的获取有异常,在开发环境进行验证在上传文件的过程中取消文件的上传,错误重现。

解决方案

服务端报异常

  1. 该错误属于用户正常取消文件流操作,错误码直接屏蔽掉。
  2. 在tomcat之前添加缓存,例如在nginx中打开缓存,添加配置proxy_request_buffering on。

客户端报异常

上传文件还没到末尾就被客户端中断,是超出客户端的tomcat文件连接时间。
原因,客户端带宽可能太小,导致上传一个文件花费的时间太长,超过了客户端项目的tomcat的连接时间。
客户端tomcat配置修改:

server.connectionTimeout =180000 (客户端上传连接时长)

参考

TCP/IP:连接服务器失败(错误原因:Connection refused)
java.net.SocketException四大异常解决方案
客户端SpringBoot文件上传:java.io.EOFException: Unexpected EOF read on the socket
客户端文件上传错误:java.io.EOFException: Unexpected EOF read on the socket

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
事实上网络编程简单的理解就是两台计算机相互通讯数据而已,对于程序员而言,去掌握一种编程接口并使用一种编程模型相对就会显得简单的多了,Java SDK提供一些相对简单的Api来完成这些工作。Socket就是其中之一,对于Java而言,这些Api存在与java.net 这个包里面,因此只要导入这个包就可以准备网络编程了。   网络编程的基本模型就是客户机到服务器模型,简单的说就是两个进程之间相互通讯,然后其中一个必须提供一个固定的位置,而另一个则只需要知道这个固定的位置。并去建立两者之间的联系,然后完成数据的通讯就可以了,这里提供固定位置的通常称为服务器,而建立联系的通常叫做客户端,基于这个简单的模型,就可以进入网络编程啦。   Java对这个模型的支持有很多种Api,而这里我只想介绍有关Socket的编程接口,对于Java而言已经简化了Socket的编程接口。首先我们来讨论有关提供固定位置的服务方是如何建立的。Java提供了ServerSocket来对其进行支持.事实上当你创建该类的一个实力对象并提供一个端口资源你就建立了一个固定位置可以让其他计算机来访问你,ServerSocket server=new ServerSocket(6789);这里稍微要注意的是端口的分配必须是唯一的。因为端口是为了唯一标识每台计算机唯一服务的,另外端口号是从0~65535之间的,前1024个端口已经被Tcp/Ip 作为保留端口,因此你所分配的端口只能是1024个之后的。好了,我们有了固定位置.现在所需要的就是一根连接线了.该连接线由客户方首先提出要求。因此Java同样提供了一个Socket对象来对其进行支持,只要客户方创建一个Socket的实例对象进行支持就可以了。Socket client=new Socket(InetAddress.getLocalHost(),5678);客户机必须知道有关服务器的IP地址,对于着一点Java也提供了一个相关的类InetAddress 该对象的实例必须通过它的静态方法来提供,它的静态方法主要提供了得到本机IP 和通过名字或IP直接得到InetAddress的方法。   上面的方法基本可以建立一条连线让两台计算机相互交流了,可是数据是如何传输的呢?事实上I/O操作总是和网络编程息息相关的。因为底层的网络是继续数据的,除非远程调用,处理问题的核心在执行上,否则数据的交互还是依赖于IO操作的,所以你也必须导入java.io这个包.java的IO操作也不复杂,它提供了针对于字节流和Unicode的读者和写者,然后也提供了一个缓冲用于数据的读写。 BufferedReader in=new BufferedReader(new InputStreamReader(server.getInputStream())); PrintWriter out=new PrintWriter(server.getOutputStream());   上面两句就是建立缓冲并把原始的字节流转变为Unicode可以操作,而原始的字节流来源于Socket的两个方法,getInputStream()和getOutputStream()方,分别用来得到输入和输出,那么现在有了基本的模型和基本的操作工具,我们可以做一个简单的Socket例程了。   服务方:   import java.io.*;   import java.net.*;   public class MyServer {   public static void main(String[] args) throws IOException{   ServerSocket server=new ServerSocket(5678);   Socket client=server.accept();   BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));   PrintWriter out=new PrintWriter(client.getOutputStream());   while(true){    String str=in.readLine();    System.out.println(str);    out.println("has receive....");    out.flush();    if(str.equals("end"))     break;   }   client.close();   }   }   这个程序的主要目的在于服务器不断接收客户机所写入的信息只到,客户机发送"End"字符串就退出程序,并且服务器也会做出"Receive"为回应,告知客户机已接收到消息。   客户机代码:   import java.net.*;   import java.io.*;   public class Client{   static Socket server;   public static void main(String[] args)throws Exception{   server=new Socket(InetAddress.getLocalHost(),5678);   BufferedReader in=new BufferedReader(new InputStreamReader(server.getInputStream()));   PrintWriter out=new PrintWriter(server.getOutputStream());   BufferedReader wt=new BufferedReader(new InputStreamReader(System.in));   while(true){    String str=wt.readLine();    out.println(str);    out.flush();    if(str.equals("end")){     break;    }    System.out.println(in.readLine());   }   server.close();   }   }   客户机代码则是接受客户键盘输入,并把该信息输出,然后输出"End"用来做退出标识。   这个程序只是简单的两台计算机之间的通讯,如果是多个客户同时访问一个服务器呢?你可以试着再运行一个客户端,结果是会抛出异常的。那么多个客户端如何实现呢? 其实,简单的分析一下,就可以看出客户和服务通讯的主要通道就是Socket本身,而服务器通过accept方法就是同意和客户建立通讯.这样当客户建立Socket的同时。服务器也会使用这一根连线来先后通讯,那么既然如此只要我们存在多条连线就可以了。那么我们的程序可以变为如下:   服务器:   import java.io.*;   import java.net.*;   public class MyServer {   public static void main(String[] args) throws IOException{   ServerSocket server=new ServerSocket(5678);   while(true){    Socket client=server.accept();    BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));    PrintWriter out=new PrintWriter(client.getOutputStream());    while(true){     String str=in.readLine();     System.out.println(str);     out.println("has receive....");     out.flush();     if(str.equals("end"))      break;    }    client.close();   }   }   }   这里仅仅只是加了一个外层的While循环,这个循环的目的就是当一个客户进来就为它分配一个Socket直到这个客户完成一次和服务器的交互,这里也就是接受到客户的"End"消息.那么现在就实现了多客户之间的交互了。但是.问题又来了,这样做虽然解决了多客户,可是是排队执行的。也就是说当一个客户和服务器完成一次通讯之后下一个客户才可以进来和服务器交互,无法做到同时服务,那么要如何才能同时达到既能相互之间交流又能同时交流呢?很显然这是一个并行执行的问题了。所以线程是最好的解决方案。   那么下面的问题是如何使用线程.首先要做的事情是创建线程并使得其可以和网络连线取得联系。然后由线程来执行刚才的操作,要创建线程要么直接继承Thread要么实现Runnable接口,要建立和Socket的联系只要传递引用就可以了.而要执行线程就必须重写run方法,而run方法所做的事情就是刚才单线程版本main所做的事情,因此我们的程序变成了这样:   import java.net.*;   import java.io.*;   public class MultiUser extends Thread{   private Socket client;   public MultiUser(Socket c){   this.client=c;   }   public void run(){   try{    BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));    PrintWriter out=new PrintWriter(client.getOutputStream());    //Mutil User but can parallel    while(true){     String str=in.readLine();     System.out.println(str);     out.println("has receive....");     out.flush();     if(str.equals("end"))      break;    }    client.close();   }catch(IOException ex){   }finally{   }   }   public static void main(String[] args)throws IOException{   ServerSocket server=new ServerSocket(5678);   while(true){   //transfer location change Single User or Multi User   MultiUser mu=new MultiUser(server.accept());   mu.start();   }   }   }   我的类直接从Thread类继承了下来.并且通过构造函数传递引用和客户Socket建立了联系,这样每个线程就有了。一个通讯管道.同样我们可以填写run方法,把之前的操作交给线程来完成,这样多客户并行的Socket就建立起来了。   以上的代码使用的是 BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter out=new PrintWriter(client.getOutputStream());   还有一种方法是使用 DataInputStream isFromClient = new DataInputStream(client.getInputStream()); DataOutputStream osToClient = new DataOutputStream(client.getOutputStream());   关于这两种输入输出流的不同,我也只知道前一种对字符串支持比较好,后面对于读取一个字符串需要处理,但是可以支持很多种类型的输出。对于传递字符串而言前一种应该是很好的选择了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

融极

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

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

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

打赏作者

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

抵扣说明:

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

余额充值