转载:http://www.blogjava.net/landon/archive/2013/07/02/401137.html
Java网络编程精解笔记2:Socket详解
Socket用法详解
在C/S通信模式中,client需要主动创建于server连接的Socket(套接字).服务器端收到了客户端的连接请求,也会创建与客户连接的Socket.Socket可看做是通信两端的收发器.server与client都通过Socket来收发数据.
1.构造Socket
1.Socket()
2.Socket(InetAddress address,int port) throws UnknownHostException,IOException
3.Socket(InetAddress addrss,int port,InetAddress localAddr,int localPort) throws IOException
4.Socket(String host,int port) throws UnknownHostException,IOException
5.Socket(String host,int port,InetAddress localAddr,int localPort) throws IOExcception
除了第一个不带参数的构造方法外,其他构造方法都会试图建立与服务器的连接.如果连接成功,就返回Socket对象.如果因为某些原因连接失败,则抛出IOException.
2.设定等待建立连接的超时时间
1.客户端的Socket构造方法请求与server连接时,可能要等待一段时间.默认会一直等待下去,直到连接成功或者出现异常.Socket构造方法请求连接时,受底层网络传输速度的影响,可能处于长时间的等待状态.
->希望限定等待连接的时间
->Socket socket = new Socket();
SocketAddress rermoteAddr = new InetSocketAddress("localhost",8000);
socket.connect(remoteAddr,60000);//设置等待建立连接的超时时间为1分钟
->如果1分钟之内连接成功,则connect顺利返回.如果1分钟之内出现异常,则抛出该异常.如果超过了1分钟,即没有连接成功,也没有出现其他异常.则会抛出
SocketTimeoutException
2.Socket#connect(SocketAddress endpoint,int timeout).endpoint为服务器的地址,timeout设定超时时间.ms->
timeout为0,表示永远不会超时.
3.设定服务器的地址.
Socket(InetAddress address,int port)
Socket(String host,int port)
InetAddress表示服务器的IP地址.->该类提供了一系列的静态工厂方法.用于构造自身的实例.如
1.InetAdress addr1 = InetAdress.getLocalHost();
2.InetAddress addr2 = InetAddress.getByName("10.10.137.44");
3.InetAddress addr2 = InetAddress.getByName("www.javathinker.org");
4.设定客户端的地址
1.在一个Socket对象中,即包含远程服务器的IP和端口信息,也包含本地客户端的IP地址和端口信息.默认情况下,客户端的IP地址来自客户程序所在的主机,而客户端的端口则有操作系统随机分配.
->Socket(InetAddress address,int port,InetAddress localAddress,int localPort) throws IOException
->Socket(String host,int port,InetAddress localAddress,int localPort) throws IOException
上两个方法用来设置客户端的ip端口和地址
->这种情况主要适用于一个主机同时属于两个以上的网络,它可能拥有两个以上的IP地址.如一个在Internet,一个在局域网.
->如果希望和局域网的服务器程序通讯,则可以以局域网的IP地址作为localAddress来构造Socket.
5.客户连接服务器时可能抛出的异常
1.UnknownHostException:无法识别主机的名字或ip地址时,就会抛出此异常
2.ConnectException:如果没有服务器监听指定的端口;或者服务器进程拒接连接,则会抛出此异常.
3.SocketTimeoutException:如果等待连接超时,就会抛出此异常.
4.BindException:如无法把Socket对象与指定的本地IP地址或端口绑定则会抛出此异常.
IOException
-UnknownHostException
-InterruptedIOException
-SocketTimeoutException
-SocketException
-BindException
-ConnectException
6.获取Socket的信息
1.同时包含了远程服务器的IP和端口信息以及客户本地的IP和端口信息.
2.获取输出流合输入流,分别用于向服务器发送数据以及接收从服务端发来的数据.
1.getInetAddress():获得远程服务器的IP地址
2.getPort():获得远程服务器的端口
3.getLocalAddress():获得客户本地的IP地址
4.getLocalPort():获得客户本地的端口
5.getInputStream():获得输入流.如果Sokcet还没有连接或已经关闭或者已经通过shutdownInput方法关闭输入流,则才方法会抛出IOException.
6.getOutputStream(): 获得输入流..如果Sokcet还没有连接或已经关闭或者已经通过shutdownOutput方法关闭输出流,则才方法会抛出IOException.
7.关闭Socket
1.当client与Server通信结束,应该及时关闭Socktt,以释放Socket占用的包括端口在内的各种资源.
2.Socket#close方法负责关闭socket.当一个Socket对象被关闭就不能通过其输入和输出流进行io操作.否则会导致IOException.
3.确保关闭Socket的操作就是被执行,建议把该操作放在finally代码块中.如
Socket socket = null;
try
{
socket = new Socket("www.thinker.org",80);
//执行接收和发送数据的操作
...
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
4.Socket类提供了3个状态测试方法.
1.isClosed()
1.public synchronized void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
2.public boolean isClosed() {
synchronized(closeLock) {
return closed;// true if the socket has been closed
}
}
2.isConnected()
1.true if the socket was successfuly connected to a server
2. Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
(see {@link #isClosed()}) if it was successfuly connected prior to being closed.
3.isBound()
1. true if the socket was successfuly bound to an address
2.Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
(see {@link #isClosed()}) if it was successfuly bound prior to being closed.
4.判断一个Socket对象是否处于连接状态,用以下形式:
boolean isConnected = socket.isConnected() && !socket.isClosed()
8.半关闭Socket
A进程与B进程通过Socket通信.假定A输出数据,B读入数据.A如何告诉B所有数据已经输出完毕:
1.A与B交换的是字符流,且一行一行的读写.可事先约定以一个特殊标志作为结束标志,如以"bye"作为结束标志.当A向B发送一行字符串"bye"时,B读到这一行数据时,则停止读数据.{@link EchoServer},{@link EchoClient}
2.进程A先发送消息,告诉B所发送正文的长度.->再发送正文.->B先获知A发送的正文长度->接下来只要读取完该长度的字符或者字节,就停止读数据.
3.A发完所有数据后,关闭Socket->B读取A发送的所有数据后->InputStream#read->该方法返回-1.->BufferedReader#readLine->返回null.
{@link HTTPClient}
4.Socket#close->输入输出流都被关闭->有时候希望仅关闭输入流或输出流之一->Socket半关闭方法->
shutdownInput():关闭输入流
shutdownOutput():关闭输出流
->B读取数据时,如果A的输出流已经关闭->B读入所有数据后,就会读到输入流的末尾.
->先后调用Socket的shutdonwInput和shutdownOutput方法.仅仅是关闭了输入流和输出流,并不等价Socket#close.->通信结束后,依然要调用Socket的close方法.只有该方法才会释放Socket占用的资源.如占用的本能地端口等.
5.Socket#isInputShutdown()->输入流关闭,返回true.
Socket#isOutputShutdown()->输出流关闭,返回true
6.client与Server通信时,如果有一方突然结束程序或者关闭了Socket或者单独关闭了输入流或输出流.对另一方会造成什么影响.
{@link Sender} {@link Receiver}.
9.设置Socket选项
1.TCP_NODELAY:表示立即发送数据
1.public void setTcpNoDelay(boolean on) throws SocketException
2.public boolean getTcpNoDelay() throws SocketException
3.默认情况,发送数据采用Negale算法.即指发送方发送的数据不会立即发出,而是先放到缓冲区内.等缓冲区区慢了再发出.->发送完一批数据等待接收方对这批数据的回应.->再发送下一批数据.->适用于发送方需要发送大批量数据,且接收方会及时回应的场合->通过减少传输数据的次数来提高通信效率.
->对于发送方持续发送小批量数据,且接收方不一定立即发送响应->该算法会使发送方运行很慢->如实时网络游戏
4.TCP_NODELAY默认值为false->即表示采用Negale算法.->setTcpNoDelay(true)->关闭Socket缓存,确保数据及时发送
5.if(!socket.getTcpNoDelay()){socket.setTcpNoDelay(true)}
6.Socket底层不支持该选项,则抛出SocketException.
2.SO_REUSEADDR:表示是否允许重用Socket所绑定的本地地址
1.public void setReuseAddress(boolen on) throws SocketException
2.public boolean getReuseAddress() throws SocketException
3.接收方通过Socket#close关闭Socket->如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立刻释放本地端口->会等待一段时间->确保接收到了网络上发送过来的延迟数据->释放端口->Socket收到延迟数据后,不会对这些数据做任何处理->Socket接收延迟数据的目的->确保这些数据不会被其他恰巧绑定到同样端口的新进程接收到.
4.客户端程序一般采用随机端口->出现两个client程序绑定到同样端口的可能性不大
5.server程序采用固定端口->server关闭后,其端口可能还会被占用一段时间->此时如果重启程序,端口已经被占用->使得程序无法绑定到给端口->启动失败
6.确保一个进程关闭Socket后,即使其还未释放端口->同一个主机上的其他进程还可以立即重用该端口->
if(!socket.getReuseAddress()){socket.setReuseAddress(true)}
7.该方法必须在Socket还未绑定到一个本地端口之前调用.否则无效.
1.Socket socket = new Socket();
socket.setResueAddress(true);
socket.connect(new InetSocketAddress("localhost",8080));
2.Socket socket = new Socket();
socket.setResueAddress(true);
socket.bind(new InetSocketAddress("localhost",9000));
socket.connect(new InetSocketAddress("remotehost",8000));
8.两个公用一个端口的进程必须都调用socket.SetResueAddress(true)->才能使得一个进程关闭Socket后,另一个进程的Socket能立即重用相同端口.
9.当多个ServerSocket对象同时绑定一个端口时,系统会随机选择一个ServerSocket对象来接收客户端请求->接收客户端请求的ServerSocket对象必须关闭才能轮到其他的ServerSocket对象接收客户端请求。如果不关闭这个ServerSocket对象,那么其他的ServerSocket对象将永远无法接收客户端请求
3.SO_TIMEOUT:表示接收数据时的等待时间
1.public void setSoTimeout(int milliseconds) throws SocketException
2.public int getSoTimeout() throws SocketException
3.通过Socket的输入流读数据时,如果还未有数据,则等待:
如:
byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
in.read(buff);
->输入流没有数据,则in.read(buff)就会等待发送方发送数据->结束等待条件:
1.输入流中有1024个字节->read将其读到buff中->返回读到的字节数
2.距离输入流末尾还有小雨1024个字节->read读到buff中,返回读到的字节数
3.读到输入流的末尾
4.连接已经断开,抛出IOException
5.Socket#setSoTimeout设置了等待超时时间,超过这一时间则抛出SocketTimeoutException
4.该选项用于设定接收数据的等待超时时间,单位为毫秒->默认值为0,表示无限等待,永远不会超时.
5.该方法必须在接收数据之前执行才有效.
6.输入流的read方法抛出SocketTimeoutException后,Socket依然是连接的->可尝试再次读取数据->
socket.setTimeout(3 * 60 * 1000);
byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
int len = -1;
do
{
try
{
len = in.read(buff);
// 处理读到的数据
...
}
catch(SocketTimeoutException e)
{
e.printStackTrace();
len = 0;
}
}
while(len != -1)
4.SO_LINGER:表示当执行Socket的close方法时,是否立即关闭底层Socket
1.public void setSoLinger(boolean on,int seconds) throws SocketException
2.public int getSoLinger() throws SocketException
3.该选项用来控制Socket关闭时的行为->默认执行Socket的close,该方法会立即返回.但是底层的Socket不立即关闭,会延迟一段时间,知道发送完所有剩余的数据->真正关闭socket->断开连接.
4.socket.setSoLinger(true,0)->执行Socket#close时,该方法立即返回且底层的Socket也会立即关闭->所有未发送完的剩余数据被丢弃.
5.socket.setSoLinger(true,60)->
1.Socket#close->该方法不会立即返回->进入阻塞状态
2.底层的Socket会尝试发送剩余的数据->返回条件:
1.底层的Socket已经发送完所有剩余数据
2.尽管底层的Socket还没有发送完所有的剩余数据->但是已经阻塞了60秒->也会返回->剩余未发送的数据将被丢弃
1.->close返回后->底层的Socket会被关闭,断开连接
2.setSoLinger(boolean on,int seconds)->seconds参数以秒为单位->
6.程序通过输出流写数据时,->仅表示程序向网络提交了一批数据->由网络负责输送到到接收方->程序关闭Socket时,有可能这批数据还在网络上传输,未达到接收方->未发送完的数据指还在网络上传输未被接收方接收的数据
{@link TestLingerClient} {@link TestLingerServer}
5.SO_SNDBUF:表示发送数据的缓冲区大小
1.public void setSendBufferSize(int size) throws SocketException
2.public int getSendBufferSize() throws SocketException
3.该选项用来表示Socket用于输出数据的缓冲区的大小->底层Socket不支持该选项->set 抛出SocketException
6.SO_RCVBUF:表示接收数据的缓冲区大小
1.public void setReceiveBufferSize(int size) throws SocketException
2.public int getReceiveBufferSize() throws SocketException
3.该选项用来表示Socket的用于输入数据的缓冲区的大小->传输大的连续的数据块,如基于HTTP和FTP协议的通信,可以使用较大缓冲区->减少数据传输的次数->提高传输数据的效率->对于交互频繁且单次传送数据量比较小的通信方式如Telnet和网络游戏,则应该采用交换缓冲区.确保小批量的数据能及时发送给对方.-->设定缓冲区大小的原则使用与SO_SNDBUf选项
4.底层Socket不支持该选项->set 抛出SocketException.
7.SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭.
1.public void setKeepAlive(boolean on) throws SocketException
2.public boolean getKeepAlive() throws SocketException
3.该选项为true->底层的TCP实现会监视该连接是否有效->当连接处于空闲状态(连接的两端没有互相传送数据)->超过2小时->本地的TCP实现会发送一个数据包一个远程的Socket->远程Socket没有发回响应->TCP实现持续尝试11分钟->直到接收到响应->12分钟内未收到响应->TCP实现就会自动关闭本地Socket,断开连接->不同的网络平台,TCP实现尝试与远程Socket对话的实现会有所差别.
4.该选项为false->表示TCP不会监视连是否有效->不活动的client可能会永久存在下去->而不会注意server已经崩溃
5.if(!socket.getKeepAlive()){socket.setKeepAlive(true)}
8.OOBINLINE:表示是否支持发送一个字节的TCP紧急数据
//注OOB:out-of-band 带外
1.public void setOOBInline(boolean on) throws SocketException
2.public int getOOBInline() throws SocketException
3.该选项为true,表示支持发送一个自己的TCP紧急数据->Socket#sendUrgentData(int data),用于发送一个字节的TCP紧急数据
4.该选项为false->接收方收到紧急数据时不做处理,直接丢弃->需要socket.setOOBInline(true)->接收方会将接收到的紧急数据与普通数据放在同样的队列->注:除非采用更高层次的协议,否则接收方处理紧急数据的能力非常有限->紧急数据到来时,接收方不会得到任何通知->因此很难区分普通数据与紧急数据->只好按照同样的方式处理.
10.服务类型选项
1.用户去邮局时,可选择不同的服务->发送普通信 | 挂号信 | 快件->价格,发送速度及可靠性均不同.
2.Internet上传输数据也分为不同的服务器类型.->如发送视频需要较高的宽带,快速到达目的,保证接收方看到连续的画面.
3.IP规定了4种服务类型,定性的描述服务的质量:
1.低成本->发送成本低.
2.高可靠性->保证把数据可靠的送达目的地.
3.最高吞吐量->一次性可以接收或发送大批量的数据
4.最小延迟->传输数据的速度要快,把数据快速送达目的地.
4.4种服务类型可以组合->即可进行或运算
IPTOS_LOWCOST (0x02)
IPTOS_RELIABILITY (0x04)
IPTOS_THROUGHPUT (0x08)
IPTOS_LOWDELAY (0x10)
{@link Socket#setTrafficClass}
5.public void setTrafficClass(int trafficClass) throws SocketException
public int getTrafficClass() throws SocketException
11.设置连接时间,延迟和带宽的相对重要性(注意相对二字)
1.Socket#setPerformancePreferences(int connectionTime,int latency,int bandwidth)
2.3个参数为网络传输数据的3项指标
1.connectionTime-表示用最少时间建立连接
2.latency-表示最小延迟
3.bandwidth-表示最高带宽
->三项指标的相对重要性.->3项参数的整数之前的相对大小决定了响应参数的相对重要性.
如setPerformancePreferences(2,1,3)->则表示最高带宽最重要,其实是最少连接时间,最后是最小延迟.
12.小结:
1.通信过程中,如果发送方没有关闭Socket,就突然中止程序,则接收方在接收数据时会抛出SocketException.
2.发送方发送完数据后,应该及时关闭Socket或关闭Socket的输出流,这样,接收方就能顺利读到输入流的末尾.
部分源代码:
package com.game.landon.socket;
import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
/**
*
*测试Socket连接服务器可能抛出的异常
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/
public class ConnectTester
{
public static void main(String[] args)
{
//默认的host+port
String host = "localhost";
int port = 8000;
//通过main参数解析host+port
if(args.length > 1)
{
host = args[0];
port = Integer.parseInt(args[1]);
}
new ConnectTester().connect(host, port);
}
public void connect(String host,int port)
{
SocketAddress remoteAddress = new InetSocketAddress(host,port);
Socket socket = null;
String result = "";
try
{
long begin = System.currentTimeMillis();
socket = new Socket();//这里未指定任何参数
// socket.connect(remoteAddress,5000);//设置超时时间为5秒
socket.connect(remoteAddress,100);//超时时间设短,用来测试SocketTimeoutException
long end = System.currentTimeMillis();
result = (end - begin) + "ms";//计算连接所化的时间
}
catch(BindException e)//绑定异常
{
result = "Local address and port can't be binded";
//1.调用Socket#bind方法绑定本地IP|端口
//2.Socket构造方法中指定本地IP|端口
//->如果本地主机不具有IP地址或者端口已经被占用,则会抛出此异常
}
catch(UnknownHostException e)//无法识别主机server的ip地址
{
result = "Unknown host";//测试参数 unknownhost 80
}
catch(ConnectException e)//如果没有服务器进程监听指定的端口,或者服务器进程拒绝连接,就会抛出这种异常
{
result = "Connection refused";//测试参数1: localhost 7777(没有服务器进程监听7777端口) 测试2:server指定连接请求队列的长度
}
catch(SocketTimeoutException e)//服务器超时就会抛出此异常
{
result = "Timeout";// 测试参数 www.javathinker.org 80
}
catch(IOException e)
{
result = "failure";
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
//打印结果
System.out.println(remoteAddress + " : " + result );
}
}
package com.game.landon.socket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
/**
*
*Socket连接超时
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-9
*
*/
public class ConnectTimeout
{
public static void main(String[] args)
{
try
{
Socket socket = new Socket();
SocketAddress serverAddr = new InetSocketAddress("localhost", 8000);
socket.connect(serverAddr, 60 * 1000);//指定1分钟超时时间
}
catch(SocketTimeoutException timeoutException)
{
System.out.println("connect localhost:8000 timeout in 1minutes:" + timeoutException);
}
catch(IOException ioException)
{
System.out.println("connect localhost:8000 fail:" + ioException);
}
}
}
package com.game.landon.socket;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
/**
*
*测试连接到一个http服务器,然后发送http协议的请求,接着接收从http服务器发回的响应结果
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/
public class HTTPClient
{
String host = "www.javathinker.org";
int port = 80;
Socket socket;
public void createSocket() throws Exception
{
socket = new Socket(host,port);
}
//访问网页www.javathinker.org/index.jsp
public void communication() throws Exception
{
//组装http请求协议
StringBuffer sb = new StringBuffer("GET " + "/index.jsp" + " HTTP/1.1\r\n");
sb.append("Host:www.javathinker.org\r\n");
sb.append("Accept:*/*\r\n");
sb.append("Accept-Language:zh-cn\r\n");
sb.append("Accept-Encoding:gzip,deflate\r\n");
sb.append("User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Window NT 5.0)\r\n");
sb.append("Connection:Keep-Alive\r\n\r\n");
//发出http请求->request
OutputStream socketOut = socket.getOutputStream();
// 发送数据时,先把字符串形式的请求信息转换为字节数组,即字符串的编码 sb.toString().getBytes()
socketOut.write(sb.toString().getBytes());
socket.shutdownOutput();//关闭输出流
//接收响应结果->response
InputStream socketIn = socket.getInputStream();
// 接收数据时把接收到的字节写到一个ByteArrayOutputSteam中,其是一个容量能够自动增长的缓冲区.
//socketIn.read(buff)返回-1,则表示独到了输入流的末尾
// 问题,如果接收的网页数据量很大,则先把这些数据全部保存在ByteArrayOutputSteam,很不明智,因为这些数据会占用大量内存.->
//更有效的做法是利用BufferReader来逐行读取数据
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = -1;
while((len = socketIn.read(buff)) != -1)
{
//将buff写入buffer
buffer.write(buff, 0, len);
}
System.out.println(new String(buffer.toByteArray()));//把字节数组转为字符串
socket.close();
}
//利用BufferReader逐行读取数据
public void communication2() throws Exception
{
//组装http请求协议
StringBuffer sb = new StringBuffer("GET " + "/index.jsp" + " HTTP/1.1\r\n");
sb.append("Host:www.javathinker.org\r\n");
sb.append("Accept:*/*\r\n");
sb.append("Accept-Language:zh-cn\r\n");
sb.append("Accept-Encoding:gzip,deflate\r\n");
sb.append("User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Window NT 5.0)\r\n");
sb.append("Connection:Keep-Alive\r\n\r\n");
//发出http请求->request
OutputStream socketOut = socket.getOutputStream();
// 发送数据时,先把字符串形式的请求信息转换为字节数组,即字符串的编码 sb.toString().getBytes()
socketOut.write(sb.toString().getBytes());
socket.shutdownOutput();//关闭输出流
//接收响应结果->response
InputStream socketIn = socket.getInputStream();
// 问题,如果接收的网页数据量很大,则先把这些数据全部保存在ByteArrayOutputSteam,很不明智,因为这些数据会占用大量内存.->
//更有效的做法是利用BufferReader来逐行读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
String data;
while((data = br.readLine()) != null)
{
System.out.println(data);
}
socket.close();
}
public static void main(Stringargs) throws Exception
{
HTTPClient client = new HTTPClient();
client.createSocket();
// client.communication();
client.communication2();
}
}
package com.game.landon.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
*
*发送邮件的SMTP客户程序
*
*<pre>
*1.SMTP-Simple Mail Transfer Protocol,简单邮件传输协议,应用层协议,建立在TCP/IP协议基础之上.
*2.RFC821
*3.SMTP服务器默认监听25端口.客户程序请求发送邮件,服务器负责将邮件传输到目的地.
*4.client会发送一系列SMTP命令,服务器会做出响应,返回应答码及对应答码的描述
*<pre>
*
*<output>
Server>220 EX-01.hec.intra Microsoft ESMTP MAIL Service ready at Wed, 26 Jun 2013 12:56:47 +0800
Client>HELO PC
Server>250 EX-01.hec.intra Hello [10.130.137.44]
Client>MAIL FROM:<wenyong.lv@happyelements.com>
Server>550 5.7.1 Client does not have permissions to send as this sender
Client>RCPT TO:<wenyong.lv@happyelements.com>
Server>503 5.5.2 Need mail command
Client>DATA
Server>503 5.5.2 Need mail command
Client>Subject:hello
I just test smtp using java.
Client>.
Server>500 5.3.3 Unrecognized command
Client>QUIT
Server>500 5.3.3 Unrecognized command
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-25
*
*/
public class MailSender
{
private String smtpServer = "EX-01.hec.intra";//公司邮箱服务器的域名
private int port = 25;
private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true);
}
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
/**
*
* 发送一行字符串并接收服务器的响应数据
*
* @param str
* @param reader
* @param writer
*/
private void sendAndReceive(String str,BufferedReader reader,PrintWriter writer) throws IOException
{
if(str != null)
{
System.out.println("Client>" + str);
writer.println(str);//是println.需要发送\r\n
}
String response;
if((response = reader.readLine()) != null)
{
System.out.println("Server>" + response);
}
}
/**
*
* 发送邮件
*
* @param msg
*/
public void sendMail(Message msg)
{
Socket socket = null;
try
{
socket = new Socket(smtpServer,port);//连接至邮件服务器
BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);
String localhost = InetAddress.getLocalHost().getHostName();
//因为连接成功时,SMTP服务器会返回一个应答码为220的响应,表示就绪.
//214-帮助信息 220-服务就绪 221-服务关闭 250-邮件操作完成 354-开始输入邮件内容,以.结束 421-服务未就绪,关闭传输通道
//501 命令参数格式错误 502 命令不支持 503 错误的命令序列 504 命令参数不支持
sendAndReceive(null, reader, writer);//为了接收服务器的响应数据
sendAndReceive("HELO " + localhost, reader, writer);//HELO | EHLO表示邮件发送者的主机地址
sendAndReceive("MAIL FROM:<" + msg.from + ">", reader, writer);//邮件发送者的邮件地址
sendAndReceive("RCPT TO:<" + msg.to + ">", reader, writer);//邮件接收者送者的邮件地址
//邮件内容
sendAndReceive("DATA", reader, writer);
writer.println(msg.data);
System.out.println("Client>" + msg.data);
// 发送完毕
sendAndReceive(".", reader, writer);
// 退出
sendAndReceive("QUIT", reader, writer);
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public static void main(Stringargs)
{
Message message = new Message("wenyong.lv@happyelements.com",
"wenyong.lv@happyelements.com",
"hello",
"I just test smtp using java.");
new MailSender().sendMail(message);
}
}
/**
*
* 一封邮件消息
*
* @author landon
*
*/
class Message
{
/** 发送者地址 */
String from;
/** 接收者地址 */
String to;
/** 标题 */
String subject;
/** 正文 */
String content;
/** 数据<标题+正文> */
String data;
public Message(String from,String to,String subject,String content)
{
this.from = from;
this.to = to;
this.subject = subject;
this.content = content;
data = "Subject:" + subject + "\r\n" + content;
}
}
package com.game.landon.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
*
*有些SMTP服务器要求客户提供身份认证信息.本例使用126邮箱进行测试,向qq邮箱测试,注意发送主题和内容要正规一些
*否则发送时会被认为是垃圾邮件,不被发送
*
*1.先发送EHLO
*2.发送AUTH LOGIN
*3.采用Base64编码用户名和口令.即可通过认证.
*
*{@link MailSender}
*Server>550 5.7.1 Client does not have permissions to send as this sender
*
*<output>
Server>220 126.com Anti-spam GT for Coremail System (126com[20121016])
Client>HELO PC
Server>250 OK
Client>AUTH LOGIN
Server>334 dXNlcm5hbWU6
Client>c210cGxhbmRvbg==
Server>334 UGFzc3dvcmQ6
Client>YTEyMzQ1Ng==
Server>235 Authentication successful
Client>MAIL FROM:<smtplandon@126.com>
Server>250 Mail OK
Client>RCPT TO:<340706410@qq.com>
Server>250 Mail OK
Client>DATA
Server>354 End data with <CR><LF>.<CR><LF>
Client>Subject:hello,我是stmplandon,测试一下stmp
I just test smtp using java.ok??
Client>.
Server>250 Mail OK queued as smtp4,jdKowECpUWU+gcpR0HQUCA--.1261S2 1372225854
Client>QUIT
Server>221 Bye
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-26
*
*/
public class MailSenderWithAuth
{
private String smtpServer = "smtp.126.com";//使用126邮箱进行测试
private int port = 25;
private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true);
}
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
/**
*
* 发送一行字符串并接收服务器的响应数据
*
* @param str
* @param reader
* @param writer
*/
private void sendAndReceive(String str,BufferedReader reader,PrintWriter writer) throws IOException
{
if(str != null)
{
System.out.println("Client>" + str);
writer.println(str);//是println.需要发送\r\n
}
String response;
if((response = reader.readLine()) != null)
{
System.out.println("Server>" + response);
}
}
/**
*
* 发送邮件
*
* @param msg
*/
public void sendMail(Message msg)
{
Socket socket = null;
try
{
socket = new Socket(smtpServer,port);//连接至邮件服务器
BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);
String localhost = InetAddress.getLocalHost().getHostName();
//因为连接成功时,SMTP服务器会返回一个应答码为220的响应,表示就绪.
//214-帮助信息 220-服务就绪 221-服务关闭 250-邮件操作完成 354-开始输入邮件内容,以.结束 421-服务未就绪,关闭传输通道
//501 命令参数格式错误 502 命令不支持 503 错误的命令序列 504 命令参数不支持
sendAndReceive(null, reader, writer);//为了接收服务器的响应数据
sendAndReceive("HELO " + localhost, reader, writer);//HELO | EHLO表示邮件发送者的主机地址
sendAndReceive("AUTH LOGIN", reader, writer);//认证命令
// 新注册的一个126账号
String userName = new sun.misc.BASE64Encoder().encode("smtplandon".getBytes());
String pwd = new sun.misc.BASE64Encoder().encode("a123456".getBytes());
sendAndReceive(userName, reader, writer);
sendAndReceive(pwd, reader, writer);
sendAndReceive("MAIL FROM:<" + msg.from + ">", reader, writer);//邮件发送者的邮件地址
sendAndReceive("RCPT TO:<" + msg.to + ">", reader, writer);//邮件接收者送者的邮件地址
//邮件内容
sendAndReceive("DATA", reader, writer);
writer.println(msg.data);
System.out.println("Client>" + msg.data);
// 发送完毕
sendAndReceive(".", reader, writer);
// 退出
sendAndReceive("QUIT", reader, writer);
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public static void main(Stringargs)
{
Message message = new Message("smtplandon@126.com",
"340706410@qq.com",
"hello,我是stmplandon,测试一下stmp",
"I just test smtp using java.ok??");
new MailSenderWithAuth().sendMail(message);
}
}
package com.game.landon.socket;
import java.io.IOException;
import java.net.Socket;
/**
*
*扫描1到1024的端口,用Socket连接这些端口,如果Socket对象创建成功,则说明在这些端口中有服务器程序在监听
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-9-27
*
*/
public class PortScanner
{
public static void main(Stringargs)
{
String host = "localhost";
new PortScanner().scan(host);
}
/**
*
* 扫描指定的Host下的各端口的服务器程序
*
* @param host
*/
public void scan(String host)
{
Socket socket = null;
for(int port = 0;port < 1024;port++)
{
try
{
//该构造方法就会试图建立与服务器的连接
socket = new Socket(host,port);
System.out.println("There is a Server on Port:" + port);
}
catch(IOException e)
{
System.out.println("Can't connect to port:" + port);
}
finally
{
try
{
if(socket != null)
{
socket .close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
}
package com.game.landon.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
/**
*
*接收数据的服务器程序,每隔1秒接收一行字符串.共接收20行字符串
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-14
*
*/
public class Receiver
{
private int port = 8000;
private ServerSocket serverSocket;
private static int stopWay = 1;
private final int NATURAL_STOP = 1;
private final int SUDDEN_STOP = 2;
private final int SOCKET_STOP = 3;
private final int INPUT_STOP = 4;
/** 关闭ServerSocket,再结束程序 */
private final int SERVERSOCKET_STOP = 5;
public Receiver() throws IOException
{
serverSocket = new ServerSocket(port);
System.out.println("server has started.");
}
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
public void receive() throws Exception
{
Socket socket = null;
socket = serverSocket.accept();
BufferedReader br = getReader(socket);
for(int i = 0;i < 20;i++)
{
//client socket close后,readLine则会抛出此异常
// Exception in thread "main" java.net.SocketException: Connection reset
// at java.net.SocketInputStream.read(Unknown Source)
// at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
// at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
// at sun.nio.cs.StreamDecoder.read(Unknown Source)
// at java.io.InputStreamReader.read(Unknown Source)
// at java.io.BufferedReader.fill(Unknown Source)
// at java.io.BufferedReader.readLine(Unknown Source)
// at java.io.BufferedReader.readLine(Unknown Source)
// at com.game.landon.socket.Receiver.receive(Receiver.java:59)
String msg = br.readLine();
System.out.println("receive:" + msg);
TimeUnit.MILLISECONDS.sleep(1000);
if(i == 2)
{
//2.通过提前停止receiver.发现Sender依然会发送全部的20行字符.
//因为进入Receiver结束运行,但是底层的Socket并没有立即释放本地端口.OS检测还没有发送给Socket的数据,会使底层Socket继续占用本地端口一段时间
if(stopWay == SUDDEN_STOP)
{
System.out.println("sudden stop");
System.exit(0);
}
else if(stopWay == SOCKET_STOP)
{
System.out.println("close socket and stop");
socket.close();
break;
}
else if(stopWay == INPUT_STOP)
{
System.out.println("shutdown the input and stop");
socket.shutdownInput();
break;
}
else if(stopWay == SERVERSOCKET_STOP)
{
System.out.println("close serverSocket and stop");
serverSocket.close();
break;
}
}
}
//1.server和client均已正常结束方式运行的话,因为二者sleep的时间不同.所以server可能再次read的时候会出现异常:
//Exception in thread "main" java.net.SocketException: Connection reset
//at java.net.SocketInputStream.read(Unknown Source)
//这样的话,其实server可能会丢失读了部分数据(Connection reset.Client的Socket已经close了->client的数据可能还在网络传输,即还未被接收方接收).
//查一下是否是Socket选项问题
if(stopWay == NATURAL_STOP)
{
socket.close();
serverSocket.close();
}
}
public static void main(String[] args) throws Exception
{
if(args.length > 0)
{
stopWay = Integer.parseInt(args[0]);
}
new Receiver().receive();
}
}
package com.game.landon.socket;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
/**
*
*读取SendClient发送来的数据,直到抵达输入流的末尾
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/
public class ReceiveServer
{
public static void main(Stringargs) throws Exception
{
ServerSocket serverSocket = new ServerSocket(8000);
Socket socket = serverSocket.accept();
// 设置接收数据的等待时间
socket.setSoTimeout(3 * 1000);
InputStream in = socket.getInputStream();
ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = -1;
do
{
try
{
//1.启动ReceiveServer再启动SendClient.->因为client至发送了helloworld.所以不能读到足够的数据填满buff.->一直等待->client睡眠结束,关闭Socket
//->ReceiverServer读到输入流末尾->立即结束等待->read返回-1.
//2.启动ReceiveServer再启动SendClient->in.read一直在等待->在client随眠期间,关掉client->抛出Exception in thread "main" java.net.SocketException: Connection reset
//3.socket.setSoTimeout(3 * 1000)->加上这个后,in.read则会超时抛出异常
len = in.read(buff);
if(len != -1)
{
bufferStream.write(buff, 0, len);
}
}
catch(SocketTimeoutException e)
{
System.err.println("read timeout");
len = 0;
}
}
while(len != -1);
System.out.println(new String(bufferStream.toByteArray()));
}
}
package com.game.landon.socket;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
/**
*
*发送字符串->sleep->关闭Socket
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/
public class SendClient
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket("localhost",8000);
OutputStream out = socket.getOutputStream();
out.write("hello".getBytes());
out.write("world".getBytes());
// sleep
TimeUnit.MILLISECONDS.sleep(5 * 1000);
socket.close();
}
}
package com.game.landon.socket;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
/**
*
* 发送数据的客户程序,每隔500毫秒发送一行字符串.共发送20行字符串
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-6-14
*
*/
public class Sender
{
private String host = "localhost";
private int port = 8000;
private Socket socket;
/** 结束通信的方式 */
private static int stopWay;
/** 自然结束 */
private final int NATURAL_STOP = 1;
/** 突然终止程序 */
private final int SUDDEN_STOP = 2;
/** 关闭Socket,再结束程序 */
private final int SOCKET_STOP = 3;
/** 关闭输出流,再结束程序 */
private final int OUTPUT_STOP = 4;
public static void main(Stringargs) throws Exception
{
if(args.length > 0)
{
stopWay = Integer.parseInt(args[0]);
}
new Sender().send();
}
public Sender() throws IOException
{
socket = new Socket(host, port);
}
private PrintWriter getWriter(Socket socket) throws IOException
{
return new PrintWriter(socket.getOutputStream(), true);
}
public void send() throws Exception
{
PrintWriter pw = getWriter(socket);
for(int i = 0;i < 20;i++)
{
String msg = "hello_" + i;
pw.println(msg);
System.out.println("send:" + msg);
TimeUnit.MILLISECONDS.sleep(500);
if(i == 2)
{
//1.sender突然中止,server会抛出:Exception in thread "main" java.net.SocketException: Connection reset
//at java.net.SocketInputStream.read(Unknown Source)
if(stopWay == SUDDEN_STOP)
{
System.out.println("sudden stop");
System.exit(0);
}
else if(stopWay == SOCKET_STOP)
{
System.out.println("socket close");
socket.close();
break;
}
else if(stopWay == OUTPUT_STOP)//2.如果send以这种方式运行,则server会出现:receive:null receive:null receive:null.
//因为已经shutdownOutput.server调用readLine方法时读到了输入流的末尾,因为返回null
{
System.out.println("socket shutdown outputstream");
socket.shutdownOutput();
break;
}
}
}
if(stopWay == NATURAL_STOP)
{
socket.close();
}
}
}
package com.game.landon.socket;
import java.net.Socket;
/**
*
*simple client,用来测试服务器的连接请求队列的长度
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/
public class SimpleClient
{
public static void main(Stringargs) throws Exception
{
Socket s1 = new Socket("localhost",8000);
System.out.println("第一次连接成功");
Socket s2 = new Socket("localhost",8000);
System.out.println("第二次连接成功");
//这里会抛出异常
//Exception in thread "main" java.net.ConnectException: Connection refused: connect
Socket s3 = new Socket("localhost",8000);
System.out.println("第三次连接成功");
}
}
package com.game.landon.socket;
import java.net.ServerSocket;
/**
*
*一个SimpleServer,用来测试服务器的连接请求队列的长度
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/
public class SimpleServer
{
public static void main(Stringargs) throws Exception
{
//设置连接请求队列的长度为2
ServerSocket serverSocket = new ServerSocket(8000,2);//ServerSocket(int port,int backlog)
Thread.sleep(6 * 60 * 1000);//sleep 6分钟
// 个人认为这个连接请求队列只有在server端将连接的socket断掉后,才会从队列移除(属个人猜测)
}
}
package com.game.landon.socket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
*
*测试BindException
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/
public class TestBindException
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket();
//直接运行程序,即抛出Exception in thread "main" java.net.BindException: Cannot assign requested address: JVM_Bind
// socket.bind(new InetSocketAddress(InetAddress.getByName("10.10.0.0"),5678));
// 抛出异常:Exception in thread "main" java.net.BindException: Address already in use: JVM_Bind
socket.bind(new InetSocketAddress("127.0.0.1", 3306));//3306为mysql所占端口
// Socket socket = new Socket("localhost",80,InetAddress.getByName("10.10.0.0"),5678);
}
}
package com.game.landon.socket;
import java.io.OutputStream;
import java.net.Socket;
/**
*
*测试SO_LINGER选项的一个client.发送100个字符(10000个的话控制台就显示不出来了)给Server.然后调用close关闭Socket
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/
public class TestLingerClient
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket("localhost",8000);
// socket.setSoLinger(true, 0);
socket.setSoLinger(true, 60);
OutputStream out = socket.getOutputStream();
StringBuilder builder = new StringBuilder();
for(int i = 0;i < 100;i++)
{
builder.append(i);
}
//1.注释掉两句setSoLinger的代码->启动Server再启动client.
//1.close方法立即返回 输出close socket cost Time:0 ms
//2.因为server执行了sleep->client已经执行了close且client程序本身也结束了->但是server依然受到了全部所有的数据.
//因为client执行了Socket#close后,底层的Socekt其实并没有真正关闭,与server的连接仍然存在.底层的Socket会存在一段时间,知道发送完所有的数据.
//2.socket.setSoLinger(true, 0)->先后启动server|client.->client执行Socket#close时会强制关闭底层Socket.->所有未发送数据丢失.->Server
//结束休眠后,读数据抛出异常->Exception in thread "main" java.net.SocketException: Connection reset
//3.socket.setSoLinger(true, 60)->先后启动server|client->client执行Socket#close会阻塞状态,直到等待了60秒.->或者底层已经将所有未发送的数据
//发送完毕,才会从close返回。
//close socket cost Time:1651 ms->server结束休眠后,因为client还在执行close并处于阻塞状态.client与server之前的连接依然存在.所以可以收到所有数据.
out.write(builder.toString().getBytes());//发送100个字符
long begin = System.currentTimeMillis();
socket.close();
long end = System.currentTimeMillis();
System.out.println("close socket cost Time:" + (end - begin) + " ms");
}
}
package com.game.landon.socket;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
*测试SO_LINGER选项的一个简单server.接收连接请求后,不立即接收client发送的数据,而是睡眠5秒再接收数据.
*等到其开始接收数据时,client可能已经执行了close方法.server还会接收到client发送的数据吗?
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/
public class TestLingerServer
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket(8000);
Socket socket = serverSocket.accept();
Thread.sleep(5000);//睡眠5秒再读输入流
InputStream in = socket.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = -1;
do
{
len = in.read(buff);
if(len != -1)
{
buffer.write(buff, 0, len);
}
}
while (len != -1);
System.out.println(new String(buffer.toByteArray()));//字节数组转为字符串
}
}
package com.game.landon.socket;
/**
*
*测试String字符串长度问题
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/
public class TestStringMax
{
public static void main(String[] args)
{
StringBuilder builder = new StringBuilder();
for(int i = 0;i < 10000;i++)
{
builder.append(i);
}
// 打印出了长度
System.out.println(builder.toString().length());
// 但是字符串却无法打印,原因是控制台设置的原因->Window->Preferences->Run/Debug->Console->Fixed with Console->Maximum Character Width
System.out.println(builder.toString());
}
}