Java网络编程精解笔记2:Socket详解

来源:互联网 发布:ios手机编程就业 编辑:程序博客网 时间:2024/05/17 08:31

Socket用法详解
     在C/S通信模式中,client需要主动创建于server连接的Socket(套接字).服务器端收到了客户端的连接请求,也会创建与客户连接的Socket.Socket可看做是通信两端的收发器.server与client都通过Socket来收发数据.
 
1.构造Socket

 1.Socket()
 2.Socket(InetAddress address,int port) throws UnknownHostException,IOException
 3.Socket(InetAddress addrss,int port,InetAddress localAddr,int localPort) throws IOException
 4.Socket(String host,int port) throws UnknownHostException,IOException
 5.Socket(String host,int port,InetAddress localAddr,int localPort) throws IOExcception

 除了第一个不带参数的构造方法外,其他构造方法都会试图建立与服务器的连接.如果连接成功,就返回Socket对象.如果因为某些原因连接失败,则抛出IOException.
 
2.设定等待建立连接的超时时间

 1.客户端的Socket构造方法请求与server连接时,可能要等待一段时间.默认会一直等待下去,直到连接成功或者出现异常.Socket构造方法请求连接时,受底层网络传输速度的影响,可能处于长时间的等待状态.
 ->希望限定等待连接的时间
 ->Socket socket = new Socket();
    SocketAddress rermoteAddr = new InetSocketAddress("localhost",8000);
    socket.connect(remoteAddr,60000);//设置等待建立连接的超时时间为1分钟
 ->如果1分钟之内连接成功,则connect顺利返回.如果1分钟之内出现异常,则抛出该异常.如果超过了1分钟,即没有连接成功,也没有出现其他异常.则会抛出
 SocketTimeoutException

 2.Socket#connect(SocketAddress endpoint,int timeout).endpoint为服务器的地址,timeout设定超时时间.ms->
  timeout为0,表示永远不会超时.
  
3.设定服务器的地址.

  Socket(InetAddress address,int port)
  Socket(String host,int port)
 InetAddress表示服务器的IP地址.->该类提供了一系列的静态工厂方法.用于构造自身的实例.如
  1.InetAdress addr1 = InetAdress.getLocalHost();
  2.InetAddress addr2 = InetAddress.getByName("10.10.137.44");
  3.InetAddress addr2 = InetAddress.getByName("www.javathinker.org");
  
4.设定客户端的地址
 1.在一个Socket对象中,即包含远程服务器的IP和端口信息,也包含本地客户端的IP地址和端口信息.默认情况下,客户端的IP地址来自客户程序所在的主机,而客户端的端口则有操作系统随机分配.
 ->Socket(InetAddress address,int port,InetAddress localAddress,int localPort) throws IOException
 ->Socket(String host,int port,InetAddress localAddress,int localPort) throws IOException
 上两个方法用来设置客户端的ip端口和地址
 ->这种情况主要适用于一个主机同时属于两个以上的网络,它可能拥有两个以上的IP地址.如一个在Internet,一个在局域网.
 ->如果希望和局域网的服务器程序通讯,则可以以局域网的IP地址作为localAddress来构造Socket.
 
5.客户连接服务器时可能抛出的异常

 1.UnknownHostException:无法识别主机的名字或ip地址时,就会抛出此异常
 2.ConnectException:如果没有服务器监听指定的端口;或者服务器进程拒接连接,则会抛出此异常.
 3.SocketTimeoutException:如果等待连接超时,就会抛出此异常.
 4.BindException:如无法把Socket对象与指定的本地IP地址或端口绑定则会抛出此异常.
 
 IOException
  -UnknownHostException
  -InterruptedIOException
        -SocketTimeoutException
  -SocketException
        -BindException
        -ConnectException
        
6.获取Socket的信息

 1.同时包含了远程服务器的IP和端口信息以及客户本地的IP和端口信息.
 2.获取输出流合输入流,分别用于向服务器发送数据以及接收从服务端发来的数据.
 
 1.getInetAddress():获得远程服务器的IP地址
 2.getPort():获得远程服务器的端口
 3.getLocalAddress():获得客户本地的IP地址
 4.getLocalPort():获得客户本地的端口
 5.getInputStream():获得输入流.如果Sokcet还没有连接或已经关闭或者已经通过shutdownInput方法关闭输入流,则才方法会抛出IOException.
 6.getOutputStream(): 获得输入流..如果Sokcet还没有连接或已经关闭或者已经通过shutdownOutput方法关闭输出流,则才方法会抛出IOException.
 
