1 套接字
针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。线缆的每一端都插入一个“套接字”或者“插座”里。
“套接字”或者“插座”(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。抽象的基本宗旨是让我们尽可能不必知道那些细节。
2 套接字类
两个基于数据流的套接字类:
① ServerSocket,服务器用它“侦听”进入的连接;,ServerSocket 的主要任务是在那里耐心地等候其他机器同它连接,再利用accept()方法返回一个实际的Socket。
② Socket,客户用它初始一次连接。
③ 创建一个ServerSocket 时,只需为其赋予一个端口编号。不必把一个IP 地址分配它,因为它已经在自己代表的那台机器上了。但在创建一个Socket 时,却必须同时赋予IP 地址以及要连接的端口编号(另一方面,从ServerSocket.accept()返回的Socket 已经包含了所有这些信息)。
3.一个简单的服务器和客户机程序
服务端程序:
package com.dason.getip;
import java.io.*;
import java.net.*;
// 服务器的全部工作就是等候建立一个连接
public class DasonServerSocket {
// Choose a port outside of the range 1-1024:
public static final int PORT = 8080;
public static void main(String[] args)throws IOException {
// 1.创建一个ServerSocket 时,只需为其赋予一个端口编号
ServerSocket s = new ServerSocket(PORT);
System.out.println("ServerSocket: " + s);
try {
// 2.ServerSocket “侦听”进入的连接,方法会暂时陷入停顿状态(堵塞),
// 直到某个客户尝试同它建立连接,通过accept()方法返回一个对应的服务器端套接字
Socket socket = s.accept();
try {
System.out.println("Connection accepted: "+ socket);
//Socket[addr=127.0.0.1,port=57466,localport=8080]
//这意味着服务器刚才已接受了来自127.0.0.1 这台机器的端口57466 的连接,同时监听自己的本地端口8080
// 3. 然后用那个连接产生的Socket 创建一个InputStream 以及一个OutputStream
BufferedReader in =new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed by PrintWriter:
/* 对输出来说,使用Writer 方式具有明显的优势。这一优势是通过PrintWriter 表现出来的,
* 它有一个过载的构建器,能通过第二个参数一个布尔值标志,
* 指向是否在每一次println()结束的时候自动刷新输出*/
PrintWriter out =new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())),true);
// 4.它从InputStream 读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止
while (true) {
String str = in.readLine();
if (str.equals("END")) {
break;
}
System.out.println("Echoing: " + str);
out.println(str);
}
// 5.最后关闭连接
} finally {
System.out.println("closing...");
socket.close();
}
} finally {
s.close();
}
}
}
客户端程序:
package com.dason.getip;
import java.io.*;
import java.net.*;
public class DasonClientSocket {
public static void main(String[] args) throws IOException {
// 1. 获得本地主机IP地址的InetAddress 的三种途径
InetAddress addr = InetAddress.getByName(null);
// InetAddress addr = InetAddress.getByName("127.0.0.1");
// InetAddress addr = InetAddress.getByName("localhost");
System.out.println("addr = " + addr);
//2. 创建客户端套接字
Socket socket = new Socket(addr, DasonServerSocket.PORT);
// Guard everything in a try-finally to make
// sure that the socket is closed:
try {
System.out.println("socket = " + socket);
//socket: Socket[addr=/127.0.0.1,port=8080,localport=57466]
//这意味着客户已用自己的本地端口57466 与127.0.0.1 机器上的端口8080 建立了 连接
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// Output is automatically flushed by PrintWriter:
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())),true);
for(int i = 0; i < 10; i ++) {
out.println("howdy " + i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
// 3.关闭连接
} finally {
System.out.println("closing...");
socket.close();
}
}
}
注意:
① 每次重新启动客户程序的时候,本地端口的编号都会增加。这个编号从1025(刚好在系统保留的1-1024 之外)开始,并会一直增加下去,除非我们重启机器。若重新启动机器,端口号仍然会从1025 开始增值(在Unix 机器中,一旦超过保留的套按字范围,数字就会再次从最小的可用数字开始)。
② 套接字建立了一个“专用”连接,它会一直持续到明确断开连接为止,这意味着参与连接的双方都被锁定在通信中,而且无论是
否有数据传递,连接都会连续处于开放状态。从表面看,这似乎是一种合理的连网方式。然而,它也为网络带来了额外的开销。后面会介绍进行连网的另一种方式。采用那种方式,连接的建立只是暂时的。
否有数据传递,连接都会连续处于开放状态。从表面看,这似乎是一种合理的连网方式。然而,它也为网络带来了额外的开销。后面会介绍进行连网的另一种方式。采用那种方式,连接的建立只是暂时的。