作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc。
对于程序员来说,在实际生产过程中,使用最多的莫过于Http和Socket通信两中。Socket相对于Http来说速度快,效率高,所以广受青睐。
说到Socket很让人联想到"套接字"、"长连接"、"TCP/UDP"、"通信"。没错,Socket是套接字的意思,Socket能够实现长连接,所以常常人们所使用的一些即时通信的软件大多数是通过Socket来实现。TCP和UDP是两种形式,TCP是面向连接的,较安全。UDP是无连接的,速度较快。
基本概念理解
1、InetAddress用于标识网络上硬件资源。
// 获取本机的InetAddress实例
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println("计算机名:" + inetAddress.getHostName());
System.out.println("IP地址:" + inetAddress.getHostAddress());
// 获取字节数组形式的IP地址
byte[] bytes = inetAddress.getAddress();
System.out.println("字节数组形式的IP地址:" + Arrays.toString(bytes));
// 直接输出InetAddress对象
System.out.println(inetAddress);
// 根据主机名获取InetAddress实例
// InetAddress address = InetAddress.getByName("SC-201702092057");
// 根据IP地址获取InetAddress实例
InetAddress address = InetAddress.getByName("192.168.155.1");
System.out.println("计算机名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());
2、URL统一资源定位符,通过资源可以读取和写入网络上的数据。
// 创建URL实例
URL baidu = new URL("http://www.baidu.com");
// 根据已存在URL可以创建一个新的URL
URL url = new URL(baidu, "/index.html?username=tom#test");// ?后面表示参数,#后面表示锚点
System.out.println("协议:" + url.getProtocol());
System.out.println("主机:" + url.getHost());
// 如果未指定端口号,则使用默认端口号,此时url.getPort()获取的返回值为-1
System.out.println("端口:" + url.getPort());
System.out.println("文件路径:" + url.getPath());
System.out.println("文件名:" + url.getFile());
System.out.println("相对路径:" + url.getRef());
System.out.println("查询字符串:" + url.getQuery());
/**
* 使用URL获取网络内容
*/
// 通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream is = baidu.openStream();
// 将字节输入流转换成字符输入流(字节流与字符流之间的桥梁)
InputStreamReader isr = new InputStreamReader(is, "utf8");
// 为字符输入流添加缓冲(从字符输入流中读取文本)
BufferedReader br = new BufferedReader(isr);
String data = null;
while ((data = br.readLine()) != null) {
System.out.println("当前行数据:" + data);
}
Socket实现基于TCP通信
Socket通信分为服务端和客户端,在Java语言中,要实现基于TCP的通信需要通过ServerSocket(服务端)和Socket(客户端)。因为TCP在通信过程中采用字节流传输,所以数据的取出和传递都要通过字节流来实现。
1、服务端搭建
服务端的搭建基本上可以分为5步。
(1)、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口。
ServerSocket serverSocket = new ServerSocket(2500);
对于端口号,系统默认取值范围是065535,一般认为01023为系统端口号,所以这里采用>1023的端口号。
(2)、用accept()方法开始监听,等待客户端的连接。
Socket socket = serverSocket.accept();
注意accept方法会使程序处于阻塞状态,所以一般是放在子线程中完成。而接受到的socket就是用于服务端和客户端进行通信的socket。
(3)、获取输入流,并读取客户端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
String info=null;
while((info=br.readLine())!=null){// 循环读取客户端的信息
System.out.println("*******客户端信息:" + info);
}
socket.shutdownInput();// 关闭输入流
获取客户端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。
(4)、获取输出流,响应客户端
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("我是服务端,你好");
pw.flush();// 刷新缓存,将缓存输出
socket.shutdownOutput();// 关闭输出流
响应客户端需要通过获取socket输出流,这里是字节流->打印流。当然也可以直接进行输出。
(5)、关闭资源,最后将以上资源全部关闭即可。
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
到这里一个简单的服务端就构建完成了,上面的过程仅仅是用来说来,使用Socket如何构建一个服务端,实际上ServerSocket在接收链接的过程需要在子线程中完成。
优化代码:
// 1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
// (0~65535,使用1023之后的端口,0~1023一般为系统默认端口)
ServerSocket serverSocket = new ServerSocket(2500);
Socket socket = null;
// 记录客户端的数量
int count = 0;
System.out.println("*****服务端已开启******");
// 循环监听等待客户端的连接
while (true) {
// 调用accept()方法开始监听,等待客户端的连接
socket = serverSocket.accept();
// 创建一个新的线程
TcpServerThread tcpServerThread = new TcpServerThread(socket);
tcpServerThread.setPriority(4);// 设置线程优先级,[0,10],默认5
// 启动线程
tcpServerThread.start();
count++;// 统计客户端的数量
System.out.println("客户端的数量:" + count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端的IP:" + address.getHostAddress());
}
而对于socket的处理,也要放在一个子线程中完成。
public class TcpServerThread extends Thread {
// 和本线程相关的socket
private Socket socket = null;
public TcpServerThread(Socket socket) {
this.socket = socket;
}
// 线程执行的操作,响应客户端的请求
@Override
public void run() {
super.run();
if (socket != null) {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
// 获取输入流,并读取客户端信息
is = socket.getInputStream();
isr = new InputStreamReader(is);// 转换成字符输入流
br = new BufferedReader(isr);// 字符输入流缓冲
String info = null;
while ((info = br.readLine()) != null) {// 循环读取客户端的信息
System.out.println("*******客户端信息:" + info);
}
socket.shutdownInput();// 关闭输入流
// 获取输出流,相应客户端
os = socket.getOutputStream();
pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("我是服务端,你好");
pw.flush();// 刷新缓存,将缓存输出
socket.shutdownOutput();// 关闭输出流
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (pw != null)
pw.close();
if (os != null)
os.close();
if (br != null)
br.close();
if (isr != null)
isr.close();
if (is != null)
is.close();
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2、客户端搭建
客户端的搭建只需要4步
(1)、创建客户端Socket,指定服务端地址和端口
Socket socket = new Socket("localhost", 2500);
这里的服务端地址指的是服务端ip地址,端口是服务端设置的端口号。
(2)、创建输出流,向服务端发送消息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("我是客户端,你好啊");
pw.flush();// 刷新缓存
//pw.close();// 不能关闭输出流,会导致socket也关闭
socket.shutdownOutput();// 关闭输出流
注意一点,输出数据之后不要将流关闭,否则会导致socket也关闭。
(3)、获取输入流,并读取服务端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
String info=null;
while((info=br.readLine()) != null){// 循环读取客户端的信息
System.out.println("*******服务端信息:" + info);
}
socket.shutdownInput();// 关闭输入流
获取服务端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。
(4)、关闭资源,最后将以上资源全部关闭即可。
br.close();
isr.close();
is.close();
pw.close();
os.close();
socket.close();
Socket实现基于UDP通信
UDP通信时无连接的,传输过程中是通过数据包来完成,所以要想实现UDP通信,需要使用到Java中的DatagramSocket和DatagramPacket。DatagramSocket是用来建立连接,而DatagramPacket是用来传递数据。
1、服务端搭建
服务端搭建分为接收和响应客户端两个过程。
首先实现接受客户端发送的数据。
(1)、创建服务端DatagramSocket,指定端口
DatagramSocket datagramSocket = new DatagramSocket(2501);
同样要设置端口号,端口号取>1023并且<65535的整数。
(2)、创建数据报用来接收客户端传递过来的数据
byte[] data = new byte[1024];// 创建字节数组,指定接受数据包大小
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
这里接受数据为数据报,所以要采用DatagramPacket类来实现数据的接收。
(3)、接受客户端发送的数据,receive方法用于接收链接,最后的方式也是放于子线程完成这一过程
datagramSocket.receive(datagramPacket);// 此方法在接收到数据报之前会处于阻塞
(4)、读取数据
String info = new String(data, 0, datagramPacket.getLength());
System.out.println("****客户端数据*****" + info);
其次是响应客户端
(1)、定义客户端地址,端口号,数据
InetAddress inetAddress = datagramPacket.getAddress();
int port = datagramPacket.getPort();
byte[] data2 = "我是服务端,你好".getBytes();
这里就采用了InetAddress来获取客户端的相关信息,例如客户端ip和相关端口号。
(2)、创建数据报,包含发送信息
DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length, inetAddress, port);
此处的DatagramPacket构造方式与上面的构造方法不同,此处还要知道客户端的ip才能准确的发送数据。
(3)、向客户端发送数据报,通过send方法发送数据。
datagramSocket.send(datagramPacket2);
最后关闭资源
datagramSocket.close();
2、客户端搭建
其实客户端的构建与服务端的构建是一个相似的过程,一个逆向的过程。
向服务端发送数据。
// 1、定义服务端地址,端口号,数据
InetAddress inetAddress = InetAddress.getByName("localhost");
int port = 2501;
byte[] data = "我是客户端,你好".getBytes();
// 2、创建数据报,包含发送信息
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, port);
// 3、创建DataGramSocket对象
DatagramSocket datagramSocket = new DatagramSocket();
// 4、向服务端发送数据报
datagramSocket.send(datagramPacket);
接受服务端响应的数据
// 1、创建数据报用来接收服务端传递过来的数据
byte[] data2 = new byte[1024];// 创建字节数组,指定接受数据包大小
DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length);
// 2、接受服务端发送的数据
datagramSocket.receive(datagramPacket2);// 此方法在接收到数据报之前会处于阻塞
// 2、读取数据
String info = new String(data2, 0, datagramPacket2.getLength());
System.out.println("****服务端数据*****" + info);
关闭资源
datagramSocket.close();