了解Java中的Socket编程
Socket编程是在两台计算机之间通过网络进行数据通信的一种手段。可以使用面向连接的协议或无连接协议进行连接。在我们的案例中,我们将使用TCP/IP,这是一个面向连接的协议。
在交换数据之前,计算机必须建立一个链接,这是为面向连接的协议。UDP(用户数据报协议)是无连接协议的唯一选择。
为了进一步展示套接字,我们将使用客户/服务器架构。客户端和服务器通过向套接字连接的写入和读取进行通信。
套接字的定义
套接字是一个通信端点,作为网络上两台机器之间的链接。它有一个port number ,TCP/IP层可以用它来识别接收数据的应用程序。一个端点通常包括一个port number 和一个IP address 。
什么是TCP?
传输控制协议(TCP)是一个广泛使用的网络数据传输协议,支持客户/服务器端点。
有两类套接字。
- 服务器套接字 - 它等待来自客户端的请求。
- 客户端套接字--它在客户和服务器之间建立通信。
客户端必须知道关于服务器的两件事。
- 服务器的IP地址。
- 端口号。
0到1023之间的端口主要用于管理目的(例如,21用于FTP,23用于Telnet,25用于电子邮件,80用于HTTP)。在我们的程序中,我们将使用端口号
5000。
创建一个套接字连接
在Java中,我们通过以下步骤创建一个套接字连接。
服务器构造一个ServerSocket 对象,以指定我们的对话将发生在哪个端口。每当发生I/O错误时,就会使用异常处理方法。
accept() 方法被服务器调用,以验证对套接字的传入请求。
然后,客户端通过指定server name 和port number 来创建一个Socket 对象。
Socket 类的构造函数试图使用所提供的端口号将客户机连接到服务器。
如果连接成功,客户端和服务器就可以使用I/O streams 。客户端和服务器套接字类负责I/O流的工作。
客户端的OutputStream 与服务器的InputStream 进行通信,而服务器的OutputStream 与客户端的InputStream 进行通信。
一个流基本上是一个有顺序的数据的集合。
流的两个主要类型是。
- 字符流(通常用于文本文件)。
- 字节流(用于图像)。
字符流是人类可读的语言,而字节流是机器语言。
在这种情况下,客户端和服务器将同时向对方的流广播消息,因为TCP是一个双向的通信协议。
服务器端应用程序的编程
serversocket 是一个使用以下构造函数的ServerSocket 的对象。
serversocket = new ServerSocket(int port)
端口:服务器将监听客户端请求的端口号。
ServerSocket对象保持空闲状态,等待网络请求。我们的客户端将尝试连接到端口
5000。
确保端口是相同的,否则,连接将失败。
我们将使用两个while 循环。
- 第一个循环- 这确保服务器确实在运行。
- 第二个循环--它确保服务器在连接后与客户端进行交互,直到客户端断开连接。
while (true){ //ensures server is running
try {
socket = serversocket.accept();
inputStreamReader = new InputStreamReader(socket.getInputStream());
outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
bufferedReader = new BufferedReader(inputStreamReader);
bufferedWriter = new BufferedWriter(outputStreamWriter);
while (true) { // ensures server interacts with the client
String msgFromClient = bufferedReader.readLine();
System.out.println("Client: " + msgFromClient);
bufferedWriter.write(" MSG Received");
bufferedWriter.newLine();
bufferedWriter.flush();
if (msgFromClient.equalsIgnoreCase("BYE"))
break;
}
socket.close();
inputStreamReader.close();
outputStreamWriter.close();
bufferedReader.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
我们在上面的服务器端程序中使用了以下方法。
public InputStream getInputStream()- 它返回一个接收数据的 对象。InputStreampublic OutputStream getOutputStream()- 它返回一个用于发送数据的OutputStream对象。public Socket accept()- 它等待客户端连接(在客户端连接之前程序不会继续)。当你连接时,你会得到一个套接字对象,你可以用它来与客户端通信。BufferedReader()- 它包装了inputStreamReader以提高效率。BufferedWriter()- 它包装了outputStreamReader以提高效率。BufferedWriter.flush()- 冲洗输出流。它强制任何缓冲的输出字节被过度写入。当缓冲区满的时候,flush方法被调用。void close()- 这个方法关闭服务器套接字,即停止等待客户端的请求。
下面是服务器端应用程序的全部代码。
package com. company;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
Socket socket ;
InputStreamReader inputStreamReader ;
OutputStreamWriter outputStreamWriter ;
BufferedReader bufferedReader ;
BufferedWriter bufferedWriter ;
ServerSocket serversocket ;
serversocket = new ServerSocket(5000);
while (true) {
try {
socket = serversocket.accept();
inputStreamReader = new InputStreamReader(socket.getInputStream());
outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
bufferedReader = new BufferedReader(inputStreamReader);
bufferedWriter = new BufferedWriter(outputStreamWriter);
while (true){
String msgFromClient = bufferedReader.readLine();
System.out.println("Client: " + msgFromClient);
bufferedWriter.write(" MSG Received");
bufferedWriter.newLine();
bufferedWriter.flush();
if (msgFromClient.equalsIgnoreCase("BYE"))
break;
}
socket.close();
inputStreamReader.close();
outputStreamWriter.close();
bufferedReader.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这段代码的输出样本是。
Connected
Client: Hey there
Client: I am doing this for the first time
Client: BYE
Closing connection
客户端应用程序的编程
有两种方法可以访问一个Socket 实例。
- 服务器将其作为
accept()方法的return值接收。 - 你也可以使用下面的代码来创建一个
Socket。
socket = new Socket("localhost", 5000);
在上面的代码中,一个localhost 是一个重定向到你的计算机的域名。它可以解析为127.0.0.1 ,作为IP 的地址。第二个参数中是一个端口号。
我们使用套接字对象生成I/O 流,如下图所示。
inputStreamReader = new InputStreamReader(socket.getInputStream());
outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
在客户端,我们需要使用一个Scanner 对象来获取用户输入。
Scanner scanner = new Scanner(System.in);
System.in 指明我们正在获取键盘输入。
下面是客户端应用程序的完整代码。
package com. company;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class client {
public static void main(String[] args) {
Socket socket = null;
InputStreamReader inputStreamReader = null;
OutputStreamWriter outputStreamWriter = null;
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
socket = new Socket("localhost", 5000);
inputStreamReader = new InputStreamReader(socket.getInputStream());
outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
bufferedReader = new BufferedReader(inputStreamReader);
bufferedWriter = new BufferedWriter(outputStreamWriter);
Scanner scanner = new Scanner(System.in);
while (true){
String msgToSend = scanner.nextLine();
bufferedWriter.write(msgToSend);
bufferedWriter.newLine();
bufferedWriter.flush();
System.out.println("Server: " + bufferedReader.readLine()); //printing the server message
if (msgToSend.equalsIgnoreCase("BYE"))
break;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();
if (inputStreamReader != null)
inputStreamReader.close();
if (outputStreamWriter != null)
outputStreamWriter.close();
if (bufferedReader != null)
bufferedReader.close();
if (bufferedWriter != null)
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
该代码的输出示例如下。
Hey there
Server: Message Received
I am doing this for the first time
Server: Message Received
BYE
Server: Message Received
测试应用程序
使用Intellij或其他IDE
- 编译这两个程序。
- 先运行服务器程序,然后再运行客户端程序。
- 在客户端窗口中输入信息,服务器窗口将同时接收和显示这些信息。
- 要退出,请输入BYE。
使用命令提示符/终端
- 制作一个新的文件夹,命名为
project(这是你的软件包名称)。 - 把
Server.java和Client.java放到project文件夹中。 - 打开
command prompt,并浏览到root的路径。 - 执行
javac project\Server.java,然后是java project.Server。 - 使用与
Server程序相同的程序运行client程序。 - 然后你可以在
client窗口中输入信息。
例子。
Hey there
Server: Message Received
I am doing this for the first time
Server: Message Received
BYE
Server: Message Received
如果端口已经在使用,应用程序可能会导致错误。为了解决这个问题,将端口号改为唯一的值。
总结
在本教程中,我们了解了插座和TCP/IP协议。我们具体介绍了Java中套接字编程的基本原理。
此外,我们还讨论了数据流和客户/服务器交互的工作方式。因此,你可以利用这些知识来建立其他高产的应用程序。