读书百遍,其义自现。(三国志)
它的意思是读书一百遍,其中的意义和思想就自然显露出来了。
我们知道,在 Java 提供的网络库中,提供了三种套接字类:java.net.Socket, java.net.ServerSocket,DatagramSocket。其中,Socket 和 ServerSocket 是建立在 TCP 协议的基础上,DatagramSocket 建立在 UDP 协议的基础上。
传输层向应用层提供了套接字 Socket 接口,Socket 封装了下层的数据传输细节,应用层的程序通过调用 Socket Api 来建立与远程主机连接,进行出具传输. 流程如下图所示:
① ServerSocket 构造方法负责在操作系统中把当前进程注册为服务器进程,传入要监听的端口。
② 接着调用 accept() 函数一直监听端口,等待客户的连接请求。如果收到一个连接请求,它就返回一个 Socket 对象。这个Socket 对象和客户端的 Socket 形成了一条通信链路。
③ 通过 Socket 提供的 getInputStream() 和 getOutputStream() 方法,分别返回输入流 InputStream 对象和输出流 OutputStream 对象。向输出流写数据,就能向对方发送数据。从输入流读数据,就能接收来自客户端的数据。
④ 把 Socket 的输入输出流用过滤流来装饰。用 PrintWriter 装饰输出流,调用它的 println() 能够写一行数据。用 BufferedReader 装饰输入流,调用它的 readLine() 读入一行数据。
源代码如下:
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
class TcpServer {
private static final int PORT = 8000; //端口
private ServerSocket serverSocket;
//构造函数,创建 ServerSocket 对象
public TcpServer() throws IOException {
serverSocket = new ServerSocket(PORT);
System.out.println("服务器启动啦");
}
//获取输出流
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));
}
public void start(){
//不断监听
while(true){
Socket socket = null;
try {
socket = serverSocket.accept(); //监听是否有客户端连接
//连接成功,打印连接的客户端信息
System.out.println("客户端已连接,ip = " + socket.getInetAddress() + "port = " + socket.getPort());
BufferedReader br = getReader(socket);//获取输入流
PrintWriter pr = getWriter(socket);//获取输出流
String msg = null;
//不断读取客户端发过来的信息
while((msg = br.readLine())!= null){
System.out.println(msg);
pr.println("server receive: " + msg); //发消息给客户端,告诉他收到了消息
if(msg.equals("bye")){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws IOException {
new TcpServer().start();
}
}
执行结果如下:
① ip 地址,这里为本机地址。
② port 为系统随机分配给客户端的,每次客户端连接都会重新分配端口,每次基本都是不同的。
③ 结合客户端端的执行结果来看。
① 首先,创建一个 Socket 对象,参数为主机地址和端口。此例子客户端进程和服务端进程都运行在同一个主机上,所以地址直接用“localhost”。
② 对象创建成功表示连接成功,接着从 Socket 对象获取输入输出流,另外,创建一个线程用于读取服务端发过来的数据,这样两者zh就可以进行通信发送数据了。
源代码如下所示:
import java.io.*;
import java.net.Socket;
public class TcpClient {
private static final int PORT = 8000;//服务端端口
private Socket socket;
private static final String host = "localhost";//本地地址,127.0.0.1
//构造函数
public TcpClient() throws IOException {
socket = new Socket(host, PORT);
System.out.println("开始连接服务端");
}
//获取输出流
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));
}
//开始通信
public void start() throws IOException {
try {
BufferedReader br = getReader(socket);//获取输入流
PrintWriter pw = getWriter(socket);//获取输出流
//读取我们的键盘输入
BufferedReader localReaer = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
//启动一个线程用于读取服务端消息
new Thread(new ReceiveRunnable(br)).start();
//不断读取键盘输入
while ((msg = localReaer.readLine()) != null) {
pw.println(msg);//发送给服务端
if (msg.equals("bye")) {
break;
}
}
System.out.println("end");
} finally {
socket.close();
}
}
public static void main(String[] args) throws IOException {
new TcpClient().start();
System.out.println("客户端退出");
}
class ReceiveRunnable implements Runnable{
private BufferedReader br;
public ReceiveRunnable(BufferedReader br) {
this.br = br;
}
@Override
public void run() {
String msg;
try{
//读取服务端发送过来的数据
while(null != (msg = br.readLine())){
System.out.println(msg);
}
} catch (IOException E){
} finally {
}
}
}
}
执行结果如下:
① 发送数据 456 给服务器,并接收的服务端发回来的数据 server receive:456。
② 以此类推,最后 发送 bye ,让客户端退出,结束。