Socket套接字计算机网络通信的基本技术之一。大多数基于网络的软件,如浏览器、即时通讯工具(QQ)或者P2P下载(迅雷)都是基于Socket实现的。本文介绍了Socket的一些基础知识点,对UDP协议没有过多的涉及,简要分析了Socket和HTTP,最后有一个类似于QQ多人聊天的小示例可以下载 示例源代码 查看。
在了解Socket之前,首先要了解什么是客户端/服务器(client/server)模式。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如ORACLE、SYBASE、InfORMix或 SQL Server。客户端需要安装专用的客户端软件。客户端与服务器要进行通信,提供它们之间互相通信的接口就是Socket,所以说Socket本身并不是一种协议,而是基于一组协议设计而提供的对外操作的接口。
Socket可以说是对TCP/IP协议的封装和应用(程序员层面上),但是它也适用于其它协议如UDP协议,Socket 是一种应用接口, TCP/IP 是网络传输协议,虽然接口相同, 但是不同的协议会有不同的服务性质。创建Socket 连接时,可以指定使用的传输层协议,Socket 可以支持不同的传输层协议(TCP 或UDP ),当使用TCP 协议进行连接时,该Socket 连接就是一个TCP 连接。因此也可以说Socket跟TCP/IP协议没有必然的联系。Socket的出现只是可以更方便的使用TCP/IP 协议栈而已。
Java语言中Socket通信机制采用了IO流操作模型。首先通信的双方,客户端和服务器需要建立Socket连接;之后双方都有各自的Socket对象。该Socket对象包含两个流:一个是输入流InputStream,其作用是接收数据;另一个是输出流OutputStream,作用是向外发送数据。
Java为TCP协议提供了两个类:Socket类和ServerSocket类。一个Socket实例代表了TCP连接的一端。一个TCP连接(TCP connection)是一条抽象的双向信道,两端分别由IP地址和端口号确定。在开始通信之前,要建立一个TCP连接,这需要先由客户端TCP向服务器端TCP发送连接请求。ServerSocket实例则监听TCP连接请求,并为每个请求创建新的Socket实例。也就是说,服务器端要同时处理ServerSocket实例和Socket实例,而客户端只需要使用Socket实例。
网络七层协议物理层、数据链路层、网络层、传输层、回话层、表示层和应用层,Socket位于传输层,而HTTP位于应用层。HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即使不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。
由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。
HTTP 是基于Socket 通信的子协议, Socket 收发信息自由, 协议都可由使用者定义。 HTTP 在Socket 基础上做了协议规范, 通信只能按照特定的格式去做, 用户可在HTTP 上做自己的子协议, 如网页浏览,webservice,soap等
Socket常用的构造方法如下:
Socket(InetAddressaddress, int port)throws UnknownHostException, IOException Socket(String host, int port)throws UnknownHostException, IOException
如果失败会抛出IOException错误。如果成功,则返回Socket对象。InetAddress是一个用于记录主机的类,其静态getHostByName(String msg)可以返回一个实例,其静态方法getLocalHost()也可以获得当前主机的IP地址,并返回一个实例。
host一般为客户端的IP地址,port就是服务器端用来监听请求的端口,也即是服务端的端口。在选择端口时,需要注意一点,就是0~1023这些端口都已经被系统预留了。这些端口为一些常用的服务所使用,比如邮件,FTP和HTTP。当你在编写服务器端的代码,选择端口时,请选择一个大于1023的端口。
当Socket实例化完成,就表示与服务器建立好了连接。但是数据的发送和接收还需要从Socket对象中获取输入流InputStream和输出流OutputStream,IO流的获取主要通过以下方法:
public InputStreamgetInputStream()throws IOException public OutputStreamgetOutputStream()throws IOException
由上面可以知道,客户端操作主要包括两个步骤:
public class TCPClient { private static int PORT=1001; private static String HOST="127.0.0.1"; public static void main(String[] args) { String str="hello world!"; try { //建立连接 Socketclient=new Socket(HOST, PORT); OutputStreamos=client.getOutputStream(); InputStreamis=client.getInputStream(); //IO流写入操作 os.write(str.getBytes()); byte buffer[]=new byte[1024]; int len=-1; //IO流的读取操作 while((len=is.read(buffer))!=-1){ System.out.println(new String(buffer, 0, len)); } os.close(); is.close(); }catch (Exception e) { e.printStackTrace(); } } }
上述客户端仅仅表示通信的一方,若要真正完成通信,还需要相应的、能根据客户的请求作出相应的服务器程序。服务器这一端的功能实现就是通过ServerSocket实现的,ServerSocket常用的构造方法如下:
ServerSocket(int port)throws IOException
该构造方法创建一个ServerSocket对象,并绑定到所指定的端口port上面。ServerSocket对象一旦建立,就可以完成其监听端口和等待连接的功能,所采用的实例方法是:
public Socketaccept()throws IOException
上述该方法是一个阻塞方法,
服务端的操作一般分为以下三个步骤:
public class TCPServer { private static int PORT=1001; public static void main(String[] args) { try { //监听端口 ServerSocketserver=new ServerSocket(PORT); while(true){ //接收连接 Socketclient=server.accept(); //流的读写操作 InputStreamis=client.getInputStream(); OutputStreamos=client.getOutputStream(); byte buffer[]=new byte[1024]; int len=-1; while((len=is.read(buffer))!=-1){ System.out.println(new String(buffer, 0, len)); os.write("tcp server".getBytes()); } } } catch (IOException e) { e.printStackTrace(); } } }
在上面的ServerSocket示例代码中,我们采用的是一种顺序处理方式,当有多个客户向服务器发送请求时,服务器是一个一个轮流处理得;若是服务器对每个请求都有较为复杂的处理,就会导致某些客户有较长的等待时间。这非常类似于在 银行排队等候处理个人业务,所排的队伍越长,则等待的时间越长,银行的服务窗口可类比于服务器。那么如何才能减少排队等候的时间呢,可以多开几个服务窗口,相对应的,我们服务器采用多线程处理,为每一个客户端分配一个子线程进行单独处理,由该线程完成客户端的处理工作。
public class TCPServer { private static int PORT = 1001; public static void main(String[] args) { new TCPServer().startUp(); } public void startUp() { try { // 监听端口 ServerSocketserver = new ServerSocket(PORT); while (true) { // 接收连接 Socketclient = server.accept(); // 每一个连接代表了一个子线程 new Thread(new ServerThread(client)).start(); } } catch (IOException e) { e.printStackTrace(); } } private class ServerThread implements Runnable { private InputStreamis; private OutputStreamos; private boolean isRunning = true; public ServerThread(Socketclient) { try { is = client.getInputStream(); os = client.getOutputStream(); } catch (IOException e) { isRunning = false; e.printStackTrace(); } } public void run() { while (isRunning) { byte buffer[] = new byte[1024]; int len = -1; try { while ((len = is.read(buffer)) != -1) { System.out.println(new String(buffer, 0, len)); os.write("tcp server".getBytes()); } } catch (IOException e) { isRunning = false; e.printStackTrace(); } } } } }
在开发中用到Socket感觉是很高大上的,在Java中有关Socket相关的类都位于java.net包下,sun.*这个包也包含了很多的网络编程相关的类,但是不建议使用这个包下面的API,因为这个包可能会改变,另外这个包不能保证在所有的平台都有包含。
本文还有一个 示例源代码 没有贴出来,一个类似于QQ群的多人聊天代码,可以下载示例源代码查看,对于服务端的处理跟已经贴出来多线程ServerThread差不多,只是在多线程基础上,加入了一个储存ServerThread的集合列表,列表的目的就是为了在进行群发消息的时候可以方面识别出来自己。同样客户端也采用了多线程处理,读取消息和写入消息分别位于不同的子线程中。
Socket编程重点就在于如何避免多个Socket的读写阻塞,将读和写分别放在不同的子线程中是一种处理方式。在JDK1.4版本中引入了NIO,引入了非阻塞socket,可以不用堵塞进行网络操作。当然了也可以借助于第三方框架如Apache MINA包。