Java网络编程-初识Socket

来源:互联网 发布:淘宝哪里申请换货 编辑:程序博客网 时间:2024/04/29 03:59

原文:http://www.sunnyang.com/410.html

Socket套接字计算机网络通信的基本技术之一。大多数基于网络的软件,如浏览器、即时通讯工具(QQ)或者P2P下载(迅雷)都是基于Socket实现的。本文介绍了Socket的一些基础知识点,对UDP协议没有过多的涉及,简要分析了Socket和HTTP.

Socket介绍

在了解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区别

网络七层协议物理层、数据链路层、网络层、传输层、回话层、表示层和应用层,Socket位于传输层,而HTTP位于应用层。HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。
在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即使不需要获
得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

HTTP协议手机都支持,Socket不一定(Android对于WebSocket的支持很差)。
HTTP只能是一问一答(即以request/response 的方式连网收发信息), 而Socket可以双向通讯( 定位到某一URL 后, 就可以双方收发信息, 无需request/response) 。
Socket 可能会被防火墙屏蔽, 但HTTP可以穿越防火墙。
HTTP 是基于Socket 通信的子协议, Socket 收发信息自由, 协议都可由使用者定义。 HTTP 在Socket 基础上做了协议规范, 通信只能按照特定的格式去做, 用户可在HTTP 上做自己的子协议, 如网页浏览,webservice,soap等

Socket编程示例

交互过程
这里写图片描述

Socket类

Socket常用的构造方法如下:

Socket(InetAddress address, int port)throws UnknownHostException, IOExceptionSocket(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 InputStream getInputStream()throws IOExceptionpublic OutputStream getOutputStream()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 {            //建立连接            Socket client=new Socket(HOST, PORT);            OutputStream os=client.getOutputStream();            InputStream is=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常用的构造方法如下:

ServerSocket(int port)throws IOException

该构造方法创建一个ServerSocket对象,并绑定到所指定的端口port上面。ServerSocket对象一旦建立,就可以完成其监听端口和等待连接的功能,所采用的实例方法是:

public Socket accept()throws IOException

上述该方法是一个阻塞方法,阻塞的含义是其将一直处于等待状态,直到有连接请求才从方法中返回。方法的返回值是一个Socket对象,服务端就是通过该对象与客户端进行通信的。

服务端的操作一般分为以下三个步骤:

监听端口
接收连接
进行流的读写操作

public class TCPServer {    private static int PORT=1001;    public static void main(String[] args) {        try {            //监听端口            ServerSocket server=new ServerSocket(PORT);            while(true){                //接收连接                Socket client=server.accept();                //流的读写操作                InputStream is=client.getInputStream();                OutputStream os=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

在上面的ServerSocket示例代码中,我们采用的是一种顺序处理方式,当有多个客户向服务器发送请求时,服务器是一个一个轮流处理得;若是服务器对每个请求都有较为复杂的处理,就会导致某些客户有较长的等待时间。这非常类似于在 银行排队等候处理个人业务,所排的队伍越长,则等待的时间越长,银行的服务窗口可类比于服务器。那么如何才能减少排队等候的时间呢,可以多开几个服务窗口,相对应的,我们服务器采用多线程处理,为每一个客户端分配一个子线程进行单独处理,由该线程完成客户端的处理工作。

public class TCPServer {    private static int PORT = 1001;    public static void main(String[] args) {        new TCPServer().startUp();    }    public void startUp() {        try {            // 监听端口            ServerSocket server = new ServerSocket(PORT);            while (true) {                // 接收连接                Socket client = server.accept();                // 每一个连接代表了一个子线程                new Thread(new ServerThread(client)).start();            }        } catch (IOException e) {            e.printStackTrace();        }    }    private class ServerThread implements Runnable {        private InputStream is;        private OutputStream os;        private boolean isRunning = true;        public ServerThread(Socket client) {            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();                }            }        }    }}public class TCPServer {    private static int PORT = 1001;    public static void main(String[] args) {        new TCPServer().startUp();    }    public void startUp() {        try {            // 监听端口            ServerSocket server = new ServerSocket(PORT);            while (true) {                // 接收连接                Socket client = server.accept();                // 每一个连接代表了一个子线程                new Thread(new ServerThread(client)).start();            }        } catch (IOException e) {            e.printStackTrace();        }    }    private class ServerThread implements Runnable {        private InputStream is;        private OutputStream os;        private boolean isRunning = true;        public ServerThread(Socket client) {            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,因为这个包可能会改变,另外这个包不能保证在所有的平台都有包含。

Socket编程重点就在于如何避免多个Socket的读写阻塞,将读和写分别放在不同的子线程中是一种处理方式。在JDK1.4版本中引入了NIO,引入了非阻塞socket,可以不用堵塞进行网络操作。当然了也可以借助于第三方框架如Apache MINA包。

0 0
原创粉丝点击