目录
第一部分:使用Socket
一.数据报
二.socket的功能
第二部分:了解SOCKET类
1.socket类
2.构造方法
3.常用方法介绍
4.DEMO:客户端和服务端通信
第三部分:用Telnet研究协议
一、了解Telnet
二、用Socket从服务器读取
1.在终端上用Telnet测试daytime服务器
2.小DEMO:Daytime协议客户端
3.小DEMO:通过与time.nist.gov对话构造一个Date
4.小DEMO:时间协议客户端
三、用Socket写入服务器
1.了解用socket如何写入服务器
2.用java程序来显示一个完整的dict客户端
四、半关闭Socket
1.shutdownInput()和shutdownOutput()方法
2.isInputShutdown()和isOutputShutdown()方法
第四部分:构造和连接Socket
一、基本构造函数
二、选择从哪个本地接口连接
三、构造但是不连接
四、Socket地址
五、代理服务器
六、获取Socket的信息
1.Socket对象的一些属性
2.获取方法
3.远程端口
4.本地端口
5.demo:获得Socket的信息
七、关闭还是连接
八、toString()
第五部分:设置Socket选项
1.TCP_NODELY选项
2.SO_RESUSEADDR选项
设置该选项:public void setResuseAddress(boolean on)throws SocketException读取该选项 : public void getResuseAddress(boolean on)throws SocketException
3.SO_TIMEOUT选项
4.SO_LINGER选项
第六部分:Socket异常
第一部分:使用Socket
一.数据报
数据按照有限大小的包传输,这些包称为数据报。
每个数据报包含一个首部和一个有效载荷。
首部包含包发送到的地址和端口、包来自的地址和端口、检测数据是否被破坏的校验和,以及用于保证可靠传输的各种其他管理信息;
有效载荷包含数据本身。
由于数据报文长度是有限的,通常必须将数据分解为多个包,再在目的的重新组合。
也有可能一个包或者多个包在传输中丢失或者遭到破坏,需要重新传;
或者包乱序到达,需要重新排序。所有这些(将数据分解为包、生成首部、解析入站包的首部、跟踪哪些包已经收到而哪些没有收到and so on),是不是看起来都很麻烦,但是你不需要完成这些任务,前面的内容就是让大家了解一下哦。
Socket对程序员掩盖了网络的底层细节,就比如:错误检测、包大小、包分解、包重传、网络地址...
二.socket的功能
Socket是两台主机之间的一个连接,可以完成以下7个基本操作:
1.连接远程机器
2.发送数据
3.接收数据
4.关闭连接
5.绑定端口
6.监听入站数据
7.在绑定端口上接受来自远程机器的连接
Java程序采用以下方式使用客户端socket
* 程序用构造函数创建一个新的socket
*Socket尝试连接远程机器
详细了解一下:
一旦建立了连接,本地和远程机就从这个socket得到输入流和输出流,使用这两个流相互发送数据;
连接是全双工的,两台主机都可以同时发送和接受数据。数据的含义取决与协议,发送给FTP服务器的命令与发送给HTTP服务器的命令就有所不同。一般会先完成某种协商握手,然后再具体传输数据
第二部分:了解SOCKET类
1.socket类
Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
2.构造方法
public Socket(String host, int port)
eg:
Socket clients = new Socket("127.0.0.1",2628);
创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
*~*回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
3.常用方法介绍
public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。
一旦一个socket被关闭,它不可再使用。
关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。
任何先前写出的数据将被发送,随后终止输出流。
4.DEMO:客户端和服务端通信
1.两端通信时步骤:
服务端程序,需要事先启动,等待客户端的连接。
客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
2.在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
【服务端我会在下一篇博客中详细讲到,大家可以先了解一下下哦】
public Socket(String host, int port)
eg:
Socket clients = new Socket("127.0.0.1",2628);
创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
*~*回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。一旦一个socket被关闭,它不可再使用。
关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。任何先前写出的数据将被发送,随后终止输出流。
4.DEMO:客户端和服务端通信
1.两端通信时步骤:
服务端程序,需要事先启动,等待客户端的连接。
客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
2.在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
【服务端我会在下一篇博客中详细讲到,大家可以先了解一下下哦】
1.两端通信时步骤:
服务端程序,需要事先启动,等待客户端的连接。
客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
2.在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
【服务端我会在下一篇博客中详细讲到,大家可以先了解一下下哦】
客户端:
package TEMP.Temp2;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args){
//socket对象初始化
Socket socket = null;
//输出流 os对象初始化
OutputStream os = null;
try {
//1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,2022);//inet是服务端ip
//2、获取一个输出流,用于写出要发送的数据
os = socket.getOutputStream();
//3、写出数据
os.write("你好,我是客户端小杨同学!".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4、释放资源,别忘了哦!!!!
if(socket!=null){
try {
socket.close();//关闭
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();//关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端:
package TEMP.Temp2;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1、创建服务端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(2022);
//2、调用accept接收到来自于客户端的socket
socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入,接入了之后才会显示消息
//3、获取socket的输入流
is = socket.getInputStream();
//4、读取输入流中的数据
//ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
baos = new ByteArrayOutputStream();
int len=0;
byte[] buffer = new byte[1024];
//判断是否将客户端发的消息读完了
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println("收到了来自于客户端"+
socket.getInetAddress().getHostName()
+"的消息:"+baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {//5、关闭资源
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
第三部分:用Telnet研究协议
一、了解Telnet
1.Telnet是一个简单的远程终端协议。用户用Telnet就可种子其所在地通过TCP连接注册(即登录)到远地的另一个主机上(使用主机名或IP地址)。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。
2.Telnet客户进程和服务器进程一般只属于用户应用程序,终端用户通过键盘输入的数据送给操作系统内核的终端驱动进程,由终端驱动进程把用户的输入送到Telnet客户进程,Telnet客户进程把收到的数据传送给TCP,由TCP负责在客户端和服务器端建立TCP连接,数据就通过TCP连接送到了服务器端,服务器的TCP层将收到的数据送到相应的应用层Telnet服务器进程。
二、用Socket从服务器读取
1.在终端上用Telnet测试daytime服务器
2.小DEMO:Daytime协议客户端
package BOOK;
//Daytime协议客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class DaytimeClient {
public static void main(String[] args) {
String hostname = args.length>0?args[0] : "time.nist.gov";
Socket socket = null;
try{
//在端口13打开与time.nist.gov的连接
socket = new Socket(hostname,13);
//setSoTimeout()方法为连接设置一个超时时间,(单位:毫秒)
socket.setSoTimeout(15000);
//一旦打开socket并设置其超时时间,
// 可以调用getInputStream()来返回一个InputStream,用它从socket读取字节
InputStream in = socket.getInputStream();
//把字节存储在一个StringBuilder中
StringBuilder time =new StringBuilder();
//一般情况下,服务器可以发送任何字节
//但是呢,在特定的例子中,协议指定发送的字节必须是ASCII
InputStreamReader reader = new InputStreamReader(in,"ASCII");
//使用for确定将字节读完
for(int c=reader.read();c!=-1;c=reader.read()){
time.append((char) c);
}
System.out.println(time);
}catch(IOException ex){
System.err.println(ex);
}finally{
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//输出
//59773 22-07-13 02:17:17 50 0 0 720.0 UTC(NIST) *
3.小DEMO:通过与time.nist.gov对话构造一个Date
package BOOK.DEMO;
//通过与time.nist.gov对话构建一个Date
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Daytime {
public Date getDateFromNetwork() throws IOException, ParseException {
try(Socket socket = new Socket("time.nist.gov",13)){
socket.setSoTimeout(15000);
InputStream in = socket.getInputStream();
StringBuilder time = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in,"ASCII");
for(int c=reader.read(); c!=-1;c=reader.read()){
time.append((char) c);
}
return parseDate(time.toString());
}
}
static Date parseDate(String s) throws ParseException{
String[] pieces =s.split(" ");
String dateTime = pieces[1] + " " +pieces[2] + " UTC";
DateFormat format = new SimpleDateFormat("yy-MM-dd hh:mm:ss z");
return format.parse(dateTime);
}
}
注意点:从网络读取数据的时候,并不是所有协议都使用ASCII,甚至不一定使用文本。
4.小DEMO:时间协议客户端
package BOOK.DEMO;
//时间协议客户端
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.Date;
public class Time {
private static final String HOSTNAME = "time.nist.gov";
public static void main(String[] args) {
Date d = null;
try {
d = Time.getDateFromNetwork();
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("It is " + d);
}
public static Date getDateFromNetwork() throws IOException,ParseException{
long differenceBetweenEpochs = 2208988800l;
Socket socket = null;
try{
socket = new Socket(HOSTNAME,37);
socket.setSoTimeout(15000);
InputStream raw =socket.getInputStream();
long secondSince1900 =0;
for(int i=0;i<4;i++){
secondSince1900 =(secondSince1900 << 8) | raw.read();
}
long secondSince1970 =secondSince1900 - differenceBetweenEpochs;
long msSince1970 =secondSince1970*1000;
Date time = new Date(msSince1970);
return time;
}finally{
try{
if(socket!=null)
socket.close();
}catch(IOException ex){
}
}
}
}
//输出:
// It is Wed Jul 13 11:53:25 CST 2022
三、用Socket写入服务器
1.了解用socket如何写入服务器
package BOOK.DEMO;
//通过与time.nist.gov对话构建一个Date
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Daytime {
public Date getDateFromNetwork() throws IOException, ParseException {
try(Socket socket = new Socket("time.nist.gov",13)){
socket.setSoTimeout(15000);
InputStream in = socket.getInputStream();
StringBuilder time = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in,"ASCII");
for(int c=reader.read(); c!=-1;c=reader.read()){
time.append((char) c);
}
return parseDate(time.toString());
}
}
static Date parseDate(String s) throws ParseException{
String[] pieces =s.split(" ");
String dateTime = pieces[1] + " " +pieces[2] + " UTC";
DateFormat format = new SimpleDateFormat("yy-MM-dd hh:mm:ss z");
return format.parse(dateTime);
}
}
注意点:从网络读取数据的时候,并不是所有协议都使用ASCII,甚至不一定使用文本。
4.小DEMO:时间协议客户端
package BOOK.DEMO;
//时间协议客户端
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.Date;
public class Time {
private static final String HOSTNAME = "time.nist.gov";
public static void main(String[] args) {
Date d = null;
try {
d = Time.getDateFromNetwork();
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("It is " + d);
}
public static Date getDateFromNetwork() throws IOException,ParseException{
long differenceBetweenEpochs = 2208988800l;
Socket socket = null;
try{
socket = new Socket(HOSTNAME,37);
socket.setSoTimeout(15000);
InputStream raw =socket.getInputStream();
long secondSince1900 =0;
for(int i=0;i<4;i++){
secondSince1900 =(secondSince1900 << 8) | raw.read();
}
long secondSince1970 =secondSince1900 - differenceBetweenEpochs;
long msSince1970 =secondSince1970*1000;
Date time = new Date(msSince1970);
return time;
}finally{
try{
if(socket!=null)
socket.close();
}catch(IOException ex){
}
}
}
}
//输出:
// It is Wed Jul 13 11:53:25 CST 2022
三、用Socket写入服务器
1.了解用socket如何写入服务器
1.了解用socket如何写入服务器
写入服务器并不比读取服务器更困难。
只需要向Socket请求一个输出流以及一个输入流。使用输出流在socket上发送请求数据的时候,同时还可以使用输入流读取数据。但是呢,大多数协议都设计为客户端只读取socket或者只写入socket,而不是二者同时进行。
【最常见的模式】
客户端发送一个请求,然后服务器相应。客户端可能发送另一个请求,服务器再作出响应。这个过程会一直继续,直到客户端或者服务器完成了工作,然后关闭连接。
下面我们来康康具体的实例
dict是一个简单的双向TCP。在这个协议中,客户端向dict服务器的2628端口打开一个socket,并且发送命令。
2.用java程序来显示一个完整的dict客户端
package BOOK.DEMO;
import java.io.*;
import java.net.Socket;
public class DictClient {
public static final String SERVER ="dict.org";
public static final int PORT =2628;
public static int TIMEOUT =15000;
public static void main(String[] args) {
Socket socket =null;
try {
//先向一个dict服务器的端口2628打开一个Socket
socket = new Socket(SERVER,PORT);
//为了防止连接服务器是服务器挂起,设置一个超时时间
socket.setSoTimeout(TIMEOUT);
//getOutputStream()方法返回一个原始的OutputStream
OutputStream out = socket.getOutputStream();
//包装在一个Writer中
Writer writer =new OutputStreamWriter(out,"UTF-8");
writer =new BufferedWriter(writer);
//用socket的输入流来读取信息
InputStream in =socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"UTF-8"));
for(String word :args){
define(word,writer,reader);
}
//通过socket写入命令
writer.write("quitrn");
//刷新输出,确保命令会通过网络发送
writer.flush();
} catch (IOException ex) {
System.out.println(ex);
}finally{
if(socket!=null){
try {
socket.close();
} catch (IOException ex) {
}
}
}
}
static void define(String word,Writer writer,BufferedReader reader)
throws IOException,UnsupportedEncodingException{
writer.write("DEFINE eng-lat " + word + "rn");
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
1.shutdownInput()和shutdownOutput()方法
1.shutdownInput()和shutdownOutput()方法
close()方法同时关闭Socket的输入和输出;
有的时候你可能希望仅仅关闭连接的一半,关闭输入或者输出;shutdownInput()和shutdownOutput()方法可以只关闭连接的一半。
public void shutdownInput( ) throws IOException
public void shutdownOutput( ) throws IOException
小小解析:
这两个方法并不关闭Socket,实际上,它会调整与Socket连接的流,使它认为已经到了流的末尾;
*关闭输入之后再读取输入流会返回-1;
*关闭输出之后再写入socket会抛出一个IOException异常
注意哦!!!
即使半关闭了连接,或者将连接的两半都关闭,使用结束后依然需要关闭该socket;
shutdown方法只影响Socket的流,并不是方法与socket关联的资源,比如:占用的端口。
2.isInputShutdown()和isOutputShutdown()方法
isInputShutdown()和isOutputShutdown()方法分别指出输入流和输出流是打开还是关闭的
public boolean isInputShutdown()
public boolean isOnputShutdown()
第四部分:构造和连接Socket
java.netSocket类是Java完成客户端TCP操作的基础类。
其他建立TCP网络连接的面向客户端的类(比如:URL、URLConnection、Applet和JEditorPane)最终都会调用这个类的方法。这个类本身使用原生代码与主机操作系统的本地TCP栈进行通信。
一、基本构造函数
java.netSocket类是Java完成客户端TCP操作的基础类。
其他建立TCP网络连接的面向客户端的类(比如:URL、URLConnection、Applet和JEditorPane)最终都会调用这个类的方法。这个类本身使用原生代码与主机操作系统的本地TCP栈进行通信。
每个Socket构造函数指定要连接的主机和端口。
主机可以指定InetAddress或者String;
远程端口指定为1到65535之间的int值
public Socket(String host,int port) throws UnknownHostException,IOException
为创建一个流套接字并将其连接到指定主机上的指定端口号
【String host:表示主机名,int port :端口号】
public Socket(InetAddress host,int port) throws IOException
为创建一个流套接字并将其连接到指定IP地址的指定端口号
这两个构造函数会连接socket(也就是说,在构造函数返回之前,会与远程主机建立一个活动的网络连接);
如果由于某种原因不能打开连接,构造函数会抛出一个IOException或者UnknownHostException异常。
小DEMO:查看指定主机上前1024个端口中哪些安装的有TCP服务器
package BOOK.DEMO;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class LowPortScanner {
public static void main(String[] args) {
String host = args.length >0 ? args[0] : "localhost";
for(int i=1;i<1024;i++){
try{
Socket s = new Socket(host,i);
System.out.println("There is a server on port " + i + " of "+host);
s.close();
}catch(UnknownHostException ex){
System.err.println(ex);
}catch(IOException ex){
//当这个端口不是一个服务器的时候
}
}
}
}
//输出:There is a server on port 631 of localhost
二、选择从哪个本地接口连接
有两个构造函数可以指定要连接的主机和端口,以及从哪个接口和端口连接:
public Socket(String host,int port,InetAddress interface,int localPort)
throws IOException, UnknownHostException
创建一个套接字并将其连接到指定远程主机上的指定远程端口
参数解析:
String host:指定的主机( 远程主机的名称,或环回地址的 null )
int port:指定的端口号(远程端口)
InetAddress interface:套接字所绑定的本地地址,或 null为 anyLocal地址。
int localPort:套接字绑定的本地端口,或 zero用于系统选择的自由端口
public Socket(InetAddress host,int port,InetAddress interface,int localPort)
throws IOException
创建一个套接字并将其连接到指定远程地址上的指定远程端口
三、构造但是不连接
public socket()
如果没有为Socket构造函数提供任何参数,它就没有目标主机可以连接。
可以以后再为某个connect()方法传入一个SocketAddress来建立连接
eg:
还可以传入一个int作为第二个参数,来指定连接超时之前等待的时间(毫秒数)
public void connect(SocketAddress endpoint,int timeout) throws IOException
默认值0表示永远等待下去
public socket()
如果没有为Socket构造函数提供任何参数,它就没有目标主机可以连接。
可以以后再为某个connect()方法传入一个SocketAddress来建立连接
eg:
还可以传入一个int作为第二个参数,来指定连接超时之前等待的时间(毫秒数)
public void connect(SocketAddress endpoint,int timeout) throws IOException
默认值0表示永远等待下去
你有没有思考一下下,为什么要有这个构造函数呢??!
有这个构造函数其实是为了支持不同类型的socket;
还有一个好处是:可以在try-catch-finally块中清理代码
明显可以看到:finally块中关闭一个Socket 的时候可以避免烦人的null检查哈哈哈哈。
四、Socket地址
SocketAddress类表示一个连接端点。这是一个空的抽象类,除了一个默认构造函数之外没有其他方法。
SocketAddress类的主要用途是为了暂时的socket连接信息(比如IP地址和端口)提供一个方便的存储,即使最初的socket已断开并且被垃圾回收了,这些信息也可以重用来创建新的Socket.
Socket提供了两个返回SocketAddress对象的方法
public SocketAddress getRemoteSocketAddress()
返回所连接系统的地址
public SocketAddress getLocalSocketAddress()
返回发起连接的地址
五、代理服务器
前面提到的最后一个构造函数创建一个未连接的Socket,它通过一个指定的代理服务器连接:
public Socket(Proxy proxy)
一般情况下,Socket使用的代理服务器由socksProxyHost和socksProxyPort系统属性控制,这些属性应用于系统中的所有Socket。
六、获取Socket的信息
1.Socket对象的一些属性
Socket对象有一些属性可以通过获取方法来访问:
1.远程地址
2.远程端口
3.本地地址
4.本地端口
2.获取方法
1. public InetAddress getInetAddress()
返回套接字所连接的地址。
2. public int getPort()
返回此套接字连接到的远程端口号。
3. public InetAddress getLocalAddress()
获取套接字所绑定的本地地址。
4. public int getLocalPort()
返回此套接字绑定到的本地端口号。
3.远程端口
1. public InetAddress getInetAddress()
返回套接字所连接的地址。
2. public int getPort()
返回此套接字连接到的远程端口号。
3. public InetAddress getLocalAddress()
获取套接字所绑定的本地地址。
4. public int getLocalPort()
返回此套接字绑定到的本地端口号。
对于客户端Socket来说,通常是由一个标准委员会预先分配的“已知端口”。
4.本地端口
通常是由系统在运行时从未使用的空闲端口中选择。
5.demo:获得Socket的信息
package BOOK.DEMO;
//获取SOCKET信息
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SocketInfo {
public static void main(String[] args) {
for(String host :args){
try{
Socket theSocket = new Socket(host,80);
System.out.println("Connected to" + theSocket.getInetAddress()
+ " on port " + theSocket.getPort() + " from port "
+ theSocket.getLocalPort() + " of "
+ theSocket.getLocalAddress());
}catch (UnknownHostException ex){
System.err.println("I can't find " + host);
}catch(SocketException ex){
System.err.println("Could not connect to " + host);
}catch(IOException ex){
System.err.println(ex);
}
}
}
}
七、关闭还是连接
如果socket关闭,isClose()方法会返回true,否则返回false。
有的时候你不确定一个Socket的状态,可以用这个方法来检查,千万别冒着抛出IOException异常的风险。
but!!!! 这个方法也有缺陷,如果Socket从一开始就没有连接,isClosed()也返回false,尽管Socket实际上根本没有打开过。
Socket类还有一个isConnected()方法,它会指出Socket是否从未连接过一个远程主机。
八、toString()
Socket类只覆盖了java.lang.Object中的一个标准方法:toString().
第五部分:设置Socket选项
Socket有以下几个选项:
1. TCP_NODELY:表示立即发送数据
2. SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址
3. SO_TIMEOUT:表示接收数据时的等待超时时间
4. SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket
5. SO_SNFBUF:表示发送数据的缓冲区大小
6. SO_RCVBUF:表示接受数据的缓冲区大小
7. SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动将它关闭
8. OOBINLINE:表示是否发送一个字节的TCP紧急数据
1.TCP_NODELY选项
Socket有以下几个选项:
1. TCP_NODELY:表示立即发送数据
2. SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址
3. SO_TIMEOUT:表示接收数据时的等待超时时间
4. SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket
5. SO_SNFBUF:表示发送数据的缓冲区大小
6. SO_RCVBUF:表示接受数据的缓冲区大小
7. SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动将它关闭
8. OOBINLINE:表示是否发送一个字节的TCP紧急数据
设置该选项:public void setTcpNoDelay(boolean on)throws SocketException
读取该选项:public boolean getTcpNoDelay()throws SocketException
默认情况下,发送数数据采用Negale算法。Negale算法是指发送方发送 数据不会立刻发出,而是先放在缓冲区内,等缓冲区满了再发出。发送完一批数据后,会等待接收方对这批数据的回应,然后在发送下一批数据。Negale算法适用于发送方需要发送大批量数据,并且接受方会及时作出反应的场合,这种算法通过减少数据的通信的次数来提高通信效率。
如果发送方持续的发送小批量数据,并且接受方不一定会立即发送响应数据,那么Negale算法会使发送方运行很慢。对于GUI程序,如网络游戏程序(服务器需要实时跟踪客户端鼠标的移动),这个问题尤为突出。客户端鼠标位置的改动的信息需要实时发送到服务器上,由于Negale算法采用缓冲,大大减低了实时反应速度,导致客户端运行很慢。
TCP_NODELY的默认值为false,表示采用Negale算法。如果调用setTcpNoDelay(true)方法,就会关闭Socket的缓冲,确保数据及时发送:【eg】
if(!socket.getResuseAddress())
socket.setResuseAddress(true);
2.SO_RESUSEADDR选项
设置该选项:public void setResuseAddress(boolean on)throws SocketException
读取该选项 : public void getResuseAddress(boolean on)throws SocketException
当接受方通过Socket的close()方法关闭Socket时,如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立刻释放本地端口,而是会等待一段时间,确保收到了网络上发送过来的延迟数据,然再释放该端口。Socket接受到延迟数据后,不会对这些数据做任何处理。Socket接受延迟数据的目的是,确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到。
客户程序一般采用随机端口,因此会出现两个客户端程序绑定到同样端口的可能性不大。许多服务器都使用固定的端口。当服务器进程关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一主机上重启服务器程序,由于端口已经被占用,使得服务器无法绑定到该端口,启动失败。为了确保一个进程被关闭后,及时它还没有释放该端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用Socket的setResuseAddress(true)方法:
if(!socket.getResuseAddress())
socket.setResuseAddress(true);
值得注意的是:socket.setResuseAddress(true)方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行socket.setResuseAddress(true)方法无效
所以呢必须按照以下方法创建Socket对象,然后在连接远程服务器:
Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器
socket.setReuseAddress(true);
SocketAddress socketAddress=new InetSocketAddress("remotehost",2022);
socket.connect(socketAddress);
//或者
Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器
socket.setReuseAddress(true);
socketAddress localAddr=new InetSocketAddress("localhost",2021);
socketAddress remoteAddr=new InetSocketAddress("remotehost",2022);
socket.bind(localAddr);//与本地端口绑定
socket.connect(remoteAddr);//连接远程服务器
此外,两个共用同一个端口的进程必须都调用socket.setReuseAddress(true)放方法才能使得一个进程关闭Socket后,另一个进程的Socket能够立刻重用相同的端口。
3.SO_TIMEOUT选项
读取该选项 : public void getResuseAddress(boolean on)throws SocketException
当接受方通过Socket的close()方法关闭Socket时,如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立刻释放本地端口,而是会等待一段时间,确保收到了网络上发送过来的延迟数据,然再释放该端口。Socket接受到延迟数据后,不会对这些数据做任何处理。Socket接受延迟数据的目的是,确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到。
客户程序一般采用随机端口,因此会出现两个客户端程序绑定到同样端口的可能性不大。许多服务器都使用固定的端口。当服务器进程关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一主机上重启服务器程序,由于端口已经被占用,使得服务器无法绑定到该端口,启动失败。为了确保一个进程被关闭后,及时它还没有释放该端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用Socket的setResuseAddress(true)方法:
if(!socket.getResuseAddress())
socket.setResuseAddress(true);
值得注意的是:socket.setResuseAddress(true)方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行socket.setResuseAddress(true)方法无效
所以呢必须按照以下方法创建Socket对象,然后在连接远程服务器:
Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器 socket.setReuseAddress(true); SocketAddress socketAddress=new InetSocketAddress("remotehost",2022); socket.connect(socketAddress); //或者 Socket socket=new Socket();//此时socket端口未绑定本地端口,并且未连接远程服务器 socket.setReuseAddress(true); socketAddress localAddr=new InetSocketAddress("localhost",2021); socketAddress remoteAddr=new InetSocketAddress("remotehost",2022); socket.bind(localAddr);//与本地端口绑定 socket.connect(remoteAddr);//连接远程服务器此外,两个共用同一个端口的进程必须都调用socket.setReuseAddress(true)放方法才能使得一个进程关闭Socket后,另一个进程的Socket能够立刻重用相同的端口。
3.SO_TIMEOUT选项
设置该选项:public void setSoTimeout(int milliseconds)throws SocketException
读取该选项:public void getSoTimeout(int milliseconds)throws SocketException
当通过Socket流读取数据时,如果还没有数据,就会等待。
例如,在以下代码中,in.read(buf)方法从输入流中读入1024个字节:
byte[] luck=new byte[1024]; InputStream in=new socket.getInputStream(); in.read(luck);如果输入流中还没有数据,in.read(buf)方法就会等待发送方发送数据,直到满足以下情况才停止等待:
1>. 输入流中有10个字节,read()方法把这些字节读入到buff中,再返回读取的字节数;
2>.当已经接近输入流的末尾,举例末尾还有小于1024个字节时,read()方法会把这些字节读入到buff中,在返回读物的字节数;
3>.已读到输入流的末尾,返回-1;
4>. 连接已断开,抛出IOException;
5>.如果通过Socket的setTimeout()方法设置了等待超时时间,单位为毫秒,它的默认值为0,表示会无限等待,永远不会超时。下面的代码把接受数据的等待时间设为5分钟;
if(socket.getTimeout()==0) socket.setTimeout(60000*5);
4.SO_LINGER选项
设置该选项:public void setSoLinger(boolean on,int seconds)throws SocketException
读取该选项:public void getSoLinger(boolean on,int seconds)throws SocketException



