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

本文详细介绍了Socket编程的基础知识,包括客户端Socket的使用方式、Socket构造与连接、Socket选项的设置等核心内容,帮助读者掌握Socket编程的基本技能。
客户端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) 设置连接时间、延迟、带宽的先对优先性,同样,该设置也不是服务的保证




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值