您的位置:首页 > 理论基础 > 计算机网络

Java网络编程-初识Socket

2016-04-19 09:42 561 查看
原文: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, 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 InputStream getInputStream()throws IOException
public 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!";

4000
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包。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  网络编程 socket java