0


Java网络编程——客户端Socket

第一部分:使用Socket

一.数据报

数据按照有限大小的包传输,这些包称为数据报。

每个数据报包含一个首部和一个有效载荷。

首部包含包发送到的地址和端口、包来自的地址和端口、检测数据是否被破坏的校验和,以及用于保证可靠传输的各种其他管理信息;

有效载荷包含数据本身。

由于数据报文长度是有限的,通常必须将数据分解为多个包,再在目的的重新组合。

也有可能一个包或者多个包在传输中丢失或者遭到破坏,需要重新传;

或者包乱序到达,需要重新排序。所有这些(将数据分解为包、生成首部、解析入站包的首部、跟踪哪些包已经收到而哪些没有收到and so on),是不是看起来都很麻烦,但是你不需要完成这些任务,前面的内容就是让大家了解一下哦。

Socket对程序员掩盖了网络的底层细节,就比如:错误检测、包大小、包分解、包重传、网络地址...

二.socket的功能

Socket是两台主机之间的一个连接,可以完成以下7个基本操作:

1.连接远程机器

2.发送数据

3.接收数据

4.关闭连接

5.绑定端口

6.监听入站数据

7.在绑定端口上接受来自远程机器的连接

Java程序采用以下方式使用客户端socket

*** 程序用构造函数创建一个新的socket**

*Socket尝试连接远程机器

详细了解一下:

一旦建立了连接,本地和远程机就从这个socket得到输入流和输出流,使用这两个流相互发送数据;

连接是全双工的,两台主机都可以同时发送和接受数据。数据的含义取决与协议,发送给FTP服务器的命令与发送给HTTP服务器的命令就有所不同。一般会先完成某种协商握手,然后再具体传输数据

第二部分:了解SOCKET类

1.socket类

Socket

类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

2.构造方法

public Socket(String host, int port)

eg:

Socket clients = new Socket("127.0.0.1",2628);

创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址

