java套接字编程
本教程是Java套接字编程的简介,从一个简单的客户机-服务器示例开始,该示例演示了Java I / O的基本功能。 将向您介绍原始的java.io软件包和NIO,即Java 1.4中引入的非阻塞I / O(java.nio)API。 最后,您将看到一个示例,该示例演示了在NIO.2中从Java 7向前实现的Java网络。
套接字编程可归结为两个相互通信的系统。 通常,网络通信有两种形式:传输控制协议(TCP)和用户数据报协议(UDP)。 TCP和UDP用于不同目的,并且都有独特的约束:
- TCP是相对简单且可靠的协议,它使客户端可以连接到服务器,并使两个系统进行通信。 在TCP中,每个实体都知道已收到其通信有效载荷。
- UDP是一种无连接协议 ,适用于不一定需要每个数据包到达其目的地的情况,例如媒体流。
要了解TCP和UDP之间的区别,请考虑如果您从自己喜欢的网站流式传输视频并且丢帧了会发生什么情况。 您是希望客户端放慢影片的速度来接收丢失的帧,还是希望视频继续播放? 视频流协议通常利用UDP。 因为TCP保证传送,所以它是HTTP,FTP,SMTP,POP3等的首选协议。
在本教程中,我向您介绍Java中的套接字编程。 我提供了一系列客户机/服务器示例,这些示例演示了原始Java I / O框架中的功能,然后逐步推进使用NIO.2中引入的功能。
老式Java套接字
在NIO之前的实现中,Java TCP客户端套接字代码由java.net.Socket类处理。 以下代码打开与服务器的连接:
Socket socket = new Socket( server, port );
一旦我们的socket实例连接到服务器,我们就可以开始获取到服务器的输入和输出流。 输入流用于从服务器读取数据,而输出流用于将数据写入服务器。 我们可以执行以下方法来获取输入和输出流:
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
因为这些是普通流,所以我们将使用它们从文件中读取和写入文件,因此可以将它们转换为最适合我们用例的形式。 例如,我们可以将OutputStream与PrintStream包裹在一起,以便我们可以轻松地使用诸如println()类的方法编写文本。 再举一个例子,我们可以通过InputStream用BufferedReader包装InputStreamReader ,以便使用readLine()类的方法轻松读取文本。
Java套接字客户端示例
让我们来看一个简短的示例,该示例针对HTTP服务器执行HTTP GET。 HTTP比我们的示例所允许的更为复杂,但是我们可以编写客户端代码来处理最简单的情况:从服务器请求资源,然后服务器返回响应并关闭流。 这种情况需要执行以下步骤:
- 创建一个侦听端口80的Web服务器套接字。
- 获取服务器的
PrintStream并发送GET PATH HTTP/1.0请求,其中PATH是服务器上请求的资源。 例如,如果我们要打开网站的根目录,则路径为/。 - 获取服务器的
InputStream,将其包装为BufferedReader然后逐行读取响应。
清单1显示了此示例的源代码。
清单1. SimpleSocketClientExample.java
package com.geekcap.javaworld.simplesocketclient;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class SimpleSocketClientExample
{
public static void main( String[] args )
{
if( args.length < 2 )
{
System.out.println( "Usage: SimpleSocketClientExample <server> <path>" );
System.exit( 0 );
}
String server = args[ 0 ];
String path = args[ 1 ];
System.out.println( "Loading contents of URL: " + server );
try
{
// Connect to the server
Socket socket = new Socket( server, 80 );
// Create input and output streams to read from and write to the server
PrintStream out = new PrintStream( socket.getOutputStream() );
BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
// Follow the HTTP protocol of GET <path> HTTP/1.0 followed by an empty line
out.println( "GET " + path + " HTTP/1.0" );
out.println();
// Read data from the server until we finish reading the document
String line = in.readLine();
while( line != null )
{
System.out.println( line );
line = in.readLine();
}
// Close our streams
in.close();
out.close();
socket.close();
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
清单1接受两个命令行参数:要连接的服务器(假设我们正在端口80上连接服务器)和要检索的资源。 它创建一个指向服务器的Socket ,并显式指定端口80 。 然后执行以下命令:
GET PATH HTTP/1.0
例如:
GET / HTTP/1.0
刚才发生了什么?
当您从网络服务器(例如www.google.com检索网页时,HTTP客户端会使用DNS服务器查找服务器的地址:首先从顶级域服务器询问com域的权威域,名称服务器用于www.google.com 。 然后,它要求该域名服务器提供www.google.com的IP地址。 接下来,它在端口80上打开该服务器的套接字。(或者,如果要定义其他端口,则可以通过在端口号之前添加冒号来实现,例如:8080 。)最后,HTTP客户端执行指定的HTTP方法,例如GET , POST , PUT , DELETE , HEAD或OPTI/ONS 。 每种方法都有自己的语法。 如上面的代码片段所示, GET方法需要一个路径,后跟HTTP/version number和空行。 如果要添加HTTP标头,可以在输入新行之前完成。
在清单1中,我们检索了OutputStream并将其包装在PrintStream以便我们可以更轻松地执行基于文本的命令。 我们的代码获得了InputStream ,将其包装在InputStreamReader ,然后将其转换为Reader ,然后将其包装在BufferedReader 。 我们使用PrintStream执行GET方法,然后使用BufferedReader逐行读取响应,直到收到null响应,表明套接字已关闭。
现在执行此类,并向其传递以下参数:
java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /
您应该看到类似于以下内容的输出:
Loading contents of URL: www.javaworld.com
HTTP/1.1 200 OK
Date: Sun, 21 Sep 2014 22:20:13 GMT
Server: Apache
X-Gas_TTL: 10
Cache-Control: max-age=10
X-GasHost: gas2.usw
X-Cooking-With: Gasoline-Local
X-Gasoline-Age: 8
Content-Length: 168
Last-Modified: Tue, 24 Jan 2012 00:09:09 GMT
Etag: "60001b-a8-4b73af4bf3340"
Content-Type: text/html
Vary: Accept-Encoding
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Gasoline Test Page</title>
</head>
<body>
<br><br>
<center>Success</center>
</body>
</html>
此输出显示JavaWorld网站上的测试页。 它回复说它使用HTTP版本1.1,响应为200 OK 。
Java套接字服务器示例
我们已经介绍了客户端,幸运的是服务器端的通信方面也很容易。 从简单的角度看,该过程如下:
- 创建一个
ServerSocket,指定要监听的端口。 - 调用
ServerSocket的accept()方法在配置的端口上侦听客户端连接。 - 当客户端连接到服务器时,
accept()方法将返回一个Socket,服务器可以通过该Socket与客户端进行通信。 这是我们用于客户端的Socket类,因此过程是相同的:获取要从客户端读取的InputStream和向客户端写入的OutputStream。 - 如果服务器需要可伸缩性,则需要将
Socket传递给另一个线程进行处理,以便服务器可以继续侦听其他连接。 - 再次调用
ServerSocket的accept()方法以侦听另一个连接。
正如您很快将看到的,NIO对这种情况的处理将有所不同。 不过,到目前为止,我们可以通过向其传递一个端口来直接创建ServerSocket进行侦听(在下一节中,更多关于ServerSocketFactory的信息):
ServerSocket serverSocket = new ServerSocket( port );
现在我们可以通过accept()方法接受传入的连接:
Socket socket = serverSocket.accept();
// Handle the connection ...
使用Java套接字的多线程编程
下面的清单2将到目前为止的所有服务器代码放到一个更健壮的示例中,该示例使用线程来处理多个请求。 所示服务器是回显服务器 ,表示它回显收到的任何消息。
尽管清单2中的示例并不复杂,但是它确实可以预期NIO的下一部分中将要介绍的一些内容。 要特别注意我们为了构建可以处理多个同时请求的服务器而必须编写的线程代码量。
清单2. SimpleSocketServer.java
package com.geekcap.javaworld.simplesocketclient;
import java.io.BufferedReader;
import java.io.I/OException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleSocketServer extends Thread
{
private ServerSocket serverSocket;
private int port;
private boolean running = false;
public SimpleSocketServer( int port )
{
this.port = port;
}
public void startServer()
{
try
{
serverSocket = new ServerSocket( port );
this.start();
}
catch (I/OException e)
{
e.printStackTrace();
}
}
public void stopServer()
{
running = false;
this.interrupt();
}
@Override
public void run()
{
running = true;
while( running )
{
try
{
System.out.println( "Listening for a connection" );
// Call accept() to receive the next connection
Socket socket = serverSocket.accept();
// Pass the socket to the RequestHandler thread for processing
RequestHandler requestHandler = new RequestHandler( socket );
requestHandler.start();
}
catch (I/OException e)
{
e.printStackTrace();
}
}
}
public static void main( String[] args )
{
if( args.length == 0 )
{
System.out.println( "Usage: SimpleSocketServer <port>" );
System.exit( 0 );
}
int port = Integer.parseInt( args[ 0 ] );
System.out.println( "Start server on port: " + port );
SimpleSocketServer server = new SimpleSocketServer( port );
server.startServer();
// Automatically shutdown in 1 minute
try
{
Thread.sleep( 60000 );
}
catch( Exception e )
{
e.printStackTrace();
}
server.stopServer();
}
}
class RequestHandler extends Thread
{
private Socket socket;
RequestHandler( Socket socket )
{
this.socket = socket;
}
@Override
public void run()
{
try
{
System.out.println( "Received a connection" );
// Get input and output streams
BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
PrintWriter out = new PrintWriter( socket.getOutputStream() );
// Write out our header to the client
out.println( "Echo Server 1.0" );
out.flush();
// Echo lines back to the client until the client closes the connection or we receive an empty line
String line = in.readLine();
while( line != null && line.length() > 0 )
{
out.println( "Echo: " + line );
out.flush();
line = in.readLine();
}
// Close our connection
in.close();
out.close();
socket.close();
System.out.println( "Connection closed" );
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
翻译自: https://www.infoworld.com/article/2853780/socket-programming-for-scalable-systems.html
java套接字编程
Java套接字编程实战:客户端与服务器示例
本文是一篇关于Java套接字编程的教程,通过客户端和服务器的示例,介绍了如何使用老式Java套接字进行TCP通信。文章详细解释了TCP与UDP的区别,并提供了简单的HTTP GET请求的客户端示例,以及多线程服务器的实现,展示了如何处理多个并发连接。
930

被折叠的 条评论
为什么被折叠?



