我们知道网络通信,既然是通信他就不再局限于一台机器上的数据传送,他实现了不同机器上的数据传输。现在开始介绍一下,java中时怎么去实现通信的。从简单入手、实现一个客服端和服务器之间的简单通信。
客服端和服务器能实现连接和通信的条件:客服端的创建需要知道它所连接的服务器的ip和端口号。服务器的创建需要一个端口号。至于ip可能大家比较清楚,我们知道每一台其计算机都有一个独一无二的ip地址,之所以为什么?这个很容易理解每不同的ip对应一台计算机才能实现数据的准确传输,只要你ip弄对了就不会发生你想把数据发送到A但却却发送到B这样的问题。而端口号又是什么呢?用ip类比,ip相当于一台计算机,而端口号就相当于一台电脑上一个程序,你要给一台计算机上的某一个程序发送数据,不仅仅要知道这台计算机的在哪里,还要知道这个程序在哪里。即你要知道ip(计算机的位置),和端口号(程序的位置)。再做一个类比,ip相当于一个酒店名,而他端口号就是这个就酒店房间的房间号。这样只要有了这两个信息我们就能很容易的找到我们要找的房间。
说了这么多,如何用java代码编程实现通信了。这个是这边文章所要讲的重点内容。
我们知道java中就是利用一个个类实例化的一个个对象,并调用他们所特有的方法来实现完成一个软件的开发的。当然通信也不例外。
服务器的创建就是实例化一个ServerSocket类的一个对象。如下我们定义一个创建服务器的方法。
/** * 创建服务器的方法 * @param port端口号 */ public void creat_server(int port){ try { //实例化服务器对象 ServerSocket server=new ServerSocket(port); System.out.println("服务器启动成功,等待客服端连接、、、"); } catch (IOException e) { e.printStackTrace(); } }
这样一个非常简单的客户端和服务器就建立起来了。
那么该如何去实现我们建立起来的客户端和服务器的数据传输呢?这个是通信的重点所在。
如上面所讲有了独一无二的ip和特定的端口号我们能让客户端很容易的找它所要连接的服务器。那么怎么样让客户端和服务器连接起来呢?这个很简单,只要调用我们上面建好的服务器对象server调用它等待客户端连接的方法accept(),就行了.
即Socket client=server.accept();
这是一个会阻塞的方法,所谓的阻塞就是当程序运行到这段代码的时候会停在这边,即等待到有相应的客户端程序运行起来,这句代码下面的代码才能被执行。
要是还不懂,做一个小测试。
Public class Test{ Public static void main(String [] args){ try { //实例化服务器对象 ServerSocket server=new ServerSocket(9090); System.out.println("服务器启动成功,等待客服端连接、、、"); //等待客服端连接 Socket client=server.accept(); System.out.println("连接服务器成功了!!!"); } catch (IOException e) { e.printStackTrace(); } } }
把这个程序运行起来,下面的输出语句并没有被打印出来,因为上面server.accept();这个方法阻塞住了代码的执行。想要让他执行起来,只要有相应的客户端程序运行起来就行。
如在一个主函数中运行我们上面写的创建客户端的代码。就能在控制台看到我们打印出来的东西了。需要注意的是创建客户端的端口号要和服务器的端口号对应起来,也就是上面的9090。这边就不多做测试,留给你们自己动手。
这样我们就实现了客户端和服务器之间的连接了。
那么接下来就是讲讲他们之间的交流了。
I/o流我们都知道。(但大多人都没有很好的去解析java中的流,包括本人,所以在学通信过程中很多由于流而产生的问题都没有去真正解决,或者是解决了但却不知道所以然)。
其实java通信过程简单的说就是流之间传送数据的过程、流的输入和输出过程。
那么我们就必须从客户端和服务器中获取输入输出流。这个很容易。
从我们上面实例化的客户端client对象中调用它的方法便能获取流。
即:InputStream in=client.getInputStream();
OutputStream out=client.getOutputStream();
服务器和客户端两端的流都是这样获取的。
获取了流,那么下面就可以利用流来实现客户端和服务器之间的数据传输了。
比如用客户端这边的输出流out执行这样一句代码:
out.write(10);
意思就是从客户端向服务器发送10这个整形数据。
那么以之相应的是服务器端执行代码:
int i=In.read();
那么这样服务器就读取了客户端所发送过来的数据。这样是否能准确传送客户端和服务器之间的数据呢?我们下面来做个测试。
服务器:
Public class Test{ Public static void main(String [] args){ try { // 实例化服务器对象 ServerSocket server= new ServerSocket(9090); // 等待客服端连接 Socket client=server.accept(); System.out.println("服务器启动成功,等待客服端连接、、、"); InputStream in=client.getInputStream(); OutputStream out=client.getOutputStream(); int i=0; while(true){ i=in.read(); System.out.println("客户端发送过来的数据是:"+i); } } catch (IOException e) { e.printStackTrace(); } }
客服端:
Public class Test2{ Public static void main(String [] args){ try { // 实例化客服端对象 Socket client= new Socket ("localhost",9090); InputStream in=client.getInputStream(); OutputStream out=client.getOutputStream(); int i=0; while(true){ i++; out.write(i); Thread.sleep(1000); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }
先运行服务器,在运行客户端,这样我们就可以从控制台看到打印出来如下:
客户端发送过来的数据是:1
客户端发送过来的数据是:2
客户端发送过来的数据是:3
客户端发送过来的数据是:4
。。。。。。。。
客户端发送过来的数据是:254
客户端发送过来的数据是:255
客户端发送过来的数据是:0
客户端发送过来的数据是:1
客户端发送过来的数据是:2
为什么255后面不是256?? 客户端发送过来的数据是:2
客户端发送过来的数据是:3
客户端发送过来的数据是:4
。。。。。。。。
客户端发送过来的数据是:254
客户端发送过来的数据是:255
客户端发送过来的数据是:0
客户端发送过来的数据是:1
客户端发送过来的数据是:2
即用out.write(256);
单用int i=in.read();接收到的却是0;
这个问题我实在做网络画图板的时候发现的。比如说要在客户端画上一条直线,在服务器上能把这条直线显示出来。那么就必须把直线的四个坐标点传过去。四个坐标点都是整形,坐标点有很多大于255的,所以用上面的方法就出现了这样的问题,当你四个坐标点都不大于255的时候直线在服务器上画出来是和客户端一模一样的,也就是数据正确传送了。但但出现大于255的时候两条直线就差别很大了。这究竟是为什么呢?
下面给你解答:
由于OutputStream和InputStream这两种流在传输过程中是以一个字节为单位的。一个字节8个位所以最大只能传送2^8-1=255;当超过255它就自动截取低8位,比如256写成二进制是100000000,它就截取了8个0,所以服务器那边接收到的就是0了。
相信这样并不难理解。
发现了是由于这样的原因造成的,那么怎么去解决这个问题呢?
知道了原因去解决问题是不难的。
我们在传送整形数据之间可以先计算他的字节数,先发送字节数(有几个字节)给服务器,然后将它拆分成一个个字节,用for循环再用out.write();去发送数据;在接收到先收到它的字节数,然后相应的用for循环来把一个个字节接收过来。在并起来,两者是一个逆过程。这个我们就要自己去实现几个方法:
发送端:
1.获取一个整形数据对应的二进制的字节数。
2.获取一个整形数据对象的二进制字符串,不足8的倍速补零。
这样我们就能截取一个个8位字节去发送了。
接收端:
1.根据获取的字节数来计算相应的整形数据。
比如发送端发送256,那么接收到会接收到2个字节,第一个字节获取的整形数据是0,第二是1,但第二个要相应乘于2^8,也就是256,然后两者相加。这样就能获得了。相信这样并不难懂。要是有三个字节。那么获取的第三个整形数据要乘以2^16在加上前面两个字节对应的整形数据。
以上方法我们是基于InputStream和OutputStream流加上我们对问题的理解自定义的能准确传送比较大的整形数据的方法。显然十分的麻烦。其实有更简单的方法,用java中写好的类和封装好的方法可以非常容易的实现大型整形数据的传送。(他的底层操作或许就是我上面所说的哦!!!!)
Java中提供了数据流DataInputStream和DataOutputStream这两个数据流
他们有writeInt(),和相应的readInt()方法;你写入一个什么样的整形他就会相应读取到什么整形数。在实例化数据流的对象时只要把我们上面获取的in和out流对象作为参数,便可以建立两者之间的联系了。
如DaraOutputStream dataout=new DataOutputStream(out);这样就行了。具体测试就有兴趣的可以自己去测试一下。
有了上面这些知识,去实现一个简单的网络画图板、网络五子棋、还有文件的传送和聊天工具就不难了、有兴趣的同学可以去实践一下。
关于java网络通信编程这边就略作介绍这些。后面关于我在利用java网络通信编程在写自己小项目遇到的问题和我如何去解决在做介绍。