Socket通信
2018-12-25 本文已影响356人
若兮缘
Socket通信简介
TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据
基于TCP协议实现网络通信的类
— 客户端的Socket
类
— 服务器端的ServerSocket
类
通讯模型
![](https://img.haomeiwen.com/i14795543/2d30a2b3d91b49b8.jpg)
Socket通信实现步骤
- 创建ServerSocket和Socket
- 打开连接到Socket的输入/输出流
- 按照协议对Socket进行读/写操作
- 关闭输入输出流、关闭Socket
ServerSocket类
此类实现服务器套接字。服务器套接字等待请求通过网络传入并进行相关操作
构造方法
ServerSocket(int port)
:创建绑定到特定端口的服务器套接字
常用方法
![](https://img.haomeiwen.com/i14795543/9c71a6d6f0793f2e.png)
Socket类
此类实现客户端套接字,套接字是两台机器间通信的端点
构造方法
Socket(String host, int port)
:创建一个套接字并将其连接到指定主机上的指定端口号
常用方法
![](https://img.haomeiwen.com/i14795543/fd834d852fb09a8f.png)
DatagramPacket类
此类表示数据报包,数据报包用来实现无连接包投递服务
构造方法
![](https://img.haomeiwen.com/i14795543/7f9306c4bde9d5a2.png)
常用方法
![](https://img.haomeiwen.com/i14795543/123e3e2fd69d5e4f.png)
DatagramSocket类
此类表示用来发送和接收数据报包的套接字,数据报套接字是包投递服务的发送或接收点
构造方法
![](https://img.haomeiwen.com/i14795543/0692aacebd65d8a3.png)
常用方法
![](https://img.haomeiwen.com/i14795543/39e56cafbcbbb8e9.png)
基于TCP的socket通信
服务端
- 创建
ServerSocket
对象,绑定监听端口 - 通过
accept()
方法监听客户端请求 - 连接建立后,通过输入流读取客户端发送的请求信息
- 通过输出流向客户端发送响应信息
- 关闭相关资源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 基于tcp协议的socket通信,实现用户登录
* 服务器端
*/
public class Server {
public static void main(String[] args) {
try {
//1.创建服务器端Socket,指定绑定的端口
ServerSocket server = new ServerSocket(8888);
//2.调用accept()方法开始监听,等待客户端的连接
System.out.println("***服务器启动,等待客户端连接***");
Socket socket = server.accept();
//3.获取输入流,并读取客户端信息
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));//转换为缓冲字符流
String info = null;
while((info = br.readLine()) != null){//循环读取客户端信息
System.out.println("我是服务器,客户端说:"+info);
}
//此处必须关闭输入流
socket.shutdownInput();
//4.获取输出流,响应客户端的请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);//包装为打印流
pw.write("欢迎您!");
pw.flush(); //将缓冲输出
socket.shutdownOutput();
//5.关闭资源
socket.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
- 创建
Socket
对象,指明需要连接的服务器的地址和端口号 - 连接建立后,通过输出流向服务器端发送请求信息
- 通过输入流获取服务器响应的信息
- 关闭相关资源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 基于TCP的socket通信,实现用户登录
* 客户端
*/
public class Client {
public static void main(String[] args) {
try {
// 1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("127.0.0.1", 8888);
// 2.获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("用户名:alice;密码:123456");
pw.flush();
// 此处必须关闭输出流
//注意不能使用os.close()或者pw.close()方法关闭流,会导致socket也被关闭
socket.shutdownOutput();
// 3.获取输入流,并读取服务器端的响应信息
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器说:" + info);
}
socket.shutdownInput();
// 4.关闭资源
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
首先启动服务端,然后启动客户端
# 服务端打印内容
***服务器启动,等待客户端连接***
我是服务器,客户端说:用户名:alice;密码:123456
# 客户端打印内容
我是客户端,服务器说:欢迎您!
多线程服务器
应用多线程来实现服务器与多客户端之间的通信基本步骤
- 服务器端创建
ServerSocket
,循环调用accept()
等待客户端连接 - 客户端创建一个
socket
并请求和服务器端连接 - 服务器端接受客户端请求,创建
socket
与该客户建立专线连接 - 建立连接的两个
socket
在一个单独的线程上对话 - 服务器端继续等待新的连接
创建服务端线程处理类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务器线程处理类
*/
public class ServerThread implements Runnable{
// 和本线程相关的Socket
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
//线程执行的操作,响应客户端的请求
public void run() {
try {
//获取输入流,并读取客户端信息
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));//转换为缓冲字符流
String info = null;
while((info = br.readLine()) != null){//循环读取客户端信息
System.out.println("我是服务器,客户端说:"+info);
}
//此处必须关闭输入流
socket.shutdownInput();
//获取输出流,响应客户端的请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);//包装为打印流
pw.write("欢迎您!");
pw.flush();//将缓冲输出
socket.shutdownOutput();
//关闭资源
socket.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null)
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端改写
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
* 基于tcp协议的socket通信,实现用户登录
* 服务器端多线程实现
*/
public class Server {
public static void main(String[] args) {
try {
//创建服务器端Socket,指定绑定的端口
ServerSocket server = new ServerSocket(8888);
Socket socket = null;
//记录计数连接过服务器的客户端数量
int count = 0;
System.out.println("***服务器启动,等待客户端连接***");
//循环监听等待客户端的连接
while(true){
//调用accept()方法开始监听,等待客户端的连接
socket = server.accept();
//创建一个新的线程
ServerThread serverThread = new ServerThread(socket);
Thread thread = new Thread(serverThread);
thread.setPriority(4); //设置线程优先级,范围为[1-10],默认值为5
//启动线程
thread.start();
count++;//统计数量
System.out.println("连接过服务器端的客户端的数量:"+count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端的IP:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
首先启动服务端,然后启动一个客户端,修改客户端写入的内容,再次启动一个客户端
# 服务端打印内容
***服务器启动,等待客户端连接***
连接过服务器端的客户端的数量:1
当前客户端的IP:127.0.0.1
我是服务器,客户端说:用户名:alice;密码:123456
连接过服务器端的客户端的数量:2
当前客户端的IP:127.0.0.1
我是服务器,客户端说:用户名:tom;密码:555666
基于UDP的socket通信
UDP协议(用户数据报协议)是无连接、不可靠的、无序的,UDP协议以数据报作为数据传输的载体
进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram)
,在数据报中指明数据所要到达的Socket
(主机地址和端口号),然后再将数据报发送出去
服务器端
- 创建
DatagramSocket
,指定端口号 - 创建
DatagramPacket
- 接收客户端发送的数据信息
- 读取数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 服务器端,实现基于UDP的用户登陆
*/
public class UDPServer {
public static void main(String[] args) throws IOException {
/*********接收客户端发送的数据*********/
// 1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(8800);
// 2.创建数据报,用于接收客户端发送的数据
byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3.接收客户端发送的数据
System.out.println("***服务器端已经启动,等待客户端发送数据***");
socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
// 4.读取数据
String info = new String(packet.getData(), 0, packet.getLength());
System.out.println("我是服务器,客户端说:" + info);
/*********向客户端响应数据*********/
// 1.定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "欢迎您!".getBytes();
// 2.创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
// 3.响应客户端
socket.send(packet2);
// 4.关闭资源
socket.close();
}
}
客户端
- 定义发送信息
- 创建
DatagramPacket
,包含将要发送的信息 - 创建
DatagramSocket
- 发送数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 客户端,实现基于UDP的用户登陆
*/
public class UDPClient {
public static void main(String[] args) throws IOException {
/*********向服务器端发送数据*********/
// 1.定义服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin;密码:123".getBytes();
// 2.创建数据报,包含发送的数据信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
// 3.创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
// 4.向服务器端发送数据报
socket.send(packet);
/*********接收服务器端响应的数据*********/
// 1.创建数据报,用于接收服务器端响应的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
// 2.接收服务器响应的数据
socket.receive(packet2);
// 3.读取数据
String reply = new String(data2, 0, packet2.getLength());
System.out.println("我是客户端,服务器说:" + reply);
// 4.关闭资源
socket.close();
}
}
运行结果
首先启动服务端,然后启动客户端
# 服务端打印内容
***服务器端已经启动,等待客户端发送数据***
我是服务器,客户端说:用户名:admin;密码:123
# 客户端打印内容
我是客户端,服务器说:欢迎您!
Socket总结
多线程优先级
在使用多线程处理多客户端通信时,未设置优先级时可能会导致运行时速度非常慢,可以降低优先级
![](https://img.haomeiwen.com/i14795543/65c81aa1aafed2c9.png)
输入和输出流的关闭
- 在同时使用输入和输出流时,
socket
只能接受一个流,要么输入要么输出。完成输入或输出流之后必须关闭(调用socket.shutdownInput()
或socket.shutdownInput()
)让下一个流进来,此时socket
仍然是连接状态。 - 对于同一个
socket
,如果直接关闭输入或者输出流(调用close()
方法),则与该输入或者输出流关联的socket
也会被关闭,所以一般不用关闭流,直接关闭socket
即可。
![](https://img.haomeiwen.com/i14795543/530528b7a5021da1.png)
使用TCP通信传输对象
真实的开发中都是以对象作为传输数据,可以使用ObjectOutputStream
和 ObjectInputStream
完成对象传输
1.定义User类,实现序列化接口
public class User implements Serializable{
private String username;
private String password;
//省略getter和setter
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
2.修改Client发送逻辑
![](https://img.haomeiwen.com/i14795543/1015daaedd5d2bf6.png)
3.修改ServerThread处理逻辑
![](https://img.haomeiwen.com/i14795543/9d69487330b2cd09.png)
Socket编程传递文件
这里简单演示服务器向客户端发送文件
1.修改ServerThread处理逻辑
![](https://img.haomeiwen.com/i14795543/f750f36b4af9947c.png)
2.修改Client逻辑
![](https://img.haomeiwen.com/i14795543/a08d94dd642c8515.png)
附上代码链接:https://gitee.com/ruoxiyuan/JavaSocket