Socket又称套接字,是链接运行在网络上两个程序间的双向通信的端点。java
服务端:服务器程序将一个套接字绑定到一个特定的端口,并经过此套接字等待和监听客户端的链接请求。编程
客户端:客户端程序根据你服务器所在的主机名和端口号发出链接请求。服务器
二者之间的通讯是经过Socket完成的,咱们能够认为Socket是两个城市之间的交通工具,有了它,就能够在两个城市之间穿梭了。网络
Socket通讯示例socket
主机A的应用程序和主机B的应用程序通讯,必须经过Socket创建链接,而创建Socket必须由底层的TCP/IP协议来创建TCP链接。创建TCP链接须要底层IP协议来寻址网络中的主机。IP地址只能帮助咱们找到目标主机,可是一个主机上面有多个应用程序,如何才能找到咱们须要的应用程序,这个时候就能够经过端口号来指定了。 函数
服务器端:工具
1 public static void main(String[] args) throws IOException 2 { 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 = new PrintStream(os); 13 ps.print("您好,您收到了来自服务端的中秋祝福"); 14 ps.close(); 15 os.close(); 16 socket.close(); 17 } 18 }
执行结果:学习
server startspa
客户端:.net
1 public static void main(String[] args) throws IOException, Exception 2 { 3 Socket socket = new Socket("localhost",8888); 4 InputStream is = socket.getInputStream(); 5 BufferedReader br = new BufferedReader(new InputStreamReader(is)); 6 String str = br.readLine(); 7 System.out.println(str); 8 br.close(); 9 is.close(); 10 socket.close(); 11 }
执行结果:
您好,您收到了来自服务端的中秋祝福
一、上面展现的是一个简易的服务端和客户端通讯的创建过程。
二、咱们经过交互图来详细介绍这个过程:
三、首先在server端,指定端口号建立serverSocket对象,经过serverSocket的accpet方法获取套接字,这个方法的特色是:侦听并接受到此套接字的链接,此方法在链接传入以前一直阻塞。这也就意味着,若是没有客户端链接请求过来,服务端会一致阻塞在这里。
四、后面的代码就是经过套接字socket能够获得输入输出流,到此为止,就是I/O的内容了。
五、在客户端这边,经过指定的服务器主机名和服务器监听的端口号,获得套接字Socket,这个时候就表示服务端和客户端的链接已经创建了,而后经过输入输出流来进行通讯了。
在上面的Demo中,咱们是以行做为通讯的最小数据单位,服务器端也是逐行进行处理的。可是咱们在大多数场景下,通讯的数据单位是多行的,这时候Socket的输出流如何表达输出的数据已经结束?
在IO学习过程当中提到过,如何要表示输出已经结束,则经过关闭输出流来实现,可是在socket中是行不通的,由于关闭socket,会致使没法再从该socket中读取数据了。为了解决这种问题,java提供了两个半关闭的方法:
一、shutdownInput():关闭该Socket的输入流,程序还能够经过该Socket的输出流输出数据。
二、shutdownOutput():关闭该Socket的输出流,程序还能够经过该Socket的输入流读取数据。
若是咱们对同一个Socket实例前后调用shutdownInput和shutdownOutput方法,该Socket实例依然没有被关闭,只是该Socket既不能输出数据,也不能读取数据。
服务器端:
1 ServerSocket ss = new ServerSocket(5555); 2 Socket socket = ss.accept(); 3 PrintStream ps = new PrintStream(socket.getOutputStream()); 4 ps.println("服务器端:开源中国杭州论坛"); 5 ps.println("服务器端:杭州G20峰会"); 6 //关闭输出流,代表输出已经结束 7 socket.shutdownOutput(); 8 //判断该socket是否关闭 9 System.out.println(socket.isClosed()); 10 Scanner scan = new Scanner((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 = new PrintStream(s.getOutputStream()); 11 ps.println("客户端:欢迎参加开源中国论坛"); 12 ps.println("客户端:欢迎参加G20峰会"); 13 is.close(); 14 ps.close(); 15 s.close(); 16
执行结果:
在服务器端程序中能够看到,在输出两段字符串以后,调用了shutdownOutput方法,表示输出已经结束。随即又去判断了socket是否关闭,执行的结果为false,表示socket并未关闭。
可是在调用了这两个半关闭的方法关闭了输出输入流以后,该socket没法再次打开该输出流或者输入流。所以这种场景不适合保持持久通讯状态的交互使用,只适合一站式的通讯协议.例如http协议:客户端链接到服务器以后,开始发送数据,发送完成以后无须再次发送数据,只须要读取服务器响应数据便可,读取数据完毕以后,该socket链接也被关闭了。
前面介绍的socket编程都是基于TCP协议的,如今来看下基于UDP协议的编程,TCP和UDP的区别在上一章已经有过介绍。
UDP协议的主要做用就是完成网络数据流和数据报之间的转换-----在信息的发送端,UDP协议将网络数据流封装到数据报,而后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。
一、首先在UDP网络编程中没有服务器端和客户端这种说法,两个socket之间没有虚拟链路,只是接收和发送数据报文而已。
二、这里面有两个重要的类:DatagramSocket 和DatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该包中的包含的信息从一台机器 路由到另外一台机器。
三、DatagramSocket 的两个构造函数:
DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port):建立数据报套接字并将其绑定到本地主机上的指定端口。
在咱们下面的DEMO中,UDPServerTest类中先发送数据报,使用的是套接字的无参构造器,而UDPClientTest类中先接收数据报,必须监听某一个端口,因此使用的是套接字的有参构造器。
四、DatagramPacket:建立的时候分为接收和发送两种
DatagramPacket(byte[] buf, int length):用来接收长度为
length
的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port):用来将长度为
length
的包发送到指定主机上的指定端口号。
1 public class UDPServerTest 2 { 3 public static void main(String[] args) throws IOException 4 { 5 DatagramSocket ds = new DatagramSocket(); 6 String str = "hello world"; 7 //构造用于发送的数据包,指定主机和端口号 8 DatagramPacket packet = new DatagramPacket(str.getBytes(), 9 str.length(), InetAddress.getByName("localhost"), 5555); 10 ds.send(packet); 11 12 //读取从客户端发送过来的响应 13 byte[] buffer = new byte[1024]; 14 DatagramPacket packet2 = new DatagramPacket(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 class UDPClientTest { public static void main(String[] args) throws Exception { DatagramSocket ds = new DatagramSocket(5555); byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); ds.receive(packet); String str = new String(buffer, 0, packet.getLength()); System.out.println(str); // 接收到数据包以后,客户端返回响应回去 String str2 = "welcome"; DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2 .length(), packet.getAddress(), packet.getPort()); ds.send(packet2); ds.close(); } }
执行过程:
一、上面的程序中,第一步是服务器端(暂且以这种叫法来区分这两个类)建立一个UDP套接字,没有指定端口,使用的是系统分配的端口。而后构建了一个数据包,包中指定 了目标机器的ip和端口号。
二、做为客户端,建立了一个UDP套接字,而且绑定了端口,若是想要接收到服务端发送过来的报文,绑定的端口必须和服务器端发送的包中指定的端口一致。
三、客户端打印了包中的内容以后,想要返回一些内容回去。这个时候,服务器端的ip和端口号能够从以前发送过来的数据包中获取。
DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2.length(), packet.getAddress(), packet.getPort());
四、在服务器接收数据包的时候,已经不须要再像客户端建立套接字同样去绑定端口了,由于目前监听的端口和客户端发送的包中指定的端口是同样的。
五、打印看下服务器端的ip和监听的端口号:
serverIp =/127.0.0.1;serverPort=62965
六、其中DatagramSocket的receive(DatagramPacket p)方法在接收到数据包前一直阻塞。