本笔记包含:
Socket:Socket for Client
ServerSocket:Socket for Server
一些Demo
什么是Socket?
IP+端口
无法实现底层的网络嗅探以及获得IP包结构等信息
Socket程序思路
Server端口Listen(监听)某个端口是否有连接请求;
Client向Server发出Connect请求,写数据,立即返回;
网络发送中...
Server向Client发出Accept;
连接建立;
Server/Client通过Send/Write等方法通信。
关于端口
1 client 向 server 发送时,指定了远程端口(比如90),也生成了一个随机的本地端口(比如15009);
2 server apccept 后,返回一个Socket,相当于服务器端本地Socket,其remote port依然是90,localport是刚才那个随机端口(15009)。
Socket与ServerSocket
Socket表示客户端;
ServerSocket表示服务器端;
无论是Client还是Server均在连接成功后生成一个 Socket,通过Socket实例进行通信。
创建客户端Socket
// ctor1 创建并连接远程主机
Socket socketClient = new Socket("127.0.0.1",1234);
// ctor2 创建并连接远程主机
new Socket(InetAddress addr,int port);
// 同时指定远程socket和本地socket
new Socket(
String host, /*remote*/
int port,/*remote*/
InetAddress interface,/*local*/
int localPort/*local,如果是0,则随机端口*/)
new Socket(
InetAddress host, /*remote*/
int port,/*remote*/
InetAddress interface,/*local*/
int localPort/*local,如果是0,则随机端口*/)
// 创建一个没有连接的Socket,可以通过指定 SocketAddress + connect()来连接
new Socket();
// 指定Proxy
new Socket(Proxy proxy);
获取 Socket 实例的信息
InetAddress s.getInetAddress(); // 远端服务器地址
s.getPort();// 远端port
.getLocalAddress();
.getLocaloPort();
读写
.getInputStream(); // 可读取数据
.getOutputStream(); // 写入数据流
数据流向:A->B
从A的角度看,A需要将数据Output,需要写入数据,所以 .getOutputStream().write()
从B的角度看,数据时Input而来的,需要读,所以,.getInputStream().read()
关闭 Socket
尽管 InputStream/OutputStream 关闭之后,或者GC之后,Socket会关闭,但是,最好手动关闭。
远程和本地均会关闭。
Socket s = null;
try
{
s = ...;
}
finally
{
if(s!=null)s.close();
}
Socket关闭之后,依然可以.getInetAddress()、.getPort()。
.isClosed()
如果Socket还没有Connect,那么.isClose()总是返回false。
.isConnected() ——远程连接
表示是否建立过连接。并不是当前是否连接,而是是否曾经连接过。即使.close(),也会返回true。
判断是否连接
if(isConnected() && isClose()==false)
.isBound() —— 本地连接是否已经连接
半关闭
.shutdownInput() // 只关闭 inputStream
.shutdownOutput()
.isInputShutdown();
.isOutputShutdown();
设置Socket选项
TCP_NODELAY
.setTcpNoDelay(boolean on);
.getTcpNoDealy();
开启TCP_NODELAY表示package被忽略size尽快地发送。
通常情况下(默认),为了解决“small packet problem”,多个小包数据(比如1byte)会被组装成一个大包,一次封包发送。这就是 Nagle 算法。因为假如数据包占用1byte,而TCP header 却占40bytes,频繁按照小包数据发送显然效率不高。
Nagel算法:
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
Nagle 算法的问题是需要等待数据包的确认,万一时间过长,导致Client端实时度下降。所以,适时开启TCP_NODELAY。
SO_LINGER
用来处理当close()时,如何处理没有发送的数据包。
.setSoLinger(boolean on,int seconds/*等待时长(max65535),如果为0,则未发送数据立即丢弃*/)
.getSolinger(); // -1 表示未开启,
SO_TIMEOUT
.setSoTimeout(int milliseconds)
.getSoTimeout()
为了方式 .read() 阻塞时间过长。设置后,如果超过设置时长,则引发异常,你应该处理异常,再次read()。
注意是读写超时时长,而不是连接超时时长!建立连接的时候,没有超时这个概念。
SO_RCVBUF
带宽大的时候,大Buffer;带宽小的时候,小Buffer,均可以改善性能。
大文件,需要大Buffer,小文件则不需要!
.setReceiveBufferSize(int size); // byte 数量
.getReceiveBufferSize();
SO_SNDBUF
.setSendBufferSize(int size)
.getSendBufferSize()
SO_KEEPALIVE
保持监测对方主机是否崩溃。
默认情况下关闭
.setKeepAlive(boolean on);
OOBINLINE
.sendUrgentData(int data); // 发送紧急数据,缓冲被立即冲刷
.setOOBinline(boolean on);
SO_REUSEADDR
默认关闭。如果开启,表示其他Socket可以使用上个Socket的端口。
Traffic class 流量类别,(IP头中的值)
TrafficClass包括,或者它们的组合
0x02 Low coast
0x04 Hight reliablity
0x08 Max throughput
0x10 Min delay
.setTrafficClass(int trafficClass)
ServerSocket
Socket类用来写Server并不够用,所以出现一个ServerSocket,负责监听;
Server你可以想象成一个客服,等待接听电话。
ServerSocket等待Client发出Connection请求,然后进行连接,返回一个本地Socket实例与Client进行交互。
ServerSocket
Server端的一般处理流程:
1 基于某个端口,创建一个ServerSocket。
2 ServerSocket使用.accpet()方法在端口处监听,阻塞等待,一旦有连接过来,建立连接并返回一个Socket对象。
3 采用 getInputStream() / getOutputStream() 进行交互。
4 直到双方关闭连接,Server返回第二步,继续监听。
大量连接
Server端应该创建足够量的线程(进程)对端口进行监听,以满足Client的大量连接。
也可以通过 ServerSocketChannel(基于Channel而非Stream),使用no-blocking、multiplexed I/O等概念,使得单个进程处理大量连接。
连接排队
多个Connection请求会被OS存储在一个queue中。不同系统,队列长度不同。
构造 ServerSocket
new ServerSocket(int port); // 本机所有网卡和ip上监听
new ServerSocket(int port, int queueLength/*Client到达来的Connection请求队列数量*/);
new ServerSocket(int port, int queueLength, InetAddress bindAddress);
new serverSocket(); // 不指定端口,则监听所有端口;可以使用.bind()稍后绑定。
Accept()
阻塞,等待Client的ConnectionRequest,一旦建立连接,返回Socket对象。
通常把.accpt()放到一个新线程里面,或者采用 NIO 的方式。
.close()
.isClosed() // 无参构造函数创建,还未绑定,均会返回 false
.isBound()
判断ServerSocket是否打开
ss.isBound() && ss.isClosed()==false
选项
SO_TIMEOUT
.setSoTimeout(int t/* 默认是0,表示永远等待 */);
超过超时时间,引发InterruptedIOException,在里面可处理。
SO_REUSEADDR
SO_RCVBUF
.setPerformancePreferences(int connectionTime, int latency, int bandwidth)