下面的分析主要是参阅了 计算机网络(谢希仁第7版) 进行总结的,从 系统调用--->应用编程接口API--->套接字接口 来分析Socket的来龙去脉,当然维基百科上也有对Socket的详细解释
大多数操作系统使用 系统调用 的机制在应用程序和操作系统之间传递控制权。对程序员来说,系统调用和一般程序设计中的函数调用非常相似
当某个应用进程启动系统调用时,控制权就从应用进程传递给了系统调用接口,此接口再将控制权传递给计算机的操作系统。操作系统将此调用转给某个内部过程,并执行所请求的操作。内部过程一旦执行完毕,控制权就又通过系统调用接口返回给应用进程。
系统调用接口实际上就是应用进程的控制权和操作系统的控制权进行转换的一个接口,即 应用编程接口API
由于TCP/IP协议族被设计成能够运行在多种操作系统的环境下,TCP/IP标准允许系统设计者能够选择有关API的具体实现细节。
目前,可供应用程序使用TCP/IP的应用编程接口API的最著名的是 套接字接口
当应用进程(客户或者服务器)需要使用网络进行通信时,必须首先发出socket系统调用,请求操作系统为其创建一个"套接字"。这个调用的实际效果是请求操作系统把网络通信所需要的一些 系统资源(存储器空间,CPU时间,网络带宽等) 分配给该应用进程。
操作系统为这些资源的总和用一个叫做 套接字描述符(socket descriptor)的号码(小的整数) 来表示,然后把这个套接字描述符返回给应用进程。
由图可以看出Socket通信与TCP/IP协议是分不开的,要使主机A和主机B能够通信,必须建立Socket连接,建立Socket连接必须通过底层TCP/IP协议来建立TCP连接。
上面就提到,Socket通信与TCP/IP协议是紧密相关的,关于Socket编程通信我们有两种协议可以选择,那就是常见的TCP协议和UDP协议
UDP协议是一种无连接的协议,也称为数据报协议。每次发送数据报时,需要同时发送本机的socket描述符(就是上面所说的套接字描述符)和接收端的socket描述符。所以,每次通信都要发送额外的数据。
TCP协议是一种有连接的协议,使用应用程序之前,必须先建立TCP连接。所以每次在进行通信之前那,我们需要先建立Socket连接,一个socket作为服务端监听请求,一个socket作为客户端进行连接请求。只有双方建立连接好以后,双方才可以通信。
简单分析两者的区别:
说到这,至于选择哪种协议,还是取决于你的使用场景,当然目前见得比较多就是基于TCP协议的Socket通信。当然一些实时性较高的一些服务,局域网的一些服务用UDP的多一些。
socket编程总的来说分为3步:建立连接,数据传送,连接释放。当然服务端程序和客户端程序具体步骤有些区别,如上图所示。
这里我们用java.net包下的ServerSocket类(主要用于服务端)和Socket类(用于建立连接)来实现一个Socket通信实例
package com.pjmike.Socket; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.sql.SQLOutput; /** * 服务端 * * @author pjmike * @create 2018-08-12 17:43 */ public class Server { private ServerSocket serverSocket = null; private Socket socket = null; private DataInputStream input = null; public Server(int port) { try { //绑定端口 System.out.println("bind port ..."); serverSocket = new ServerSocket(port); System.out.println("Server started and waiting a client .."); //调用accept()方法,提取连接请求 socket = serverSocket.accept(); //一般都是以字节传输 input = new DataInputStream(new BufferedInputStream(socket.getInputStream())); String line = ""; while (!line.equals("exit")) { try { //readUTF()方法需要读取writeUTF()写过来的数据 line = input.readUTF(); System.out.println("recd: " + line); } catch (IOException o) { o.printStackTrace(); } } //关闭连接 System.out.println("connection closed ..."); input.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Server server = new Server(5000); } } 复制代码
package com.pjmike.Socket; import java.io.*; import java.net.Socket; import java.nio.Buffer; /** * 客户端 * * @author pjmike * @create 2018-08-12 17:52 */ public class Client { private Socket socket = null; private DataOutputStream output = null; private BufferedReader input = null; public Client(String address, int port) { try { //建立连接 socket = new Socket(address, port); System.out.println("Connected ..."); //从控制台输入信息 input = new BufferedReader(new InputStreamReader(System.in)); output = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } String line = ""; while (!line.equals("exit")) { try { line = input.readLine(); System.out.println("客户端输入的是: "+line); output.writeUTF(line); } catch (IOException e) { e.printStackTrace(); } } try { input.close(); socket.close(); output.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Client client = new Client("localhost", 5000); } } 复制代码
Connected ... hello world 客户端输入的是: hello world nihao 客户端输入的是: nihao exit 客户端输入的是: exit 复制代码
bind port ... Server started and waiting a client .. recd: hello world recd: nihao recd: exit connection closed ... 复制代码
当然有时我们也需要多个客户端连接到同一个服务端,这也不是什么难事,采用多线程的方式,让服务器循环调用accept()方法,每收到一个客户端请求就开启一个线程来进行处理。
实际上,Socket就是一种进程间的通信机制,在Java Socket编程中,也是分三步走:建立通信链路,数据传输,链路关闭。而网络编程也是和Java I/O操作紧密结合在一起的,熟悉I/O操作也是必不可少的。