***~回送地址*(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

3.常用方法介绍

public InputStream getInputStream() : 返回此套接字的输入流。

如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。

public OutputStream getOutputStream() : 返回此套接字的输出流。

如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。

public void close() :关闭此套接字。

一旦一个socket被关闭,它不可再使用。
关闭此socket也将关闭相关的InputStream和OutputStream 。

public void shutdownOutput() : 禁用此套接字的输出流。

任何先前写出的数据将被发送,随后终止输出流。

4.DEMO:客户端和服务端通信

1.两端通信时步骤:
服务端程序,需要事先启动,等待客户端的连接。
客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

2.在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
【服务端我会在下一篇博客中详细讲到,大家可以先了解一下下哦】

客户端:

package TEMP.Temp2;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args){

        //socket对象初始化
        Socket socket = null;

        //输出流 os对象初始化
        OutputStream os = null;
        try {

            //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
            InetAddress inet = InetAddress.getByName("127.0.0.1");
            socket = new Socket(inet,2022);//inet是服务端ip

            //2、获取一个输出流,用于写出要发送的数据
            os = socket.getOutputStream();

            //3、写出数据
            os.write("你好,我是客户端小杨同学!".getBytes());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4、释放资源,别忘了哦!!!!
            if(socket!=null){
                try {
                    socket.close();//关闭
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(os!=null){
                try {
                    os.close();//关闭
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端:

package TEMP.Temp2;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            //1、创建服务端的ServerSocket,指明自己的端口号
            serverSocket = new ServerSocket(2022);

            //2、调用accept接收到来自于客户端的socket
            socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入,接入了之后才会显示消息

            //3、获取socket的输入流
            is = socket.getInputStream();

            //4、读取输入流中的数据
            //ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
            baos = new ByteArrayOutputStream();
            int len=0;
            byte[] buffer = new byte[1024];

            //判断是否将客户端发的消息读完了
            while ((len=is.read(buffer))!=-1){
                baos.write(buffer,0,len);
            }
            System.out.println("收到了来自于客户端"+
                    socket.getInetAddress().getHostName()
                    +"的消息:"+baos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {//5、关闭资源
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(baos!=null){
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

第三部分:用Telnet研究协议

一、了解Telnet

1.Telnet是一个简单的远程终端协议。用户用Telnet就可种子其所在地通过TCP连接注册(即登录)到远地的另一个主机上(使用主机名或IP地址)。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。
2.Telnet客户进程和服务器进程一般只属于用户应用程序,终端用户通过键盘输入的数据送给操作系统内核的终端驱动进程,由终端驱动进程把用户的输入送到Telnet客户进程,Telnet客户进程把收到的数据传送给TCP,由TCP负责在客户端和服务器端建立TCP连接,数据就通过TCP连接送到了服务器端,服务器的TCP层将收到的数据送到相应的应用层Telnet服务器进程。

二、用Socket从服务器读取

1.在终端上用Telnet测试daytime服务器

2.小DEMO:Daytime协议客户端

package BOOK;
//Daytime协议客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class DaytimeClient {
    public static void main(String[] args) {
        String hostname = args.length>0?args[0] : "time.nist.gov";
        Socket socket = null;
        try{
            /**在端口13打开与time.nist.gov的连接
             * 这里不仅仅是创建一个对象,实际上它会在网络上建立连接;
             * 如果连接超时(或者由于服务器没有在端口13上监听而失败),
             * 构造函数会抛出一个IOException异常
             * 所以呢,要把这段代码放在一个try块中。
             */
            
            //在端口13打开与time.nist.gov的连接
            socket = new Socket(hostname,13);  
            
            //setSoTimeout()方法为连接设置一个超时时间,(单位:毫秒)
            socket.setSoTimeout(15000);
            
            //一旦打开socket并设置其超时时间,
            // 可以调用getInputStream()来返回一个InputStream,用它从socket读取字节
            InputStream in = socket.getInputStream();
            
            //把字节存储在一个StringBuilder中
            StringBuilder time =new StringBuilder();
            
            //一般情况下,服务器可以发送任何字节
            //但是呢,在特定的例子中,协议指定发送的字节必须是ASCII
            InputStreamReader reader = new InputStreamReader(in,"ASCII");
           
            //使用for确定将字节读完
            for(int c=reader.read();c!=-1;c=reader.read()){
                time.append((char) c);
            }
            System.out.println(time);
        }catch(IOException ex){
            System.err.println(ex);
        }finally{
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

//输出
//59773 22-07-13 02:17:17 50 0 0 720.0 UTC(NIST) *
 

3.小DEMO:通过与time.nist.gov对话构造一个Date

package BOOK.DEMO;

//通过与time.nist.gov对话构建一个Date
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Daytime {
    public Date getDateFromNetwork() throws IOException, ParseException {
        try(Socket socket = new Socket("time.nist.gov",13)){
            socket.setSoTimeout(15000);
            InputStream in = socket.getInputStream();
            StringBuilder time = new StringBuilder();
            InputStreamReader reader = new InputStreamReader(in,"ASCII");
            for(int c=reader.read(); c!=-1;c=reader.read()){
                time.append((char) c);
            }
            return parseDate(time.toString());
        }
    }
    static Date parseDate(String s) throws ParseException{
        String[] pieces =s.split(" ");
        String dateTime = pieces[1] + " " +pieces[2] + " UTC";
        DateFormat format = new SimpleDateFormat("yy-MM-dd hh:mm:ss z");
        return format.parse(dateTime);
    }
}

注意点:从网络读取数据的时候,并不是所有协议都使用ASCII,甚至不一定使用文本。

4.小DEMO:时间协议客户端

package BOOK.DEMO;

//时间协议客户端

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.Date;

public class Time {
    private static final String HOSTNAME = "time.nist.gov";
    public static void main(String[] args) {
        Date d = null;
        try {
            
            d = Time.getDateFromNetwork();
            
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println("It is " + d);
    }

    public static Date getDateFromNetwork() throws IOException,ParseException{
        long differenceBetweenEpochs = 2208988800l;
        Socket socket = null;
        try{
            socket = new Socket(HOSTNAME,37);
            socket.setSoTimeout(15000);
            InputStream raw =socket.getInputStream();

            long secondSince1900 =0;
            for(int i=0;i<4;i++){
                secondSince1900 =(secondSince1900 << 8) | raw.read();
            }
            long secondSince1970 =secondSince1900 - differenceBetweenEpochs;
            long msSince1970 =secondSince1970*1000;
            Date time = new Date(msSince1970);
            return time;
        }finally{
            try{
                if(socket!=null)
                    socket.close();
            }catch(IOException ex){

            }
        }
    }
    
}

//输出:
// It is Wed Jul 13 11:53:25 CST 2022

三、用Socket写入服务器

1.了解用socket如何写入服务器

写入服务器并不比读取服务器更困难。

只需要向Socket请求一个输出流以及一个输入流。使用输出流在socket上发送请求数据的时候,同时还可以使用输入流读取数据。但是呢,大多数协议都设计为客户端只读取socket或者只写入socket,而不是二者同时进行。

【最常见的模式】

客户端发送一个请求,然后服务器相应。客户端可能发送另一个请求,服务器再作出响应。这个过程会一直继续,直到客户端或者服务器完成了工作,然后关闭连接。

下面我们来康康具体的实例

dict是一个简单的双向TCP。在这个协议中,客户端向dict服务器的2628端口打开一个socket,并且发送命令。

2.用java程序来显示一个完整的dict客户端

package BOOK.DEMO;

import java.io.*;
import java.net.Socket;

public class DictClient {
    public static final String SERVER ="dict.org";
    public static final int PORT =2628;
    public static int TIMEOUT =15000;
    public static void main(String[] args) {
        Socket socket =null;
            try {
                //先向一个dict服务器的端口2628打开一个Socket
                socket = new Socket(SERVER,PORT);

                //为了防止连接服务器是服务器挂起,设置一个超时时间
                socket.setSoTimeout(TIMEOUT);

                /**
                 * getOutputStream()方法返回一个原始的OutputStream
                 * 可以用它从你的应用向Socket的另一端写数据。
                 * 在使用之前,通常会把这个流串链到一个更方便的类【DataOututStream或OutputStreamWriter】
                 */
                //getOutputStream()方法返回一个原始的OutputStream
                OutputStream out = socket.getOutputStream();

                //包装在一个Writer中
                Writer writer =new OutputStreamWriter(out,"UTF-8");
                writer =new BufferedWriter(writer);

                //用socket的输入流来读取信息
                InputStream in =socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(in,"UTF-8"));
                for(String word :args){
                    define(word,writer,reader);
                }

                //通过socket写入命令
                writer.write("quit\r\n");

                //刷新输出,确保命令会通过网络发送
                writer.flush();

            } catch (IOException ex) {
                System.out.println(ex);
            }finally{
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException ex) {

                    }
                }
            }
    }

    static void define(String word,Writer writer,BufferedReader reader)
        throws IOException,UnsupportedEncodingException{
        writer.write("DEFINE eng-lat " + word + "\r\n");
        writer.flush();

        for(String line =reader.readLine();line!=null;line=reader.readLine()){
            if(line.startsWith("250 ")){
                return ;
            }else if(line.startsWith("552 ")){
                System.out.println("No definition found for " + word);
                return;
            }
            else if(line.matches("\\d\\d\\d .*"))
                continue;
            else if(line.trim().equals("."))
                continue;
            else System.out.println(line);
        }
    }
}

四、半关闭Socket

1.shutdownInput()和shutdownOutput()方法

close()方法同时关闭Socket的输入和输出;

有的时候你可能希望仅仅关闭连接的一半,关闭输入或者输出;shutdownInput()和shutdownOutput()方法可以只关闭连接的一半。

public void shutdownInput( ) throws IOException

public void shutdownOutput( ) throws IOException

小小解析:

这两个方法并不关闭Socket,实际上,它会调整与Socket连接的流,使它认为已经到了流的末尾;

*关闭输入之后再读取输入流会返回-1;

*关闭输出之后再写入socket会抛出一个IOException异常

注意哦!!!

即使半关闭了连接,或者将连接的两半都关闭,使用结束后依然需要关闭该socket;

shutdown方法只影响Socket的流,并不是方法与socket关联的资源,比如:占用的端口。

2.isInputShutdown()和isOutputShutdown()方法

isInputShutdown()和isOutputShutdown()方法分别指出输入流和输出流是打开还是关闭的

public boolean isInputShutdown()

public boolean isOnputShutdown()

第四部分:构造和连接Socket

java.netSocket类是Java完成客户端TCP操作的基础类。

其他建立TCP网络连接的面向客户端的类(比如:URL、URLConnection、Applet和JEditorPane)最终都会调用这个类的方法。这个类本身使用原生代码与主机操作系统的本地TCP栈进行通信。

一、基本构造函数

每个Socket构造函数指定要连接的主机和端口。

主机可以指定InetAddress或者String;

远程端口指定为1到65535之间的int值

public Socket(String host,int port) throws UnknownHostException,IOException

为创建一个流套接字并将其连接到指定主机上的指定端口号

【String host:表示主机名,int port :端口号】

public Socket(InetAddress host,int port) throws IOException

为创建一个流套接字并将其连接到指定IP地址的指定端口号

这两个构造函数会连接socket(也就是说,在构造函数返回之前,会与远程主机建立一个活动的网络连接);

如果由于某种原因不能打开连接,构造函数会抛出一个IOException或者UnknownHostException异常。

小DEMO:查看指定主机上前1024个端口中哪些安装的有TCP服务器

package BOOK.DEMO;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class LowPortScanner {
    public static void main(String[] args) {
        String host = args.length >0 ? args[0] : "localhost";
        for(int i=1;i<1024;i++){
            try{
                Socket s = new Socket(host,i);
                System.out.println("There is a server on port " + i + " of "+host);
                s.close();
            }catch(UnknownHostException ex){
                System.err.println(ex);
            }catch(IOException ex){
                //当这个端口不是一个服务器的时候
            }
        }
    }
}

//输出:There is a server on port 631 of localhost

二、选择从哪个本地接口连接

有两个构造函数可以指定要连接的主机和端口,以及从哪个接口和端口连接:

public Socket(String host,int port,InetAddress interface,int localPort)

throws IOException, UnknownHostException

创建一个套接字并将其连接到指定远程主机上的指定远程端口

参数解析:

String host:指定的主机( 远程主机的名称,或环回地址的

null

int port:指定的端口号(远程端口)

InetAddress interface:套接字所绑定的本地地址,或

null

anyLocal

地址。

int localPort:套接字绑定的本地端口,或

zero

用于系统选择的自由端口

public Socket(InetAddress host,int port,InetAddress interface,int localPort)

throws IOException

创建一个套接字并将其连接到指定远程地址上的指定远程端口

三、构造但是不连接

public socket()

如果没有为Socket构造函数提供任何参数,它就没有目标主机可以连接。

可以以后再为某个connect()方法传入一个SocketAddress来建立连接

eg:

还可以传入一个int作为第二个参数,来指定连接超时之前等待的时间(毫秒数)

public void connect(SocketAddress endpoint,int timeout) throws IOException

默认值0表示永远等待下去

你有没有思考一下下,为什么要有这个构造函数呢??!

有这个构造函数其实是为了支持不同类型的socket;

还有一个好处是:可以在try-catch-finally块中清理代码

明显可以看到:finally块中关闭一个Socket 的时候可以避免烦人的null检查哈哈哈哈。

四、Socket地址

SocketAddress类表示一个连接端点。这是一个空的抽象类,除了一个默认构造函数之外没有其他方法。

SocketAddress类的主要用途是为了暂时的socket连接信息(比如IP地址和端口)提供一个方便的存储,即使最初的socket已断开并且被垃圾回收了,这些信息也可以重用来创建新的Socket.

Socket提供了两个返回SocketAddress对象的方法

public SocketAddress getRemoteSocketAddress()

返回所连接系统的地址

public SocketAddress getLocalSocketAddress()

返回发起连接的地址

五、代理服务器

前面提到的最后一个构造函数创建一个未连接的Socket,它通过一个指定的代理服务器连接:

public Socket(Proxy proxy)

一般情况下,Socket使用的代理服务器由socksProxyHost和socksProxyPort系统属性控制,这些属性应用于系统中的所有Socket。

六、获取Socket的信息

1.Socket对象的一些属性

Socket对象有一些属性可以通过获取方法来访问:

1.远程地址

2.远程端口

3.本地地址

4.本地端口

2.获取方法

1. public InetAddress getInetAddress()

返回套接字所连接的地址。

2. public int getPort()

返回此套接字连接到的远程端口号。

3. public InetAddress getLocalAddress()

获取套接字所绑定的本地地址。

4. public int getLocalPort()

返回此套接字绑定到的本地端口号。

3.远程端口

对于客户端Socket来说,通常是由一个标准委员会预先分配的“已知端口”。

4.本地端口

通常是由系统在运行时从未使用的空闲端口中选择。

5.demo:获得Socket的信息

package BOOK.DEMO;

//获取SOCKET信息
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

public class SocketInfo {
    public static void main(String[] args) {
        for(String host :args){
            try{
                Socket theSocket = new Socket(host,80);
                System.out.println("Connected to" + theSocket.getInetAddress()
                + " on port " + theSocket.getPort() + " from port "
                + theSocket.getLocalPort() + " of "
                + theSocket.getLocalAddress());
            }catch (UnknownHostException ex){
                System.err.println("I can't find " + host);
            }catch(SocketException ex){
                System.err.println("Could not connect to " + host);
            }catch(IOException ex){
                System.err.println(ex);
            }
        }
    }
}

七、关闭还是连接

如果socket关闭,isClose()方法会返回true,否则返回false。

有的时候你不确定一个Socket的状态,可以用这个方法来检查,千万别冒着抛出IOException异常的风险。

but!!!! 这个方法也有缺陷,如果Socket从一开始就没有连接,isClosed()也返回false,尽管Socket实际上根本没有打开过。

Socket类还有一个isConnected()方法,它会指出Socket是否从未连接过一个远程主机。

八、toString()

Socket类只覆盖了java.lang.Object中的一个标准方法:toString().

第五部分:设置Socket选项

Socket有以下几个选项:

  1. TCP_NODELY:表示立即发送数据
  2. SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址
  3. SO_TIMEOUT:表示接收数据时的等待超时时间
  4. SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket
  5. SO_SNFBUF:表示发送数据的缓冲区大小
  6. SO_RCVBUF:表示接受数据的缓冲区大小
  7. SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动将它关闭
  8. OOBINLINE:表示是否发送一个字节的TCP紧急数据

1.TCP_NODELY选项

设置该选项:public void setTcpNoDelay(boolean on)throws SocketException
读取该选项:public boolean getTcpNoDelay()throws SocketException

默认情况下,发送数数据采用Negale算法。Negale算法是指发送方发送 数据不会立刻发出,而是先放在缓冲区内,等缓冲区满了再发出。发送完一批数据后,会等待接收方对这批数据的回应,然后在发送下一批数据。Negale算法适用于发送方需要发送大批量数据,并且接受方会及时作出反应的场合,这种算法通过减少数据的通信的次数来提高通信效率。
如果发送方持续的发送小批量数据,并且接受方不一定会立即发送响应数据,那么Negale算法会使发送方运行很慢。对于GUI程序,如网络游戏程序(服务器需要实时跟踪客户端鼠标的移动),这个问题尤为突出。客户端鼠标位置的改动的信息需要实时发送到服务器上,由于Negale算法采用缓冲,大大减低了实时反应速度,导致客户端运行很慢。
TCP_NODELY的默认值为false,表示采用Negale算法。如果调用setTcpNoDelay(true)方法,就会关闭Socket的缓冲,确保数据及时发送:

【eg】

if(!socket.getResuseAddress())
socket.setResuseAddress(true);

2.SO_RESUSEADDR选项

设置该选项public void setResuseAddress(boolean on)throws SocketException

读取该选项 : public void getResuseAddress(boolean on)throws SocketException

当接受方通过Socket的close()方法关闭Socket时,如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立刻释放本地端口,而是会等待一段时间,确保收到了网络上发送过来的延迟数据,然再释放该端口。Socket接受到延迟数据后,不会对这些数据做任何处理。Socket接受延迟数据的目的是,确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到。
客户程序一般采用随机端口,因此会出现两个客户端程序绑定到同样端口的可能性不大。许多服务器都使用固定的端口。当服务器进程关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一主机上重启服务器程序,由于端口已经被占用,使得服务器无法绑定到该端口,启动失败。为了确保一个进程被关闭后,及时它还没有释放该端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用Socket的setResuseAddress(true)方法:
** if(!socket.getResuseAddress())
socket.setResuseAddress(true);**

值得注意的是:socket.setResuseAddress(true)方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行socket.setResuseAddress(true)方法无效

所以呢必须按照以下方法创建Socket对象,然后在连接远程服务器:

Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器
        socket.setReuseAddress(true);
        SocketAddress socketAddress=new InetSocketAddress("remotehost",2022);
        socket.connect(socketAddress);

        //或者
        Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器
        socket.setReuseAddress(true);
        socketAddress localAddr=new InetSocketAddress("localhost",2021);

        socketAddress remoteAddr=new InetSocketAddress("remotehost",2022);
        socket.bind(localAddr);//与本地端口绑定
        socket.connect(remoteAddr);//连接远程服务器

此外,两个共用同一个端口的进程必须都调用socket.setReuseAddress(true)放方法才能使得一个进程关闭Socket后,另一个进程的Socket能够立刻重用相同的端口。

3.SO_TIMEOUT选项

设置该选项:public void setSoTimeout(int milliseconds)throws SocketException
读取该选项:public void getSoTimeout(int milliseconds)throws SocketException

当通过Socket流读取数据时,如果还没有数据,就会等待。

例如,在以下代码中,in.read(buf)方法从输入流中读入1024个字节:

byte[] luck=new byte[1024];
InputStream in=new socket.getInputStream();
in.read(luck);

如果输入流中还没有数据,in.read(buf)方法就会等待发送方发送数据,直到满足以下情况才停止等待:

1>. 输入流中有10个字节,read()方法把这些字节读入到buff中,再返回读取的字节数;
2>.当已经接近输入流的末尾,举例末尾还有小于1024个字节时,read()方法会把这些字节读入到buff中,在返回读物的字节数;
3>.已读到输入流的末尾,返回-1;
4>. 连接已断开,抛出IOException;
5>.如果通过Socket的setTimeout()方法设置了等待超时时间,单位为毫秒,它的默认值为0,表示会无限等待,永远不会超时。

下面的代码把接受数据的等待时间设为5分钟;

if(socket.getTimeout()==0)
    socket.setTimeout(60000*5);

4.SO_LINGER选项

设置该选项:public void setSoLinger(boolean on,int seconds)throws SocketException
读取该选项:public void getSoLinger(boolean on,int seconds)throws SocketException

第六部分:Socket异常

Socket类的大多数方法都会声明抛出IOException或者它的子类java.net.SocketException.

(具体是什么原因我还没有了解,大家写代码的时候多多注意就好啦)

标签: 大数据 java 网络

本文转载自: https://blog.csdn.net/weixin_62017112/article/details/125756855
版权归原作者 Aurora_♚ 所有, 如有侵权,请联系我们删除。

“Java网络编程——客户端Socket”的评论:

还没有评论