Java网络编程——第八章 客户端Socket

来源:互联网 发布:表白源码 编辑:程序博客网 时间:2024/06/05 13:31
客户端Socket使用方式
     1、创建Socket
     2、Socket尝试连接主机
建立连接后,本地主机和远程主机就从该Socket获得输入、输出流,且为全双工方式;创建Socket的同时会在网络上建立连接,连接超时或者监听失败,将抛出IOException,如果服务器拒绝连接则抛出ConnectException,路由器无法确定如何将包发送给服务器则抛出NoRouteToHostException,Socket实现了AutoCloseable接口,可以使用Java 7的try-with-resource;创建Socket,强烈建议设置连接超时setSoTimeout,如果连接超时SocketTimeoutException

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

public class Time {
    private static final String HOSTNAME = "time.nist.gov";
    public static void main(String[] args) throws IOException, ParseException{
        Date d = Time.getDateFromWork();
        System.out.println(d);

    }

    public static Date getDateFromWork() throws IOException, ParseException{
        /*
         * 时间协议设置起点为1900年
         * Java的Date起始于1970年
         */
        // 70年时间内的毫秒数
        //long differenceBetweenEpochs = 2208988800L;
        // 也可以通过下列程序计算该值

        TimeZone gmt = TimeZone.getTimeZone("GMT");
        Calendar epoch1900 = Calendar.getInstance(gmt);
        epoch1900.set(1900, 01, 01, 00, 00, 00);
        long epoch1900ms = epoch1900.getTime().getTime();
        Calendar epoch1970 = Calendar.getInstance(gmt);
        epoch1970.set(1970, 01, 01, 00, 00, 00);
        long epoch1970ms = epoch1970.getTime().getTime();
        long differenceInMs = epoch1900ms - epoch1970ms;
        long differenceBetweenEpochs = differenceInMs / 1000;

        Socket socket = null;
        try {
            socket = new Socket(HOSTNAME, 37);
            socket.setSoTimeout(15000);

            InputStream raw = socket.getInputStream();

            long secodendsSince1900 = 0;
            for (int i = 0; i < 4; i++) {
                secodendsSince1900 = (secodendsSince1900 << 8) | raw.read();
            }

            long secodendsSince1970 = secodendsSince1900 - differenceBetweenEpochs;
            long msSince1970 = secodendsSince1970 * 1000;
            Date time = new Date(msSince1970);

            return time;
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {

            }
        }
    }
}
Socket写入服务器

package ch8;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;

public class DictClient {
    public static final String SERVER = "dict.org";
    public static final int PORT = 2628;
    public static final int TIMEOUT = 15000;

