JAVA编程中的服务端和客户端的Socket实例是不是同一个
不是,虽然最后都是Socket对象之间的通讯,服务器端的Socket是由ServerSocket在端口侦听,在接收到客户端的连接请求后,产生的;客户端则是利用Socket对象,去连接服务器。
Java 远程处理
Java远程方法调用(RMI)提供了Java程序语言的远程通讯功能,这种特性使客户机上运行的程序可以调用远程服务器上的对象,使Java编程人员能够在网络环境中分布操作。
创建一个简单的Java分布式远程方法调用程序可以按以下几个步骤操作,
一、定义远程接口:
在 Java 中,远程对象是实现远程接口的类的实例, 远程接口声明每个要远程调用的方法。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,客户通过接口句柄发送消息即可。
远程接口具有如下特点:
1) 远程接口必须为public属性。如果不这样,除非客户端与远程接口在同一个包内,否则 当试图装入实现该远程接口的远程对象时,调用会得到错误结果。
2) 远程接口必须扩展接口javarmiRemote。
3) 除与应用程序本身特定的例外之外,远程接口中的每个方法都必须在自己的throws从句中 声明javarmiRemoteException。(或 RemoteException 的父类)。
4) 作为参数或返回值传递的一个远程对象(不管是直接,还是本地对象中嵌入)必须声明为远 程接口,而不应声明为实施类。
下面是远程接口的定义
[java] view plaincopy
package test;
import javarmiRemote;
import javarmiRemoteException;
import javamathBigInteger;
public interface Fib extends Remote {
public int getFib(int n) throws RemoteException;
// public BigInteger getFib(BigInteger n) throws RemoteException;
}
二、实现远程接口:
远程对象实现类必须扩展远程对象javarmiUnicastRemoteObject类,并实现所定义的远程接口。远程对象的实现类中包含实现每个远程接口所指定的远程方法的代码。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。因为客户是指向接口的一个句柄,而不是它的哪个类。必须为远程对象定义构造函数,即使只准备定义一个默认构造函数,用它调用基础类构造函数。因为基础类构造函数可能会抛出 javarmiRemoteException,所以即使别无它用必须抛出javarmiRemoteException例外。
以下是远程对象实现类的声明:
[java] view plaincopy
package test;
import javamathBigInteger;
import javarmi;
import javarmiserverUnicastRemoteObject;
public class FibImp extends UnicastRemoteObject implements Fib {
public FibImp() throws RemoteException {
super();
}
public int getFib(int n) throws RemoteException {
return n+2;
}
}
三、编写服务器类:
包含 main 方法的类可以是实现类自身,也可以完全是另一个类。下面通过RmiSampleServer 来创建一个远程对象的实例,并通过javarmiregistryLocateRegistry类的createRegistry 方法从指定端口号启动注册服务程序,也可以通过执行 rmiregistry 命令启动注册服务程序,注册服务程序的缺省运行端口为 1099。必须将远程对象名字绑定到对远程对象的引用上: Namingrebind("//localhost:8808/SAMPLE-SERVER" , Server);
以下是服务器类的声明:
[java] view plaincopy
package test;
import javanetMalformedURLException;
import javarmiNaming;
import javarmiRemoteException;
import javarmiregistryLocateRegistry;
public class FibonacciServer {
/
@param args
/
public static void main(String[] args) {
try {
LocateRegistrycreateRegistry(8804);
FibImp f = new FibImp();
// 注册到 registry 中
Namingrebind("//localhost:8804/SAMPLE-SERVER", f);
Systemoutprintln("fib server ready");
} catch (RemoteException re) {
Systemoutprintln("Exception in FibonacciImplmain: " + re);
} catch (MalformedURLException e) {
Systemoutprintln("MalformedURLException " + e);
}
}
}
四、编写使用远程服务的客户机类:
客户机类的主要功能有两个,一是通过Naminglookup方法来构造注册服务程序 stub 程序实例,二是调用服务器远程对象上的远程方法。
以下是客户端类的声明:
[java] view plaincopy
package testClient;
import testFib;
import javamathBigInteger;
import javanetMalformedURLException;
import javarmiNaming;
import javarmiNotBoundException;
import javarmiRemoteException;
public class FibClient {
/
@param args
/
public static void main(String[] args) {
String url = "//localhost:8804/SAMPLE-SERVER";
try {
Fib calc = (Fib) Naminglookup(url);
for (int i = 0; i < 10; ++i) {
int f = calcgetFib(i);
Systemoutprintln(f);
}
} catch (MalformedURLException e) {
eprintStackTrace();
} catch (RemoteException e) {
eprintStackTrace();
} catch (NotBoundException e) {
eprintStackTrace();
}
}
}
提供一个聊天功能的给你
import javanet;
import javaio;
class My1 extends Thread
{
private Socket skt;
public My1(Socket skt)
{
thisskt = skt;
}
public void run()
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(sktgetInputStream()));
for(;;)
{
if(brreadLine()==null) {break;}
Systemoutprintln(brreadLine()); // 阻塞
}
}
catch(Exception e)
{
Systemoutprintln("对方断线!");
}
}
}
class My2 extends Thread
{
private Socket skt;
public My2(Socket skt)
{
thisskt = skt;
}
public void run()
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(Systemin));
PrintWriter pw = new PrintWriter(sktgetOutputStream());
for(;;)
{
pwprintln("姓名====>:"+brreadLine()); // 阻塞
pwflush();
}
}
catch(Exception e)
{
Systemoutprintln("对方断线!");
}
}
}
public class Good
{
public static void main(String[] args) throws Exception
{
//连接的目的是:获得Socket对象
Socket skt;
Systemoutprint("请输入<port> 或者:<ip>,<port> ");
// 先选择角色
String[] ss = new BufferedReader(new InputStreamReader(Systemin))readLine()split(",");
if(sslength==1)
skt = new ServerSocket(IntegerparseInt(ss[0]))accept();
else
skt = new Socket(ss[0], IntegerparseInt(ss[1]));
Systemoutprintln("连接到:" + sktgetInetAddress()getHostAddress());
// 连接建立,角色消失
new My1(skt)start(); //读网络
new My2(skt)start(); //读键盘
}
}
要完成这个工作,需要完成三个部分的工作,以下依次说明:
一、建立服务器类
Java中有一个专门用来建立Socket服务器的类,名叫ServerSocket,可以用服务器需要使用的端口号作为参数来创建服务器对象。
ServerSocket server = new ServerSocket(9998)
这条语句创建了一个服务器对象,这个服务器使用9998号端口即在端口9998上注册服务,这里稍微要注意的是端口的分配必须是唯一的。因为端口是为了唯一标识每台计算机唯一服务的,另外端口号是从0~65535之间的,前1024个端口已经被Tcp/Ip 作为保留端口,因此你所分配的端口只能是1024个之后的。当一个客户端程序建立一个Socket连接,所连接的端口号为9998时,服务器对象server便响应这个连接,并且serveraccept()方法会创建一个Socket对象。服务器端便可以利用这个Socket对象与客户进行通讯。
Socket incoming = serveraccept() ; // 监听窗口,等待连接
进而得到输入流和输出流,并进行封装
BufferedReader in = new BufferedReader(new
InputStreamReader(incominggetInputStream()));
/
当读取文件时,先把内容读到缓存中,当调用inreadLine()时,再从缓存中以字符的方式读取数据(以下简称“缓存字节读取方式”)。
/
PrintWriter ut = new PrintWriter(incominggetOutputStream(),true);
随后,就可以使用inreadLine()方法得到客户端的输入,也可以使用outprintln()方法向客户端发送数据。从而可以根据程序的需要对客户端的不同请求进行回应。
在所有通讯结束以后应该关闭这两个数据流,关闭的顺序是先关闭输出流,再关闭输入流,即使用
outclose();
inclose();
二、建立客户端代码
相比服务器端,客户端要简单一些,客户端只需用服务器所在机器的ip以及服务器的端口作为参数创建一个Socket对象。得到这个对象后,就可以用"建立服务器"部分介绍的方法实现数据的输入和输出。
Socket socket = new Socket("1681601242",9998);
或:
Socket socket = new Socket(InetAddressgetLocalHost(),5678); // 向主机名为InetAddressgetLocalHost()的服务器申请连接
客户机必须知道有关服务器的IP地址,对于着一点Java也提供了一个相关的类InetAddress 该对象的实例必须通过它的静态方法来提供,它的静态方法主要提供了得到本机IP 和通过名字或IP直接得到InetAddress的方法。
in = new BufferedReader(new InputStreamReader(socketgetInputStream()));
out = new PrintWriter(socketgetOutputStream(),true);
以上的程序代码建立了一个Socket对象,这个对象连接到ip地址为1681601242的主机上、端口为9998的服务器对象。并且建立了输入流和输出流,分别对应服务器的输出和客户端的写入。
三、实例分析
服务方:
import javaio;
import javanet;
public class MyServer {
public static void main(String[] args) throws IOException{
ServerSocket server=new ServerSocket(5678); //在端口5678上注册服务
Socket client=serveraccept(); // 监听窗口,等待连接
BufferedReader in=new BufferedReader(new InputStreamReader(clientgetInputStream()));
BufferedReader serverInput=new BufferedReader(new InputStreamReader(Systemin));
PrintWriter ut=new PrintWriter(clientgetOutputStream());
while(true){
String str=inreadLine(); //// 读取从client传来的数据信息
str = serverInputreadLine(); // 读取用户键盘输入的字符串
Systemoutprintln(str); //服务器控制台输出数据信息
outprintln("has receive"); //服务器向客户端发送信息:has receive
outflush();
if(strequals("end"))
break;
}
clientclose();
}
}
这个程序的主要目的在于服务器不断接收客户机所写入的信息只到,客户机发送"End"字符串就退出程序,并且服务器也会做出"Receive"为回应,告知客户机已接收到消息。
客户机代码:
import javanet;
import javaio;
public class Client{
static Socket server;
public static void main(String[] args)throws Exception{
server=new Socket(InetAddressgetLocalHost(),5678); // 向主机名为InetAddressgetLocalHost()的服务器申请连接
BufferedReader in=new BufferedReader(new InputStreamReader(servergetInputStream())); //客户端建立输入流并进行封装
PrintWriter ut=new PrintWriter(servergetOutputStream());
BufferedReader wt=new BufferedReader(new InputStreamReader(Systemin)); //客户端从键盘输入信息
while(true){
String str=wtreadLine(); //客户端读取(获得)键盘的字符串
String str1=inreadLine(); // 从服务器获得字符串
outprintln(str); //客户端向服务器发送信息
outflush();
if(strequals("end")){
break;
}
Systemoutprintln(inreadLine());
}
serverclose();
}
}
客户机代码则是接受客户键盘输入,并把该信息输出,然后输出"End"用来做退出标识。
这个程序只是简单的两台计算机之间的通讯,如果是多个客户同时访问一个服务器呢?你可以试着再运行一个客户端,结果是会抛出异常的。那么多个客户端如何实现呢
其实,简单的分析一下,就可以看出客户和服务通讯的主要通道就是Socket本身,而服务器通过accept方法就是同意和客户建立通讯这样当客户建立Socket的同时。服务器也会使用这一根连线来先后通讯,那么既然如此只要我们存在多条连线就可以了。那么我们的程序可以变为如下:
服务器:
import javaio;
import javanet;
public class MyServer {
public static void main(String[] args) throws IOException{
ServerSocket server=new ServerSocket(5678);
while(true){
Socket client=serveraccept();
BufferedReader in=new BufferedReader(new InputStreamReader(clientgetInputStream()));
PrintWriter ut=new PrintWriter(clientgetOutputStream());
while(true){
String str=inreadLine();
Systemoutprintln(str);
outprintln("has receive");
outflush();
if(strequals("end"))
break;
}
clientclose();
}
}
}
这里仅仅只是加了一个外层的While循环,这个循环的目的就是当一个客户进来就为它分配一个Socket直到这个客户完成一次和服务器的交互,这里也就是接受到客户的"End"消息那么现在就实现了多客户之间的交互了。但是问题又来了,这样做虽然解决了多客户,可是是排队执行的。也就是说当一个客户和服务器完成一次通讯之后下一个客户才可以进来和服务器交互,无法做到同时服务,那么要如何才能同时达到既能相互之间交流又能同时交流呢?很显然这是一个并行执行的问题了。所以线程是最好的解决方案。
那么下面的问题是如何使用线程首先要做的事情是创建线程并使得其可以和网络连线取得联系。然后由线程来执行刚才的操作,要创建线程要么直接继承Thread要么实现Runnable接口,要建立和Socket的联系只要传递引用就可以了而要执行线程就必须重写run方法,而run方法所做的事情就是刚才单线程版本main所做的事情,因此我们的程序变成了这样:
import javanet;
import javaio;
public class MultiUser extends Thread{
private Socket client;
public MultiUser(Socket c){
thisclient=c;
}
public void run(){
try{
BufferedReader in=new BufferedReader(new InputStreamReader(clientgetInputStream()));
PrintWriter ut=new PrintWriter(clientgetOutputStream());
//Mutil User but can't parallel
while(true){
String str=inreadLine();
Systemoutprintln(str);
outprintln("has receive");
outflush();
if(strequals("end"))
break;
}
clientclose();
}catch(IOException ex){
}finally{
}
}
public static void main(String[] args)throws IOException{
ServerSocket server=new ServerSocket(5678);
while(true){
//transfer location change Single User or Multi User
MultiUser mu=new MultiUser(serveraccept());
mustart();
}
}
}
我的类直接从Thread类继承了下来并且通过构造函数传递引用和客户Socket建立了联系,这样每个线程就有了。一个通讯管道同样我们可以填写run方法,把之前的操作交给线程来完成,这样多客户并行的Socket就建立起来了。
下面是一个简单的通讯实例,进行Server和Client之间的文件传输。。如果是简单的文本传输的话简化掉文本操作的内容即可。。
1服务器端
package sterning;
import javaioBufferedInputStream;
import javaioDataInputStream;
import javaioDataOutputStream;
import javaioFile;
import javaioFileInputStream;
import javanetServerSocket;
import javanetSocket;
public class ServerTest {
int port = 8821;
void start() {
Socket s = null;
try {
ServerSocket ss = new ServerSocket(port);
while (true) {
// 选择进行传输的文件
String filePath = "D:\\librar";
File fi = new File(filePath);
Systemoutprintln("文件长度:" + (int) filength());
// public Socket accept() throws
// IOException侦听并接受到此套接字的连接。此方法在进行连接之前一直阻塞。
s = ssaccept();
Systemoutprintln("建立socket链接");
DataInputStream dis = new DataInputStream(new BufferedInputStream(sgetInputStream()));
disreadByte();
DataInputStream fis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)));
DataOutputStream ps = new DataOutputStream(sgetOutputStream());
//将文件名及长度传给客户端。这里要真正适用所有平台,例如中文名的处理,还需要加工,具体可以参见Think In Java 4th里有现成的代码。
pswriteUTF(figetName());
psflush();
pswriteLong((long) filength());
psflush();
int bufferSize = 8192;
byte[] buf = new byte[bufferSize];
while (true) {
int read = 0;
if (fis != null) {
read = fisread(buf);
}
if (read == -1) {
break;
}
pswrite(buf, 0, read);
}
psflush();
// 注意关闭socket链接哦,不然客户端会等待server的数据过来,
// 直到socket超时,导致数据不完整。
fisclose();
sclose();
Systemoutprintln("文件传输完成");
}
} catch (Exception e) {
eprintStackTrace();
}
}
public static void main(String arg[]) {
new ServerTest()start();
}
}
2socket的Util辅助类
package sterning;
import javanet;
import javaio;
public class ClientSocket {
private String ip;
private int port;
private Socket socket = null;
DataOutputStream out = null;
DataInputStream getMessageStream = null;
public ClientSocket(String ip, int port) {
thisip = ip;
thisport = port;
}
/ //
创建socket连接
@throws Exception
exception
/
public void CreateConnection() throws Exception {
try {
socket = new Socket(ip, port);
} catch (Exception e) {
eprintStackTrace();
if (socket != null)
socketclose();
throw e;
} finally {
}
}
public void sendMessage(String sendMessage) throws Exception {
try {
out = new DataOutputStream(socketgetOutputStream());
if (sendMessageequals("Windows")) {
outwriteByte(0x1);
outflush();
return;
}
if (sendMessageequals("Unix")) {
outwriteByte(0x2);
outflush();
return;
}
if (sendMessageequals("Linux")) {
outwriteByte(0x3);
outflush();
} else {
outwriteUTF(sendMessage);
outflush();
}
} catch (Exception e) {
eprintStackTrace();
if (out != null)
outclose();
throw e;
} finally {
}
}
public DataInputStream getMessageStream() throws Exception {
try {
getMessageStream = new DataInputStream(new BufferedInputStream(socketgetInputStream()));
return getMessageStream;
} catch (Exception e) {
eprintStackTrace();
if (getMessageStream != null)
getMessageStreamclose();
throw e;
} finally {
}
}
public void shutDownConnection() {
try {
if (out != null)
outclose();
if (getMessageStream != null)
getMessageStreamclose();
if (socket != null)
socketclose();
} catch (Exception e) {
}
}
}
3客户端
package sterning;
import javaioBufferedOutputStream;
import javaioDataInputStream;
import javaioDataOutputStream;
import javaioFileOutputStream;
public class ClientTest {
private ClientSocket cs = null;
private String ip = "localhost";// 设置成服务器IP
private int port = 8821;
private String sendMessage = "Windwos";
public ClientTest() {
try {
if (createConnection()) {
sendMessage();
getMessage();
}
} catch (Exception ex) {
exprintStackTrace();
}
}
private boolean createConnection() {
cs = new ClientSocket(ip, port);
try {
csCreateConnection();
Systemoutprint("连接服务器成功!" + "\n");
return true;
} catch (Exception e) {
Systemoutprint("连接服务器失败!" + "\n");
return false;
}
}
private void sendMessage() {
if (cs == null)
return;
try {
cssendMessage(sendMessage);
} catch (Exception e) {
Systemoutprint("发送消息失败!" + "\n");
}
}
private void getMessage() {
if (cs == null)
return;
DataInputStream inputStream = null;
try {
inputStream = csgetMessageStream();
} catch (Exception e) {
Systemoutprint("接收消息缓存错误\n");
return;
}
try {
//本地保存路径,文件名会自动从服务器端继承而来。
String savePath = "E:\\";
int bufferSize = 8192;
byte[] buf = new byte[bufferSize];
int passedlen = 0;
long len=0;
savePath += inputStreamreadUTF();
DataOutputStream fileOut = new DataOutputStream(new BufferedOutputStream(newBufferedOutputStream(new FileOutputStream(savePath))));
len = inputStreamreadLong();
Systemoutprintln("文件的长度为:" + len + "\n");
Systemoutprintln("开始接收文件!" + "\n");
while (true) {
int read = 0;
if (inputStream != null) {
read = inputStreamread(buf);
}
passedlen += read;
if (read == -1) {
break;
}
//下面进度条本为图形界面的prograssBar做的,这里如果是打文件,可能会重复打印出一些相同的百分比
Systemoutprintln("文件接收了" + (passedlen 100/ len) + "%\n");
fileOutwrite(buf, 0, read);
}
Systemoutprintln("接收完成,文件存为" + savePath + "\n");
fileOutclose();
} catch (Exception e) {
Systemoutprintln("接收消息错误" + "\n");
return;
}
}
public static void main(String arg[]) {
new ClientTest();
}
}
0条评论