客户端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) 设置连接时间、延迟、带宽的先对优先性,同样,该设置也不是服务的保证
相关文章
- 用socket写一个简单的客户端和服务端程序
- 跟着BOY 学习COCOS2D-X 网络篇---强联网(采用技术 BSD SOCKET+多线程技术 +protobuf)客户端实战篇
- 跟着BOY 学习COCOS2D-X 网络篇---强联网(采用技术 BSD SOCKET+多线程技术 +protobuf)客户端实战篇
- Socket网络编程--FTP客户端(60篇socket博客,而且都比较简单、深入浅出)
- Java网络编程之通过代码实现Socket通信
- Socket与SocketServer结合多线程实现多客户端与服务器通信
- java在线聊天项目0.5版 解决客户端向服务器端发送信息时只能发送一次问题 OutputStreamWriter DataOutputStream socket.getOutputStream()
- java在线聊天项目0.6版 解决客户端关闭后异常问题 dis.readUTF()循环读取已关闭的socket
- Java网络编程——第八章 客户端Socket
- java socket 单服务器多客户端实时通信