java网络编程详解_Java网络编程二:Socket详解

Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点。

一、使用Socket进行网络通信的过程

服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接请求。

客户端:客户端程序根据你服务器所在的主机名和端口号发出连接请求。

两者之间的通信是通过Socket完成的,我们可以认为Socket是两个城市之间的交通工具,有了它,就可以在两个城市之间穿梭了。

Socket通信示例

f69a94c0ec8af9b748151ab70bf60608.png

主机A的应用程序和主机B的应用程序通信,必须通过Socket建立连接,而建立Socket必须由底层的TCP/IP协议来建立TCP连接。建立TCP连接需要底层IP协议来寻址网络中的主机。IP地址只能帮助我们找到目标主机,但是一个主机上面有多个应用程序,如何才能找到我们需要的应用程序,这个时候就可以通过端口号来指定了。

二、简易服务端、客户端模拟

服务器端:

1 public static void main(String[] args) throwsIOException2 {3 //创建一个ServerSocket,用于监听客户端Socket连接请求

4 ServerSocket ss = new ServerSocket(8888);5 System.out.println("server start");6 //采用循环方式监听客户端的请求

7 while(true)8 {9 //侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。

10 Socket socket =ss.accept();11 OutputStream os =socket.getOutputStream();12 PrintStream ps = newPrintStream(os);13 ps.print("您好,您收到了来自服务端的中秋祝福");14 ps.close();15 os.close();16 socket.close();17 }18 }

执行结果:

server start

客户端:

1 public static void main(String[] args) throwsIOException, Exception2 {3 Socket socket = new Socket("localhost",8888);4 InputStream is =socket.getInputStream();5 BufferedReader br = new BufferedReader(newInputStreamReader(is));6 String str =br.readLine();7 System.out.println(str);8 br.close();9 is.close();10 socket.close();11 }

执行结果:

您好,您收到了来自服务端的中秋祝福

1、上面展示的是一个简易的服务端和客户端通信的建立过程。

2、我们通过交互图来详细介绍这个过程:

c30b60e93223900c4571cf4c7e878935.png

3、首先在server端,指定端口号创建serverSocket对象,通过serverSocket的accpet方法获取套接字,这个方法的特点是:侦听并接受到此套接字的连接,此方法在连接传入之前一直阻塞。这也就意味着,如果没有客户端连接请求过来,服务端会一致阻塞在这里。

4、后面的代码就是通过套接字socket可以得到输入输出流,到此为止,就是I/O的内容了。

5、在客户端这边,通过指定的服务器主机名和服务器监听的端口号,得到套接字Socket,这个时候就表示服务端和客户端的连接已经建立了,然后通过输入输出流来进行通信了。

三、半关闭的socket

在上面的Demo中,我们是以行作为通信的最小数据单位,服务器端也是逐行进行处理的。但是我们在大多数场景下,通信的数据单位是多行的,这时候Socket的输出流如何表达输出的数据已经结束?

在IO学习过程中提到过,如何要表示输出已经结束,则通过关闭输出流来实现,但是在socket中是行不通的,因为关闭socket,会导致无法再从该socket中读取数据了。为了解决这种问题,java提供了两个半关闭的方法:

1、shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。

2、shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流读取数据。

如果我们对同一个Socket实例先后调用shutdownInput和shutdownOutput方法,该Socket实例依然没有被关闭,只是该Socket既不能输出数据,也不能读取数据。

服务器端:

1 ServerSocket ss = new ServerSocket(5555);2 Socket socket =ss.accept();3 PrintStream ps = newPrintStream(socket.getOutputStream());4 ps.println("服务器端:开源中国杭州论坛");5 ps.println("服务器端:杭州G20峰会");6 //关闭输出流,表明输出已经结束

7 socket.shutdownOutput();8 //判断该socket是否关闭

9 System.out.println(socket.isClosed());10 Scanner scan = newScanner((socket.getInputStream()));11 while(scan.hasNextLine())12 {13 System.out.println(scan.nextLine());14 }15 scan.close();16 socket.close();17 ss.close();18

19

客户端:

1 Socket s = new Socket("localhost", 5555);2 InputStream is =s.getInputStream();3 byte[] buffer = new byte[1024];4 int flag = 0;5 while(-1 != (flag = is.read(buffer,0,buffer.length)))6 {7 String str = new String(buffer,0,flag);8 System.out.print(str);9 }10 PrintStream ps = newPrintStream(s.getOutputStream());11 ps.println("客户端:欢迎参加开源中国论坛");12 ps.println("客户端:欢迎参加G20峰会");13 is.close();14 ps.close();15 s.close();16