7.关闭Socket

 1.当client与Server通信结束,应该及时关闭Socktt,以释放Socket占用的包括端口在内的各种资源.
 2.Socket#close方法负责关闭socket.当一个Socket对象被关闭就不能通过其输入和输出流进行io操作.否则会导致IOException.
 3.确保关闭Socket的操作就是被执行,建议把该操作放在finally代码块中.如
  Socket socket = null;
  try
  {
   socket = new Socket("www.thinker.org",80);
   //执行接收和发送数据的操作
   ...
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  finally
  {
   try
   {
    if(socket != null)
    {
     socket.close();
    }
    catch(IOException e)
    {
     e.printStackTrace();
    }
   }
  }

 4.Socket类提供了3个状态测试方法.
  1.isClosed()
   1.public synchronized void close() throws IOException {
          synchronized(closeLock) {
              if (isClosed())
                  return;
              if (created)
                  impl.close();
              closed = true;
          }
    }
     2.public boolean isClosed() {
        synchronized(closeLock) {
            return closed;// true if the socket has been closed
        }
    }

  2.isConnected()
   1.true if the socket was successfuly connected to a server
   2. Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
       (see {@link #isClosed()}) if it was successfuly connected prior to being closed.
  3.isBound()
   1. true if the socket was successfuly bound to an address
   2.Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
       (see {@link #isClosed()}) if it was successfuly bound prior to being closed.
      
     4.判断一个Socket对象是否处于连接状态,用以下形式:
     boolean isConnected = socket.isConnected() && !socket.isClosed()
     
   8.半关闭Socket

    A进程与B进程通过Socket通信.假定A输出数据,B读入数据.A如何告诉B所有数据已经输出完毕:
    1.A与B交换的是字符流,且一行一行的读写.可事先约定以一个特殊标志作为结束标志,如以"bye"作为结束标志.当A向B发送一行字符串"bye"时,B读到这一行数据时,则停止读数据.{@link EchoServer},{@link EchoClient}
    2.进程A先发送消息,告诉B所发送正文的长度.->再发送正文.->B先获知A发送的正文长度->接下来只要读取完该长度的字符或者字节,就停止读数据.
    3.A发完所有数据后,关闭Socket->B读取A发送的所有数据后->InputStream#read->该方法返回-1.->BufferedReader#readLine->返回null.
     {@link HTTPClient}
    4.Socket#close->输入输出流都被关闭->有时候希望仅关闭输入流或输出流之一->Socket半关闭方法->
     shutdownInput():关闭输入流
     shutdownOutput():关闭输出流
     ->B读取数据时,如果A的输出流已经关闭->B读入所有数据后,就会读到输入流的末尾.
     ->先后调用Socket的shutdonwInput和shutdownOutput方法.仅仅是关闭了输入流和输出流,并不等价Socket#close.->通信结束后,依然要调用Socket的close方法.只有该方法才会释放Socket占用的资源.如占用的本能地端口等.
    5.Socket#isInputShutdown()->输入流关闭,返回true.
       Socket#isOutputShutdown()->输出流关闭,返回true
    6.client与Server通信时,如果有一方突然结束程序或者关闭了Socket或者单独关闭了输入流或输出流.对另一方会造成什么影响.
     {@link Sender}{@link Receiver}.
     

9.设置Socket选项

 1.TCP_NODELAY:表示立即发送数据
 
  1.public void setTcpNoDelay(boolean on) throws SocketException
  2.public boolean getTcpNoDelay() throws SocketException
  3.默认情况,发送数据采用Negale算法.即指发送方发送的数据不会立即发出,而是先放到缓冲区内.等缓冲区区慢了再发出.->发送完一批数据等待接收方对这批数据的回应.->再发送下一批数据.->适用于发送方需要发送大批量数据,且接收方会及时回应的场合->通过减少传输数据的次数来提高通信效率.
  ->对于发送方持续发送小批量数据,且接收方不一定立即发送响应->该算法会使发送方运行很慢->如实时网络游戏
  4.TCP_NODELAY默认值为false->即表示采用Negale算法.->setTcpNoDelay(true)->关闭Socket缓存,确保数据及时发送
  5.if(!socket.getTcpNoDelay()){socket.setTcpNoDelay(true)}
  6.Socket底层不支持该选项,则抛出SocketException.
  
 2.SO_REUSEADDR:表示是否允许重用Socket所绑定的本地地址
 
  1.public void setReuseAddress(boolen on) throws SocketException
  2.public boolean getReuseAddress() throws SocketException
  3.接收方通过Socket#close关闭Socket->如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立刻释放本地端口->会等待一段时间->确保接收到了网络上发送过来的延迟数据->释放端口->Socket收到延迟数据后,不会对这些数据做任何处理->Socket接收延迟数据的目的->确保这些数据不会被其他恰巧绑定到同样端口的新进程接收到.
  4.客户端程序一般采用随机端口->出现两个client程序绑定到同样端口的可能性不大
  5.server程序采用固定端口->server关闭后,其端口可能还会被占用一段时间->此时如果重启程序,端口已经被占用->使得程序无法绑定到给端口->启动失败
  6.确保一个进程关闭Socket后,即使其还未释放端口->同一个主机上的其他进程还可以立即重用该端口->
   if(!socket.getReuseAddress()){socket.setReuseAddress(true)}
  7.该方法必须在Socket还未绑定到一个本地端口之前调用.否则无效.
   1.Socket socket = new Socket();
      socket.setResueAddress(true);
      socket.connect(new InetSocketAddress("localhost",8080));
   2.Socket socket = new Socket();
      socket.setResueAddress(true);
      socket.bind(new InetSocketAddress("localhost",9000));
      socket.connect(new InetSocketAddress("remotehost",8000));
  8.两个公用一个端口的进程必须都调用socket.SetResueAddress(true)->才能使得一个进程关闭Socket后,另一个进程的Socket能立即重用相同端口.
  9.当多个ServerSocket对象同时绑定一个端口时,系统会随机选择一个ServerSocket对象来接收客户端请求->接收客户端请求的ServerSocket对象必须关闭才能轮到其他的ServerSocket对象接收客户端请求。如果不关闭这个ServerSocket对象,那么其他的ServerSocket对象将永远无法接收客户端请求 

 3.SO_TIMEOUT:表示接收数据时的等待时间
 
  1.public void setSoTimeout(int milliseconds) throws SocketException
  2.public int getSoTimeout() throws SocketException
  3.通过Socket的输入流读数据时,如果还未有数据,则等待:
   如:
    byte[] buff = new byte[1024];
    InputStream in = socket.getInputStream();
    in.read(buff);
   ->输入流没有数据,则in.read(buff)就会等待发送方发送数据->结束等待条件:
    1.输入流中有1024个字节->read将其读到buff中->返回读到的字节数
    2.距离输入流末尾还有小雨1024个字节->read读到buff中,返回读到的字节数
    3.读到输入流的末尾
    4.连接已经断开,抛出IOException
    5.Socket#setSoTimeout设置了等待超时时间,超过这一时间则抛出SocketTimeoutException
  4.该选项用于设定接收数据的等待超时时间,单位为毫秒->默认值为0,表示无限等待,永远不会超时.
  5.该方法必须在接收数据之前执行才有效.
  6.输入流的read方法抛出SocketTimeoutException后,Socket依然是连接的->可尝试再次读取数据->
    socket.setTimeout(3 * 60 * 1000);
    byte[] buff = new byte[1024];
    InputStream in = socket.getInputStream();
    
    int len = -1;
    
    do
    {
     try
     {
      len = in.read(buff);
      // 处理读到的数据
      ...
     }
     catch(SocketTimeoutException e)
     {
      e.printStackTrace();
      len = 0;
     }
    }
    while(len != -1)
 
 4.SO_LINGER:表示当执行Socket的close方法时,是否立即关闭底层Socket
 
  1.public void setSoLinger(boolean on,int seconds) throws SocketException
  2.public int getSoLinger() throws SocketException
  3.该选项用来控制Socket关闭时的行为->默认执行Socket的close,该方法会立即返回.但是底层的Socket不立即关闭,会延迟一段时间,知道发送完所有剩余的数据->真正关闭socket->断开连接.
  4.socket.setSoLinger(true,0)->执行Socket#close时,该方法立即返回且底层的Socket也会立即关闭->所有未发送完的剩余数据被丢弃.
  5.socket.setSoLinger(true,60)->
   1.Socket#close->该方法不会立即返回->进入阻塞状态
   2.底层的Socket会尝试发送剩余的数据->返回条件:
    1.底层的Socket已经发送完所有剩余数据
    2.尽管底层的Socket还没有发送完所有的剩余数据->但是已经阻塞了60秒->也会返回->剩余未发送的数据将被丢弃
     1.->close返回后->底层的Socket会被关闭,断开连接
     2.setSoLinger(boolean on,int seconds)->seconds参数以秒为单位->
  6.程序通过输出流写数据时,->仅表示程序向网络提交了一批数据->由网络负责输送到到接收方->程序关闭Socket时,有可能这批数据还在网络上传输,未达到接收方->未发送完的数据指还在网络上传输未被接收方接收的数据
  {@link TestLingerClient}{@link TestLingerServer}
 
 5.SO_SNDBUF:表示发送数据的缓冲区大小
  1.public void setSendBufferSize(int size) throws SocketException
  2.public int getSendBufferSize() throws SocketException
  3.该选项用来表示Socket用于输出数据的缓冲区的大小->底层Socket不支持该选项->set 抛出SocketException
 
 6.SO_RCVBUF:表示接收数据的缓冲区大小
  1.public void setReceiveBufferSize(int size) throws SocketException
  2.public int getReceiveBufferSize() throws SocketException
  3.该选项用来表示Socket的用于输入数据的缓冲区的大小->传输大的连续的数据块,如基于HTTP和FTP协议的通信,可以使用较大缓冲区->减少数据传输的次数->提高传输数据的效率->对于交互频繁且单次传送数据量比较小的通信方式如Telnet和网络游戏,则应该采用交换缓冲区.确保小批量的数据能及时发送给对方.-->设定缓冲区大小的原则使用与SO_SNDBUf选项
  4.底层Socket不支持该选项->set 抛出SocketException.
  
 
 7.SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭.
 
  1.public void setKeepAlive(boolean on) throws SocketException
  2.public boolean getKeepAlive() throws SocketException
  3.该选项为true->底层的TCP实现会监视该连接是否有效->当连接处于空闲状态(连接的两端没有互相传送数据)->超过2小时->本地的TCP实现会发送一个数据包一个远程的Socket->远程Socket没有发回响应->TCP实现持续尝试11分钟->直到接收到响应->12分钟内未收到响应->TCP实现就会自动关闭本地Socket,断开连接->不同的网络平台,TCP实现尝试与远程Socket对话的实现会有所差别.
  4.该选项为false->表示TCP不会监视连是否有效->不活动的client可能会永久存在下去->而不会注意server已经崩溃
  5.if(!socket.getKeepAlive()){socket.setKeepAlive(true)}
  
 8.OOBINLINE:表示是否支持发送一个字节的TCP紧急数据
  //注OOB:out-of-band 带外
  1.public void setOOBInline(boolean on) throws SocketException
  2.public int getOOBInline() throws SocketException
  3.该选项为true,表示支持发送一个自己的TCP紧急数据->Socket#sendUrgentData(int data),用于发送一个字节的TCP紧急数据
  4.该选项为false->接收方收到紧急数据时不做处理,直接丢弃->需要socket.setOOBInline(true)->接收方会将接收到的紧急数据与普通数据放在同样的队列->注:除非采用更高层次的协议,否则接收方处理紧急数据的能力非常有限->紧急数据到来时,接收方不会得到任何通知->因此很难区分普通数据与紧急数据->只好按照同样的方式处理.
  
  
10.服务类型选项

 1.用户去邮局时,可选择不同的服务->发送普通信 | 挂号信 | 快件->价格,发送速度及可靠性均不同.
 2.Internet上传输数据也分为不同的服务器类型.->如发送视频需要较高的宽带,快速到达目的,保证接收方看到连续的画面.
 3.IP规定了4种服务类型,定性的描述服务的质量:
  1.低成本->发送成本低.
  2.高可靠性->保证把数据可靠的送达目的地.
  3.最高吞吐量->一次性可以接收或发送大批量的数据
  4.最小延迟->传输数据的速度要快,把数据快速送达目的地.
 4.4种服务类型可以组合->即可进行或运算
  IPTOS_LOWCOST (0x02)
      IPTOS_RELIABILITY (0x04)
  IPTOS_THROUGHPUT (0x08)
    IPTOS_LOWDELAY (0x10)
    {@link Socket#setTrafficClass}
   5.public void setTrafficClass(int trafficClass) throws SocketException
      public int getTrafficClass() throws SocketException
     
11.设置连接时间,延迟和带宽的相对重要性(注意相对二字)
 1.Socket#setPerformancePreferences(int connectionTime,int latency,int bandwidth)
 2.3个参数为网络传输数据的3项指标
  1.connectionTime-表示用最少时间建立连接
  2.latency-表示最小延迟
  3.bandwidth-表示最高带宽
  ->三项指标的相对重要性.->3项参数的整数之前的相对大小决定了响应参数的相对重要性.
   如setPerformancePreferences(2,1,3)->则表示最高带宽最重要,其实是最少连接时间,最后是最小延迟.
   
12.小结:
 1.通信过程中,如果发送方没有关闭Socket,就突然中止程序,则接收方在接收数据时会抛出SocketException.
 2.发送方发送完数据后,应该及时关闭Socket或关闭Socket的输出流,这样,接收方就能顺利读到输入流的末尾.

部分源代码:

原创粉丝点击