原文地址:http://blog.csdn.net/u0108184...
一、基础知识准备
在正式给大家介绍自定义协议之前,我们先对网络传输和协议解析的相关知识点做一个基本的介绍,尽管这些知识点我们在学校里学过,但难免会有所遗忘,这里先做一个简单的介绍,以便对后文的内容理解更加顺畅。
1. 网络七层协议
OSI的7层从上到下分别是:7 应用层、 6 表示层、 5 会话层、 4 传输层、 3 网络层、 2 数据链路层、 1 物理层;其中高层(即7、6、5、4层)定义了应用程序的功能,下面3层(即3、2、1层)主要面向通过网络的端到端的数据流。应用层常见的协议有:HTTP、FTP、SMTP等;常见的传输层有:TCP、UDP。本文主要是基于TCP自定义协议实现客户端与服务端的长连接。
2. Socket
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口,通常也称作"套接字"。套接字之间的连接过程可以分为三个步骤:客户端请求,服务端回复收到,客户端收到服务端的回复,即三次握手。连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。
3. 位(bit)、字节(byte)
“位(bit)”是电子计算机中最小的数据单位。每一位的状态只能是0或1;“字节(Byte)”由8个二进制位构成(即1byte=8bit),它是存储空间的基本计量单位,它能表示到数值范围为0到255(即2的8次方减1);
4. 算术移位运算(符号位不变,低位补0)
左移运算:1<<2,1的二进制位是1,向左移两位是100,转为十进制数即为4,所以1<<2的运算结果是4;
右移运算:7>>2,7的二进制位是111,向右移两位是1,所以7>>2的运算结果是1 。
5. Java中各类型占字节数
byte 8位,1个字节
boolean 8位,1个字节
char 16位,2个字节
short 16位,2个字节
int 32位,4个字节
float 32位,4个字节
double 64位,8个字节
long 64位,8个字节
6. Java中socket相关函数
-
Socket构造函数
Socket(InetAddress address, int port)throws UnknownHostException, IOException
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
Socket(String host, int port)throws UnknownHostException, IOException
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
-
还可以通过以下方式生成socket:
SocketFactory.getDefault().createSocket(String address, String port) throws ConnectException
-
Socket方法
getInetAddress(); // 远程服务端的IP地址
getPort(); // 远程服务端的端口
getLocalAddress(); // 本地客户端的IP地址
getLocalPort(); // 本地客户端的端口
getInputStream(); // 获得输入流
getOutStream(); // 获得输出流
-
Socket状态
isClosed(); // 连接是否已关闭,若关闭,返回true;否则返回false
isConnect(); // 如果曾经连接过,返回true;否则返回false
isBound(); // 如果Socket已经与本地一个端口绑定,返回true;否则返回false
-
判断Socket的状态是否处于连接中
boolean isConnected = socket.isConnected() && !socket.isClosed(); // 判断当前是否处于连接
-
ServerSocket构造函数
ServerSocket()throws IOException
ServerSocket(int port)throws IOException
ServerSocket(int port, int backlog)throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException
-
服务端接收客户端的连接请求:
Socket socket = serverSocket.accept();
7. Java中常见流操作类
-
输入流
-
InputStream
抽象类,描述流的输入
-
ByteArrayInputStream
从字节数组读取的输入流
-
BufferedInputStream
缓冲输入流
-
FileInputStream
从文件读入的输入流
-
ObjectInputStream
对象输入流(所读写的对象必须实现Serializable接口)
-
DataInputStream
包含了读取Java标准数据类型的输入流
-
-
输出流
-
OutputStream
抽象类,描述流的输入
-
ByteArrayOutputStream
写入字节数组的输出流
-
BufferedOutputStream
缓冲输出流
-
FileOutputStream
写入文件的输出流
-
ObjectOutputStream
对象输出流(所读写的对象必须实现Serializable接口)
-
DataOutputStream
包含了写Java标准数据类型的输出流
-
二、一个简单的socket连接例子
注:先运行服务端代码的main函数,再运行客户端代码的main函数,即可看到打印连接成功
1. 客户端
import java.net.Socket;
/**
* Created by meishan on 16/12/1.
*/
public class Client {
public static void main(String[] args) throws Exception {
boolean isConnected;
String host = "127.0.0.1";
int port = 1122;
Socket socket = null;
try {
socket = SocketFactory.getDefault().createSocket(host, port);
isConnected = true;
System.out.println("连接成功!");
} catch (ConnectException e) {
isConnected = false;
e.printStackTrace();
System.out.println("连接失败!");
}
if (!isConnected) {
return;
}
Thread.sleep(5000);
socket.close();
System.out.println("断开连接!");
}
}
2. 服务端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by meishan on 16/12/1.
*/
public class Server {
private int port = 1122;
private ServerSocket serverSocket;
public Server() throws Exception {
serverSocket = new ServerSocket(port, 3);//显式设置连接请求队列的长度为3
System.out.println("服务器启动!");
}
public void service() {
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
System.out.println("New connection accepted " + socket.getInetAddress() + ":" + socket.getPort());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception {
Server server = new Server();
Thread.sleep(3000);
server.service();
}
}
三、一个简单的自定义协议例子
例子中,数据包的定义:消息对象=包类型+包长度+消息内容
包类型 byte 型
包长度 int 型
消息内容 byte[] 型
1. 客户端
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* Created by meishan on 16/12/1.
*/
public class Client {
public static void main(String[] args) {
try {
Socket client = new Socket("127.0.0.1", 9091);
OutputStream out = client.getOutputStream();
DataOutputStream outs = new DataOutputStream(out);
while (true) {
Scanner scaner = new Scanner(System.in);
genProtocol(outs, scaner.next());
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 构造协议
*
* @param out
* @param msg
* @throws IOException
*/
private static void genProtocol(DataOutputStream out, String msg) throws IOException {
int type = 1; //消息类型
byte[] bytes = msg.getBytes(); //消息内容
int totalLen = 1 + 4 + bytes.length; //消息长度
out.writeByte(type); //写入消息类型
out.writeInt(totalLen); //写入消息长度
out.write(bytes); //写入消息内容
out.flush();
}
}
2. 服务端
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by meishan on 16/12/1.
*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(9091);
while (true) {
Socket client = server.accept();
System.out.println("客户端" + client.getRemoteSocketAddress() + "连接成功");
parseProtocol(client);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 消息解析
*
* @param client
* @throws IOException
*/
private static void parseProtocol(Socket client) throws IOException {
InputStream is = client.getInputStream();
DataInputStream dis = new DataInputStream(is); //读取Java标准数据类型的输入流
//协议解析
while (true) {
byte type = dis.readByte(); //读取消息类型
int totalLen = dis.readInt(); //读取消息长度
byte[] data = new byte[totalLen - 4 - 1]; //定义存放消息内容的字节数组
dis.readFully(data); //读取消息内容
String msg = new String(data); //消息内容
System.out.println("接收消息类型" + type);
System.out.println("接收消息长度" + totalLen);
System.out.println("发来的内容是:" + msg);
}
}
}
四、总结
本文简单介绍了socket通信和自定义协议的相关知识点,为后续的深入做一些准备工作,下一篇文章《基于Java Socket的自定义协议,实现Android与服务器的长连接(二)》将通过一个实例来详细讲解自定义协议实现长连接通信。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。