执行结果:

221d8fbfa1c7011084cc725c820f396a.png

cef2275b128339e9449a8c611c2b4737.png

在服务器端程序中可以看到,在输出两段字符串之后,调用了shutdownOutput方法,表示输出已经结束。随即又去判断了socket是否关闭,执行的结果为false,表示socket并未关闭。

但是在调用了这两个半关闭的方法关闭了输出输入流之后,该socket无法再次打开该输出流或者输入流。因此这种场景不适合保持持久通信状态的交互使用,只适合一站式的通信协议.例如http协议:客户端连接到服务器之后,开始发送数据,发送完成之后无须再次发送数据,只需要读取服务器响应数据即可,读取数据完毕之后,该socket连接也被关闭了。

四、基于UDP协议的网络编程

前面介绍的socket编程都是基于TCP协议的,现在来看下基于UDP协议的编程,TCP和UDP的区别在上一章已经有过介绍。

UDP协议的主要作用就是完成网络数据流和数据报之间的转换-----在信息的发送端,UDP协议将网络数据流封装到数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。

1、首先在UDP网络编程中没有服务器端和客户端这种说法,两个socket之间没有虚拟链路,只是接收和发送数据报文而已。

2、这里面有两个重要的类:DatagramSocket 和DatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该包中的包含的信息从一台机器        路由到另一台机器。

3、DatagramSocket 的两个构造函数:

DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。

DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。

在我们下面的DEMO中,UDPServerTest类中先发送数据报,使用的是套接字的无参构造器,而UDPClientTest类中先接收数据报,必须监听某一个端口,所以使用的是套接字的有参构造器。

4、DatagramPacket:创建的时候分为接收和发送两种

DatagramPacket(byte[] buf, int length):用来接收长度为 length 的数据包。

DatagramPacket(byte[] buf, int length, InetAddress address, int port):用来将长度为 length 的包发送到指定主机上的指定端口号。

1 public classUDPServerTest2 {3 public static void main(String[] args) throwsIOException4 {5 DatagramSocket ds = newDatagramSocket();6 String str = "hello world";7 //构造用于发送的数据包,指定主机和端口号

8 DatagramPacket packet = newDatagramPacket(str.getBytes(),9 str.length(), InetAddress.getByName("localhost"), 5555);10 ds.send(packet);11

12 //读取从客户端发送过来的响应

13 byte[] buffer = new byte[1024];14 DatagramPacket packet2 = newDatagramPacket(buffer,buffer.length);15 ds.receive(packet2);16 String str2 = new String(buffer,0,packet2.getLength());17 System.out.println(str2);18 ds.close();19 }20 }

public classUDPClientTest

{public static void main(String[] args) throwsException

{

DatagramSocket ds= new DatagramSocket(5555);byte[] buffer = new byte[1024];

DatagramPacket packet= newDatagramPacket(buffer, buffer.length);

ds.receive(packet);

String str= new String(buffer, 0, packet.getLength());

System.out.println(str);//接收到数据包之后,客户端返回响应回去

String str2 = "welcome";

DatagramPacket packet2= newDatagramPacket(str2.getBytes(), str2

.length(), packet.getAddress(), packet.getPort());

ds.send(packet2);

ds.close();

}

}

执行过程:

14e7e617e2f4077e9ca4a90c9eee6e40.png

348ba78d21a8e0b1b65e4e90189decfd.png

1、上面的程序中,第一步是服务器端(暂且以这种叫法来区分这两个类)创建一个UDP套接字,没有指定端口,使用的是系统分配的端口。然后构建了一个数据包,包中指定      了目标机器的ip和端口号。

2、作为客户端,创建了一个UDP套接字,并且绑定了端口,如果想要接收到服务端发送过来的报文,绑定的端口必须和服务器端发送的包中指定的端口一致。

3、客户端打印了包中的内容之后,想要返回一些内容回去。这个时候,服务器端的ip和端口号可以从之前发送过来的数据包中获取。

DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2.length(), packet.getAddress(), packet.getPort());

4、在服务器接收数据包的时候,已经不需要再像客户端创建套接字一样去绑定端口了,因为目前监听的端口和客户端发送的包中指定的端口是一样的。

5、打印看下服务器端的ip和监听的端口号:

serverIp =/127.0.0.1;serverPort=62965

6、其中DatagramSocket的receive(DatagramPacket p)方法在接收到数据包前一直阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值