Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以较为方便的编写网络上数据的传递。在Java中,有专门的Socket类来处理用户的请求和响应。利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。
在Java中Socket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有两个关键的方法,一个是getInputStream方法,另一个是getOutputStream方法。getInputStream方法可以得到一个输入流,客户端的Socket对象上的getInputStream方法得到的输入流其实就是从服务器端发回的数据流。GetOutputStream方法得到一个输出流,客户端Socket对象上的getOutputStream方法返回的输出流就是将要发送到服务器端的数据流,(其实是一个缓冲区,暂时存储将要发送过去的数据)。
程序可以对这些数据流根据需要进行进一步的封装。本文的例子就对这些数据流进行了一定的封装(关于封装可以参考Java中流的实现部分)。
为了更好的说明问题,这里举了一个网上对话的例子,客户端启动以后,服务器会启动一个线程来与客户进行文字交流。
要完成这个工作,需要完成三个部分的工作,以下依次说明:
一、建立服务器类
Java中有一个专门用来建立Socket服务器的类,名叫ServerSocket,可以用服务器需要使用的端口号作为参数来创建服务器对象。
ServerSocket server = new ServerSocket(9998)
这条语句创建了一个服务器对象,这个服务器使用9998号端口。当一个客户端程序建立一个Socket连接,所连接的端口号为9998时,服务器对象server便响应这个连接,并且server.accept()方法会创建一个Socket对象。服务器端便可以利用这个Socket对象与客户进行通讯。
Socket incoming = server.accept()
进而得到输入流和输出流,并进行封装
BufferedReader in = new BufferedReader(new
InputStreamReader(incoming.getInputStream()));
PrintWriter out = new PrintWriter(incoming.getOutputStream(),true);
随后,就可以使用in.readLine()方法得到客户端的输入,也可以使用out.println()方法向客户端发送数据。从而可以根据程序的需要对客户端的不同请求进行回应。
在所有通讯结束以后应该关闭这两个数据流,关闭的顺序是先关闭输出流,再关闭输入流,即使用
out.close();
in.close();
二、建立客户端代码
相比服务器端,客户端要简单一些,客户端只需用服务器所在机器的ip以及服务器的端口作为参数创建一个Socket对象。得到这个对象后,就可以用"建立服务器"部分介绍的方法实现数据的输入和输出。
Socket socket = new Socket("168.160.12.42",9998);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
以上的程序代码建立了一个Socket对象,这个对象连接到ip地址为168.160.12.42的主机上、端口为9998的服务器对象。并且建立了输入流和输出流,分别对应服务器的输出和客户端的写入。
三、建立用户界面
读者可以根据自己的喜好建立自己的用户界面,这不是本文的重点。
经过以上三个步骤,就可以建立一个比较简单的对话程序。但是,为了使这个程序更加完善,应进行以下几个改进:
一、现在服务器只能服务一个客户,也就是单线程的。可以将它改进为多线程服务器。
try
{ file://建立服务器
ServerSocket server = new ServerSocket(9998);
int i=1;
for(;;)
{
Socket incoming = server.accept();
new ServerThread(incoming,i).start();
i++;
}
}catch (IOException ex){ ex.printStackTrace(); }
循环检测是否有客户连接到服务器上,如果有,则创建一个线程来服务这个客户,这个线程的名称是ServerThread,这个类扩展了Thread类,它的编写方法与前述的服务器的写法相同。
二、为了可以随时得到对方传送过来的消息,可以在服务器以及客户端各建立一个的线程来察看输入流,如果输入流中有输入,则可以即时显示出来。代码如下:
new Thread()
{
public void run()
{
try
{
while(true)
{
checkInput();
sleep(1000);//每1000毫秒检测一次
}
}catch (InterruptedException ex)
{
}catch(IOException ex)
{
}
}
}.start();
其中的checkInput()方法为
private void checkInput() throws IOException
{
String line;
if((line=in.readLine())!=null) file://检测输入流中是否有新的数据
t.setPartner(line); file://将数据流中的消息显示出来
}
通过以上改进,程序就可以比较好的运行了。
附:服务器的实现代码
import java.net.*;
import java.io.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class talkServer
{ public static void main(String[] args)
{ try
{ file://建立服务器
ServerSocket server = new ServerSocket(9998);
int i=1;
for(;;)
{ Socket incoming = server.accept();
new ServerThread(incoming,i).start();
i++;
}
}catch (IOException ex){
ex.printStackTrace();
}
}
}
class ServerThread extends Thread implements ActionListener
{
private int threadNum;
private Socket socket;
talkServerFrm t;
BufferedReader in;
PrintWriter out;
private boolean talking=true;
public ServerThread(Socket s,int c)
{ threadNum = c;
socket = s;
}
public void actionPerformed(ActionEvent e)
{ Object source = e.getSource();
try{
if(source==t.btnSend)
{ out.println(t.getTalk());
t.clearTalk();
}else
if(source==t.btnEnd)
{ out.println("谈话过程被对方终止");
out.close();
in.close();
talking = false;
}
}catch(IOException ex){
}
}
public void run()
{ try{
t=new talkServerFrm(new Integer(threadNum).toString(),this);
t.setSize(500,500);
t.show();
in = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
}catch(Exception e){
}
new Thread()
{ public void run()
{ try{
while(true)
{ checkInput();
sleep(1000);
}
}catch (InterruptedException ex){
}catch(IOException ex){
}
}
}.start();
while(talking)
{ }
t.dispose();
}
private void checkInput() throws IOException
{ String line;
if((line=in.readLine())!=null)
t.setPartner(line); file://这是界面类里的方法,
file://用来将line的内容输出到用户界面
}
}
浅谈Socket编程及Java实现
Java是一种可用于进行网络编程的语言,它提供了两种功能强大的网络支持机制:URL访问网络资源的类和用Socket通讯的类,来满足不同的要求。一是URL用于访问Internet网上资源的应用;另一种是针对client/server(客户端/服务器)模式的应用以及实现某些特殊的协议的应用,它的通讯过程是基于TCP/IP协议中传输层接口socket实现的。本文想简单的介绍一下Socket编程的Java实现方法。
客户基于服务器之间使用的大部分通讯组件都是基于socket接口来实现的。Socket是两个程序之间进行双向数据传输的网络通讯端点,有一个地址和一个端口号来标识。每个服务程序在提供服务时都要在一个端口进行,而想使用该服务的客户机也必须连接该端口。Socket因为是基于传输层,所以它是比较原始的通讯协议机制。通过Socket的数据表现形式为字节流信息,因此通讯双方要想完成某项具体的应用则必须按双方约定的方式进行数据的格式化和解释,我们可以看出使用Socket编程比较麻烦,但是它具有更强的灵活性和更广泛的使用领域。
有些朋友会问,客户机/服务器工作的模式到底是什么样的呢?好,下面我想结合一张图来介绍一下它们的工作模式。
那么Java应用程序是如何实现上述过程的呢?java.net包中有两个类Socket和ServerSocket,分别用于在客户机和服务器上创建Socket通讯。
让我们先来看看客户段程序编写的流程:
1、 首先调用Socket类的构造函数,以服务器的指定的IP地址或指定的主机名和指定的端口号为参数,创建一个Socket流,在创建Socket流的过程中包含了向服务器请求建立通讯连接的过程实现。
2、 建立了客户端通讯Socket后。就可以使用Socket的方法getInputStream()和getOutputStream()来创建输入/输出流。这样,使用Socket类后,网络输入输出也转化为使用流对象的过程。
3、 使用输入输出流对象的相应方法读写字节流数据,因为流连接着通讯所用的Socket,Socket又是和服务器端建立连接的一个端点,因此数据将通过连接从服务器得到或发向服务器。这时我们就可以对字节流数据按客户端和服务器之间的协议进行处理,完成双方的通讯任务。
4、 待通讯任务完毕后,我们用流对象的close()方法来关闭用于网络通讯的输入输出流,在用Socket对象的close()方法来关闭Socket。
下面,我想通过一个简单的例子来进一步介绍一下客户端程序的编写
代码一:
import java.io.*;
import java.net.*;
public class SocketCommunicationClient
{
public static void main(String[] args)
{
try{
Socket clientSocket =new Socket ("mice",9000);//创建一个流Socket并与主机mice上的端口9000相连接
OutputStream output =clientSocket.getOutputStream();//向此Socket写入字节的一个输出流
DataInputStream input=new DataInputStream(clientSocket.getInputStream());
file://创建新的数据输入流以便从指定的输入流中读出数据
int c;
String response;
while (( c= System.in.read())!=-1)//从屏幕上接受输入的字符串,并且分解成一个个字符
{
output.write((byte)c);
if(c=='\\n')//如果字符为回车,则输出字符串缓冲
{
output.flush();
response=input.readLine();
System.out.println("Communication:"+response);
}
}
output.close();
input.close();
clientSocket.close();
} catch (Exception e){
System.err.println("Exception :"+e);
}
}
}
这个程序是一个非常的简单的数据通讯的例子,程序先创建了一个Socket并和主机mice上的端口9000相连接,然后打开输入输出流,接着程序从标准输入接收字符并写入流中,每写满一行(以用户键入回车为标志),就把缓冲区中的字符串送往mice上的服务器端程序进行处理,等待服务器端的应答。input.readLine()方法调用将导致程序停滞直到收到应答信息,程序将一直重复这个过程,直到用户输入中止符。最后程序要关闭socket输入输出流,在关闭socket和服务器端的连接。
上面我们看了如何使用Java编写客户端的Socket接口程序,下面我也想简要的谈一谈服务器端的Socket接口程序的Java实现方法,其过程如下所述:
1、 首先调用ServerSocket类以某个端口号为参数,创建一个ServerSocket对象,即是服务器端的服务程序在该指定端口监听的Socket。
2、 服务器端程序使用ServerSocket对象的accept()方法,接收来自客户机程序的连接请求,此时服务器端将一直保持停滞状态,直到收到客户端发来的连接请求,此时该方法将返回一个新建的Socket类的实例,代表和客户机建立的通讯链路在服务程序内的通讯端点。如果采用Java的多线程编程方法,可以实现并发服务器,继续监听来自其他客户的连接请求。
3、 使用新建的Socket对象创建输入、输出流对象。
4、 使用流对象的方法完成和客户端的数据传输,按约定协议识别并处理来自客户端的请求数据,并把处理的结果返回给客户端。
5、 客户端工作完毕后,则服务器端程序关闭和客户端通讯的流和通讯的Socket。
6、 在服务器程序运行结束之间,应当关闭用来监听的Socket.
下面让我们来看一个服务器端的程序的Java实现:
代码二:
import java.net.*;
import java.io.*;
public class SocketCommunicationServer
{
public static void main(String[] args)
try
{
boolean flag=true;//设置标志位为真
Socket client=null;//创建Socket client以接收来自客户端的请求
String inputLine;
ServerSocket serverSocket =new ServerSocket (9000);//以端口9000创建一个服务器Socket
System.out.println("服务器在端口9000上监听");
file://也可以使用serverSocket.getLocalPort()来获得端口号
while(flag)
{
client=serverSocket.accept();
file://监听并接受与此Socket的连接,该方法会阻塞直到有一个连接产生
DataInputStream input=new DataInputStream(new BufferedInputStream(client.getInputStream()));
PrintStream output=new PrintStream(new BufferedOutputStream(client.getOutputStream());
while (( inputLine= input.readLine())!=null)
{
if(inputLine.equals("Stop"))
{
flag=false;
break;
}
output.println(inputLine);
output.flush();
}
output.close();
input.close();
client.close();
)
serverSocket.close();
)catch(IOException e){}
)
)
)
以上,我简略的谈了一下Socket编程的机制以及使用Java进行Socket编程的实现方法,希望能对大家有所帮助。用Java开发网络软件非常方便和强大,Java的这种力量来源于他独有的一套强大的用于网络的 API,这些API是一系列的类和接口,均位于包java.net和javax.net中。在这篇文章中我们将介绍套接字(Socket)慨念,同时以实例说明如何使用Network API操纵套接字,在完成本文后,你就可以编写网络低端通讯软件。
什么是套接字(Socket)?
Network API是典型的用于基于TCP/IP网络Java程序与其他程序通讯,Network API依靠Socket进行通讯。Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。如图1
我们来分析一下图1,Host A上的程序A将一段信息写入Socket中,Socket的内容被Host A的网络管理软件访问,并将这段信息通过Host A的网络接口卡发送到Host B,Host B的网络接口卡接收到这段信息后,传送给Host B的网络管理软件,网络管理软件将这段信息保存在Host B的Socket中,然后程序B才能在Socket中阅读这段信息。
假设在图1的网络中添加第三个主机Host C,那么Host A怎么知道信息被正确传送到Host B而不是被传送到Host C中了呢?基于TCP/IP网络中的每一个主机均被赋予了一个唯一的IP地址,IP地址是一个32位的无符号整数,由于没有转变成二进制,因此通常以小数点分隔,如:198.163.227.6,正如所见IP地址均由四个部分组成,每个部分的范围都是0-255,以表示8位地址。
值得注意的是IP地址都是32位地址,这是IP协议版本4(简称Ipv4)规定的,目前由于IPv4地址已近耗尽,所以IPv6地址正逐渐代替Ipv4地址,Ipv6地址则是128位无符号整数。
假设第二个程序被加入图1的网络的Host B中,那么由Host A传来的信息如何能被正确的传给程序B而不是传给新加入的程序呢?这是因为每一个基于TCP/IP网络通讯的程序都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的短口号保留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口、端口号,这样形成的整体就可以区别每一个套接字t,下面我们就来谈谈两种套接字:流套接字和自寻址数据套接字。
流套接字(Stream Socket)
无论何时,在两个网络应用程序之间发送和接收信息时都需要建立一个可靠的连接,流套接字依靠TCP协议来保证信息正确到达目的地,实际上,IP包有可能在网络中丢失或者在传送过程中发生错误,任何一种情况发生,作为接受方的 TCP将联系发送方TCP重新发送这个IP包。这就是所谓的在两个流套接字之间建立可靠的连接。
流套接字在C/S程序中扮演一个必需的角色,客户机程序(需要访问某些服务的网络应用程序)创建一个扮演服务器程序的主机的IP地址和服务器程序(为客户端应用程序提供服务的网络应用程序)的端口号的流套接字对象。
客户端流套接字的初始化代码将IP地址和端口号传递给客户端主机的网络管理软件,管理软件将IP地址和端口号通过NIC传递给服务器端主机;服务器端主机读到经过NIC传递来的数据,然后查看服务器程序是否处于监听状态,这种监听依然是通过套接字和端口来进行的;如果服务器程序处于监听状态,那么服务器端网络管理软件就向客户机网络管理软件发出一个积极的响应信号,接收到响应信号后,客户端流套接字初始化代码就给客户程序建立一个端口号,并将这个端口号传递给服务器程序的套接字(服务器程序将使用这个端口号识别传来的信息是否是属于客户程序)同时完成流套接字的初始化。
如果服务器程序没有处于监听状态,那么服务器端网络管理软件将给客户端传递一个消极信号,收到这个消极信号后,客户程序的流套接字初始化代码将抛出一个异常对象并且不建立通讯连接,也不创建流套接字对象。这种情形就像打电话一样,当有人的时候通讯建立,否则电话将被挂起。
这部分的工作包括了相关联的三个类:InetAddress, Socket, 和 ServerSocket。 InetAddress对象描绘了32位或128位IP地址,Socket对象代表了客户程序流套接字,ServerSocket代表了服务程序流套接字,所有这三个类均位于包java.net中。
InetAddress类
InetAddress类在网络API套接字编程中扮演了一个重要角色。参数传递给流套接字类和自寻址套接字类构造器或非构造器方法。InetAddress描述了32位或位IP地址,要完成这个功能,InetAddress类主要依靠两个支持类Inet4Address 和 Inet6Address,这三个类是继承关系,InetAddrress是父类,Inet4Address 和 Inet6Address是子类。
由于InetAddress类只有一个构造函数,而且不能传递参数,所以不能直接创建InetAddress对象,比如下面的做法就是错误的:
| InetAddress ia = new InetAddress (); |
但我们可以通过下面的5个工厂方法创建来创建一个InetAddress对象或InetAddress数组:
. getAllByName(String host)方法返回一个InetAddress对象的引用,每个对象包含一个表示相应主机名的单独的IP地址,这个IP地址是通过host参数传递的,对于指定的主机如果没有IP地址存在那么这个方法将抛出一个UnknownHostException 异常对象。
. getByAddress(byte [] addr)方法返回一个InetAddress对象的引用,这个对象包含了一个Ipv4地址或Ipv6地址,Ipv4地址是一个4字节数组,Ipv6地址是一个16字节地址数组,如果返回的数组既不是4字节的也不是16字节的,那么方法将会抛出一个UnknownHostException异常对象。
. getByAddress(String host, byte [] addr)方法返回一个InetAddress对象的引用,这个InetAddress对象包含了一个由host和4字节的addr数组指定的IP地址,或者是host和16字节的addr数组指定的IP地址,如果这个数组既不是4字节的也不是16位字节的,那么该方法将抛出一个UnknownHostException异常对象。
. getByName(String host)方法返回一个InetAddress对象,该对象包含了一个与host参数指定的主机相对应的IP地址,对于指定的主机如果没有IP地址存在,那么方法将抛出一个UnknownHostException异常对象。
. getLocalHost()方法返回一个InetAddress对象,这个对象包含了本地机的IP地址,考虑到本地主机既是客户程序主机又是服务器程序主机,为避免混乱,我们将客户程序主机称为客户主机,将服务器程序主机称为服务器主机。
上面讲到的方法均提到返回一个或多个InetAddress对象的引用,实际上每一个方法都要返回一个或多个Inet4Address/Inet6Address对象的引用,调用者不需要知道引用的子类型,相反调用者可以使用返回的引用调用InetAddress对象的非静态方法,包括子类型的多态以确保重载方法被调用。
InetAddress和它的子类型对象处理主机名到主机IPv4或IPv6地址的转换,要完成这个转换需要使用域名系统,下面的代码示范了如何通过调用getByName(String host)方法获得InetAddress子类对象的方法,这个对象包含了与host参数相对应的IP地址:
| InetAddress ia = InetAddress.getByName ("www.javajeff.com")); |
一但获得了InetAddress子类对象的引用就可以调用InetAddress的各种方法来获得InetAddress子类对象中的IP地址信息,比如,可以通过调用getCanonicalHostName()从域名服务中获得标准的主机名;getHostAddress()获得IP地址,getHostName()获得主机名,isLoopbackAddress()判断IP地址是否是一个loopback地址。
List1 是一段示范代码:InetAddressDemo
// InetAddressDemo.java
import java.net.*;
class InetAddressDemo
{
public static void main (String [] args) throws UnknownHostException
{
String host = "localhost";
if (args.length == 1)
host = args [0];
InetAddress ia = InetAddress.getByName (host); System.out.println ("Canonical Host Name = " + ia.getCanonicalHostName ()); System.out.println ("Host Address = " + ia.getHostAddress ()); System.out.println ("Host Name = " + ia.getHostName ()); System.out.println ("Is Loopback Address = " + ia.isLoopbackAddress ()); } } |
当无命令行参数时,代码输出类似下面的结果:
| Canonical Host Name = localhost Host Address = 127.0.0.1 Host Name = localhost Is Loopback Address = true |
InetAddressDemo给了你一个指定主机名作为命令行参数的选择,如果没有主机名被指定,那么将使用localhost(客户机的),InetAddressDemo通过调用getByName(String host)方法获得一个InetAddress子类对象的引用,通过这个引用获得了标准主机名,主机地址,主机名以及IP地址是否是loopback地址的输出。下载本文