网络编程中,两个程序通过一个双向的通信连接实现数据的交换,这连接的一端称为一个socket。
socket通信原理:OSI七层模型、TCP/IP五层模型
OSI模型:应用层、表示层、会话层、传输层、网络层、数据链接层、物理层
TCP/IP五层模型:应用层(相当于OSI表示层、会话层)、传输层、网络层、数据链接层、物理层
在这七个层级中,socket属于传输层,基于TCP/IP协议下的socket通信。socket是基于应用服务与TCP/IP通信之间的一个抽象,socket本质是编程接口(API),它将TCP/IP协议的通信逻辑进行封装。
如何实现客户端到服务端的通信。而使用Socket通信步骤主要分为以下几步
- 先建立服务端的ServerSocket与客户端的Socket
- 打开连接到Socket输入、输出流
- 根据协议进行读写操作(如服务端建立IO输入流读取客户端发送过来的数据)
- 关闭资源
网络通信要素:IP地址、端口号、传输协议。
通信基本概念:
短连接:socket连接,发送数据,接收数据后马上断开
长连接:建立socket连接后,不管是否使用,保持连接
半包:接收发没有接收到一个完整的包,只接收到包的一部分
粘包:发送方发送的多个包数据到接收方接收时粘成一个包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
分包:出现粘包情况下,需要分包处理或一个数据包被分成多次接收。
Server代码 新建一个继承JFrame的类,在该类声明如下值,其中文本框用于获取输入的信息,用于向服务器发送信息,文本域用于显示连接信息、以及相互发送的信息。
//声明文本框、套接字、输出输入流等成员变量
private JTextField tf_msg; //文本框
private JTextArea ta_area;//文本域
private PrintWriter writer;
private BufferedReader reader;
private ServerSocket serverSocket;
private Socket socket; // 声明Socket对象
窗体类的构造方法:在该Server类的面板上添加一个文本框(JTextField),用于获取发送客户端的信息,再添加一个滚动面板(JScrollPane)控件,而在滚动面板上的视图添加一个文本域控件(JTextArea),用于显示连接信息以及客户端发来的信息。下面代码省略了对窗体的设置以及添加滚动面板、文本框等一些控件的代码。为文本框添加事件,将文本框的内容写入到流后,在文本域中显示。
public Server() {
super();
//省略了设置窗体、内容面板添加控件部分代码
tf_msg.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
writer.println(tf_msg.getText()); // 将文本框中信息写入流
ta_area.append("服务器:" + tf_msg.getText() + "\n");
tf_msg.setText(""); // 将文本框清空
}
});
tf_msg.setPreferredSize(new Dimension(220, 25));
panel.add(tf_msg);
}
在该类编写createrServerSocket()方法,创建服务端套接字以及监听客户端程序、创建向客户端发送信息的输出流对象以及接受客户端发过来的输入流对象
public void createrServerSocket() {
try {
// 实例化Socket对象
serverSocket = new ServerSocket(1978);
ta_area.append("服务器套接字创建成功\n");
while (true) { // 如果套接字是连接状态输出信息
ta_area.append("等待客户端连接......\n");
// 实例化Socket对象
socket = serverSocket.accept();
// 实例化BufferedReader对象
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
writer = new PrintWriter(socket.getOutputStream(), true);
// 调用方法,获取客户端发送过来的信息
getClientMsg ();
}
} catch (Exception e) {
e.printStackTrace();
}
}
下面方法用于接收客户端发送过来的信息,以及关闭输入流和套接字对象。
private void getClientMsg() {
try {
while (true) { // 套接字是连接状态
String line = reader.readLine();
if (line != null)
ta_area.append("客户端:" + line + "\n"); }
} catch (Exception e) {
ta_area.append("客户端已退出或失去连接\n"); /
} finally {
//省略了关闭输出、输入流以及套接字对象的代码
}
}
Client(客户端)关键代码
下面方法用于创建套接字对象、输入流和输出流对象以及在文本域上显示接收服务器发送过来的信息。实例化套接字对象的第一个参数为连接服务器的主机名或IP,第二个为连接到服务器的端口号
private void connectSocket() { // 连接套接字方法
ta_info.append("正在连接.....\n"); // 文本域中信息信息
try {
socket = new Socket("192.168.137.95", 1978); // 实例化Socket对象
while (true) {
writer = new PrintWriter(socket.getOutputStream(), true);
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream())); // 实例化BufferedReader对象
ta_info.append("连接成功\n"); // 文本域中提示信息
getClientInfo();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// main方法 显示窗体 调用createrServerSocket方法
public static void main(String[] args) {
//实例化该类对象
Server frame = new Server();
//设置窗体可见
frame.setVisible(true);
//调用方法
frame.createrServerSocket();
}
下面方法用于接收服务端发送的信息,以及关闭套接字对象、输入输出流
private void getClientInfo() {
try {
while (true) { //套接字是连接状态
if (reader != null) {
String line = reader.readLine();
if (line != null)
ta_info.append("服务器:" + line + "\n");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//省略了关闭输出、输入流以及套接字对象的代码 }
}
由于篇幅有限,省略了Client窗体类的构造方法以及主方法,该方法跟服务端的大同小异,都是添加内容面板控件,在该控件上添加文本框等控件,其中需要为文本框添加事件,向服务端发送信息。
运行Server类以及Client类的Main方法,注:需启动服务端先再启动客户端 效果如下
总结:使用Socket通信主要通过Socket类的getInputStream()方法获得输入流对象,获取到输入流对象后将其转换为BufferedReader对象并读取接收到的信息,使用getOutputStream()方法获取到输出流对象,创建PrintWriter对象发送信息。
getInputStream()方法:通过Socket类的getInputStream方法,可以获取到输入流对象
该方法返回值:返回该套接字读取字节的输入流,用于接收对方发送的信息
getOutputStream()方法:通过Socket类的getOutputStream方法获取到输出流对象
返回值:将字节写入此套接字的输出流,用于向对方发送信息。