    public static void main(String[] args) {
        Socket socket = null;
        String word = "hello";
        try {
            socket = new Socket(SERVER, PORT);
            socket.setSoTimeout(TIMEOUT);
            OutputStream out = socket.getOutputStream();
            Writer writer = new OutputStreamWriter(out, "UTF-8");
            writer = new BufferedWriter(writer);
            InputStream in = socket.getInputStream();
            BufferedReader reader= new BufferedReader(new InputStreamReader(in, "UTF-8"));
            define(word, writer, reader);
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {

                }
                socket = null;
            }
        }
    }

    public static void define(String word, Writer writer, BufferedReader reader) throws IOException{
        writer.write("DEFINT end-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
close方法会同时关闭输入输出流,而shutdownInput或shutdownOutput则分别关闭输入流或输出流;对于输入流,shutdownInput会调整Socket连接的流,使之认为到达流的末尾,再次读取则返回-1;对于shutdownOutput,再次写入会抛出IOException;需要注意的是,半关闭连接,或两个半连接都关闭,仍需要关闭socket,因为半关闭只会影响socket流,而不会释放Socket资源

构造和连接Sokcet
public Socket(String host, int port) throws UnkonwnHostException, IOException
public Socket(InetAddress host, int port) thwos IOException

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
// 确定是否允许某个端口建立连接
public class LowPortScanner {

     public static void main(String[] args) {
           String host = "localhost";

           for (int i = 1; i <= 1024; i++) {
                try {
                     System.out.println(i);
                     Socket socket = new Socket(host, i);
                     //socket.setSoTimeout(10);
                     System.out.println("there is a server on port " + i + " of " + host);
                     socket.close();
                } catch (UnknownHostException e) {
                     System.err.println(e);
                } catch (IOException e) {

                }
           }
     }

}

选择从本地那个接口连接
public Socket(String host, int port, InetAddress interface, int localPort) throws UnknownHostException, IOException
public Socket(InetAddress host, int port, InetAddress interface, int localPort) throws IOException
前两个参数指定需要连接的主机和端口,后两个参数指定使用的本地网络连接和端口,,如果localPort设置为0,在在1024~65535之间随机选择可用端口

构造但不连接
主要有三个构造器用于创建未连接Socket
public Socket()
public Socket(Proxy proxy)
public Socket(SocketImpl impl)

try {
     Socket socket = new Socket();
     // 填入Socket选项
     SocketAddress address = new SocketAddress(host, port);
     // 也可以使用 public void connect(SocketAddress address, int timeout) throws IOException同时设置等待连接超时时间
     socket.connect(address);
     // 使用socket
} cathc (IOException e) {
     System.err.println(e);
}

// 由于无参构造函数不会抛出异常,因可以这样书写
Socket socket = new Socket();
SocketAddress address = new SokcetAddress(host, port)
try {
     socket.connect(address);
     // 使用socket
} catch (IOException e) {
     // 处理异常
} finally {
     // 这里不需要进行 socket 的 null 检查
     try {
         socket.close(); 
     } catch (IOException e) {
          // 忽略该异常
     }
}


Socket地址
SocketAddress类表示一个连接短点,目前仅支持TCP/IP Socket,主要用途是为暂时的Socket连接信息提供方便的存储,即使最初的Socket已经断开并回收,该信息任可以重用以创建新的Socket,提供如下两个方法
public SocketAddress getRemoteSocketAddress()
public SocketAddress getLoclaSocketAddress()
分别用于获取连接端和发起端的地址,如果Socket尚未开始连接,则返回null
通常使用一个主机和一个端口或者仅一个端口创建InetSocketAddress类,亦可以使用静态工厂方法InetSocketAddres.createUnsolved,此时不再DNS中查询主机
public InetSocketAddress(InetAddress address, int port)
public InetSocketAddress(String address, int port)
public InetSocketAddress(int port)
public static InetSocketAddress createUnsolved()

代理服务器
创建未连接的Socket
public Socket(Proxy proxy);
一般的,Socket的代理服务器由socketsProxyHost和socketsProxyPort属性控制,作用于系统所有Socket,而该构造函数创建的Socket则使用与指定的代理服务器,该构造函数传入的Proxy类型,可以是Proxy.Type.NO_PROXY

SocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress);
Socket s = new Socket(proxy);
SocketAddress remote = new InetSocketAddress(remoteHost, remotePort);
s.connect(remote);

关闭还是连接
如果Socket已经关闭,则isClosed返回false,这里存在的问题是如果该Socket从未打开过连接,也会返回false;此时,可以是用isConnected方法,指示该Socket是否从未连接过远程主机,如果该Socket确实可以连接远程主机,则返回true,尽管该Socket已经关闭

// 判断当前Socket是否打开
boolean connected = !socket.isClosed && socket.isConnected();

Socket的toString方法,由于Socket是临时对象,所以Socket没有覆盖equals和hashCode方法,因此Socket不能放进散列表或者进行比较

设置Socket选项,指定Socket依赖的原生Socket是如何发送与接收数据,以下属性均可能抛出SocketException
TCP_NODELAY,由于缓冲区的存在,低于较小的数据包在发送前会等待以组成更大的包,而如果使用setTcpNoDelay设置为true,则将关闭Socekt的缓冲区,此时将尽可能快的发送数据包,而如果底层Socket不支持该选项,则抛出SocketException;

if (!s.getTcoNoDelay()) {
     s.setTcpNoDelay(true)
}

SO_LINGER,指定Socket在关闭后如何处理尚未发送的数据报,默认close的方法立即返回,但系统仍会尝试发送剩余的数据,如果此时延迟设置为0,则未发送的数据报将丢失;而如果setSoLinger将延迟设置为任意正整数,则close方法将阻塞指定时间等待数据发送与确认,此时如果时间超时,则关闭Socket,剩余数据不会发送,也不会在收到确认;getSoLinger在该选项关闭时返回-1;基于不同系统,该选项存在最大等待时间;如果底层不支持该选项,则抛出SocketException

if (s.getSoLinger() == -1) {
     s.setSoLinger(true, 1024);
}
SO_TIMEOUT,当read读取数据时,read会等待足够时间得到足够的数据,通过setSlTimeout可以指定最大等待时间,如果设置为0则等待时间无限制

if (s.getSoTimeout() == 0) {
     s.setSoTimeout(1024);
}
SO_RECVBUF和SO_SNDBUF,可以达到的最大带宽等于缓冲区大小除以延迟,因此对于快速网络,较大的缓冲区可以显著提升性能,对于较慢网络,应该使用较小缓冲区,即最大带宽的设置需要让缓冲区大小与连接的网络延迟相匹配,使他稍小于网络带宽;Java中缓冲区的大小为发送缓冲区和接收缓冲区中较小的那个,不过需要注意的是,该选项仅给出缓冲区设置的建议,具体数值依赖于系统。
SO_KEEPALIVE,如果打开的该选项,则客户端会在一定时间内通过空闲连接发送数据包,确认服务器是否崩溃,默认SO_KEEPALIVE = false

if (!s.getKeepAlive()) {
     s.setKeepAlive(true);
}
OOBINLINE( OOB = out of bind),TCP可以发送紧急数据,该数据会立即发送,接受方在收到后会得到紧急通知,同时优先处理该数据;默认该选项为false,通过setOOBInLine设置true;虽然可以通过sendUrgentDate(int data)发送一个紧急数据,不过需要注意的是Java不区分紧急数据和非紧急数据

if (!s.getOOBInLine()) {
     s.setOOBInLine(true);
}

SO_REUSEADDR,socket在关闭时,可能不会立即释放本地端口,尤其是在关闭socket但还存在打开的接口时;此时存在一个问题是会使得其他Socket无法使用该端口;如果设置SO_REUSEADDR为true,将允许其他Socket绑定到该端口,即使此时仍可能存在前一个Socket未接收的数据;使用方式,setReuseAddress必须在为该端口绑定新的Socket之前调用,使用无参Socket以非连接的方式创建,调用setReuseAddress (true),使用connect()方法连接Socket;新旧两个Socket都必须设置SO_REUSEADDR为true;
IP_TOS服务类型,服务类型存储在IP首部名为IP_TOS的8位字段中,其中高6位为差分服务类代码点DSCP,低两位为显式拥塞通知ECN,注意在设置IP_TOS时,ECN位需要设置为0;可以通过
public int getTrafficClass()
public void setTrafficClass()
设置、获取该字段,注意DSCP字段的值仅作为参考,并不作为服务的保证。
类似的,还可以使用 public void setPerformancePreference(int connectiontime, int latency, int bandwidth) 设置连接时间、延迟、带宽的先对优先性,同样,该设置也不是服务的保证