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

时间:2022-12-15 11:00: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) 设置连接时间、延迟、带宽的先对优先性,同样,该设置也不是服务的保证