目录
一、常见的客户端服务端模型
客户端:用户使用的程序。
服务端:给用户提供服务的程序。
整体流程:
- 客户端发送请求到服务端。
- 服务端根据客户端的的请求数据,执行相应的业务处理。
- 服务端返回响应:发送处理业务的处理结果。
- 客户端得到处理结果,展示处理结果。
二、Socket套接字
1、概念
Socket套接字是系统提供用于网络通信的技术,也是TCP/IP协议的网络通信的基本操作单元,并且基于Socket套接字的网络程序开发就是网络编程。
2、分类
a、流套接字
流套接字使用的是TCP协议(传输控制协议),TCP协议有如下特点:
- 有连接。
- 可靠通信。
- 面向字节流传输。
- 全双工。
- 传输数据的大小不受限制。
全双工指的是通信双方可以同时发送数据进行通信,还有半双工通信:通信双方不能同时发送数据,单工通信就是只能由一方一直发送数据。
b、数据报套接字
数据报套接字使用的是UDP协议(用户数据报协议),UDP协议有如下的特点:
- 无连接。
- 不可靠。
- 面向用户数据报传输。
- 全双工。
- 传输数据的大小有限制,一次最多传输64K。
c、原始套接字
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
三、UDP数据报套接字编程
UDP编程具有两个核心类:
DatagramSocket:是UDP版本的Socket对象,用于接收和发送数据报。有如下三个方法:
receive:发送数据。
send:发送数据。
close:释放资源。
DatagramPacket:表示一个UDP数据报,每次发送和接收的都是DatagramPacket对象。
服务器端代码实现:
整体流程:
- 首先定义一个DatagramSocket对象,用于收发数据。
- 定义一个指定端口号的构造方法,在构造方法中完成实例化对象。
- 定义start方法中,循环处理请求:定义DatagramPacket对象接收请求,处理请求后,再定义一个DatagramPacket对象发送数据。
-
public
class
UDPEchoServer {
-
//首先定义一个Socket对象
-
private
DatagramSocket
datagramSocket
=
null;
-
//构造方法:指定一个端口号
-
public
UDPEchoServer
(int port)
throws SocketException {
-
datagramSocket =
new
DatagramSocket(port);
-
}
-
//启动服务器
-
public
void
start
()
throws IOException {
-
System.out.println(
"服务器已经启动!");
-
while(
true){
-
//数据报
-
DatagramPacket
requestPacket
=
new
DatagramPacket(
new
byte[
1024],
1024);
-
//接收数据报
-
datagramSocket.receive(requestPacket);
-
//将接收到的数据报转成字符串
-
String
request
=
new
String(requestPacket.getData(),
0,requestPacket.getLength(),
"UTF-8");
-
//处理数据,返回响应
-
String
response
= process(request);
-
//将响应返回给客户端
-
DatagramPacket
responsePacket
=
new
DatagramPacket(response.getBytes(),
0,response.getBytes().length,
-
requestPacket.getSocketAddress());
-
datagramSocket.send(responsePacket);
-
//展示
-
System.out.println(
"[客户IP"+responsePacket.getAddress()+
"客户端口"+responsePacket.getPort()+
-
"请求"+request+
"响应"+response);
-
}
-
}
-
//简单的回显服务来处理数据
-
public String
process
(String request){
-
return request;
-
}
-
-
public
static
void
main
(String[] args)
throws IOException {
-
UDPEchoServer
udpEchoServer
=
new
UDPEchoServer(
9090);
-
udpEchoServer.start();
-
}
-
}
-
注意:
服务器的端口号一般是指定的,因为在客户端程序中需要指定服务器端的的端口号。
响应服务可以先别的,这里只是简单的回显服务。
客户端代码实现:
整体流程:
- 首先定义一个DatagramSocket对象,用于收发数据。
- 定义一个构造方法指定服务器端的IP地址和端口号,并完成DatagramSocket对象实例。
- 定义start方法,循环发送请求,先定义DatagramPacket对象包装请求,发送到指定的服务器端,再定义一个DatagramPacket对象接收响应处理后的数据。
-
public
class
UDPEchoClient {
-
//定义Socket对象
-
DatagramSocket
datagramSocket
=
null;
-
private
int serverPort;
-
private String serverIP;
-
//构造方法时传入的是服务器端的IP地址和端口号
-
public
UDPEchoClient
(int port,String IP)
throws SocketException {
-
datagramSocket =
new
DatagramSocket();
-
serverPort = port;
-
serverIP = IP;
-
}
-
//启动客户端
-
public
void
start
()
throws IOException {
-
Scanner
sc
=
new
Scanner(System.in);
-
while (
true){
-
System.out.println(
"发送请求>");
-
String
request
= sc.next();
-
//发送UDP请求
-
DatagramPacket
requestPacket
=
new
DatagramPacket(request.getBytes(),request.getBytes().length,
-
InetAddress.getByName(serverIP),serverPort);
-
//发送数据
-
datagramSocket.send(requestPacket);
-
//读取数据并进行解析
-
DatagramPacket
responsePacket
=
new
DatagramPacket(
new
byte[
1024],
1024);
-
datagramSocket.receive(responsePacket);
-
String
response
=
new
String(responsePacket.getData(),
0,responsePacket.getLength(),
"UTF-8");
-
//打印响应结果
-
System.out.println(response);
-
}
-
}
-
-
public
static
void
main
(String[] args)
throws IOException {
-
UDPEchoClient
udpEchoClient
=
new
UDPEchoClient(
2580,
"127.0.0.1");
-
udpEchoClient.start();
-
}
-
}
注意:
客户端程序一般需要系统自动分配,因为你不知道此时那个端口号没有被占用,而且客户端的个数一般多于服务器端的数目。在客户端的构造方法中需要指定服务器端的IP地址和端口号。
在启动程序时,先启动服务器端,再启动应用器端。
例如:将回显服务改变成数据+收到。
-
public
class
UDPEChoServer2
extends
UDPEchoServer {
-
public
UDPEChoServer2
(int port)
throws SocketException {
-
super(port);
-
}
-
public String
process
(String request){
-
return request+
"收到";
-
-
}
-
-
public
static
void
main
(String[] args)
throws IOException {
-
UDPEChoServer2
udpeChoServer2
=
new
UDPEChoServer2(
2580);
-
udpeChoServer2.start();
-
}
-
}
运行结果:
四、TCP数据报套接字编程
TCP编程的核心类:
ServerSocket:是服务器端的流套接字,创建实例对象时,可以绑定指定的端口,
accept方法:用于监听指定端口,有客户端连接的话就会返回一个Socket对象,基于该Socket对象与客户端建立连接,否则就会发生阻塞等待。
Socket:客户端与服务器端建立连接后,该类对象用于通信双方进行收发数据。创建实例对象时要绑定服务器端的IP地址和端口号。
有以下常用方法:
getInetAddress:返回套接字所连接的地址。
getInputStream:返回此套接字的输入流。
getOutputStream:返回此套接字的输出流。
服务器端代码实现:
整体流程:
- 定义一个ServerSocket对象。
- 定义指定端口号的构造方法。
- 定义start方法来启动,创建Socket对象建立连接。
- 定义processConnection方法循环处理请求,先得到请求进行处理之后将结果返回给客户端。
-
public
class
TCPEchoServer {
-
//定义一个服务器对象
-
ServerSocket
serverSocket
=
null;
-
public
TCPEchoServer
(int port)
throws IOException {
-
serverSocket =
new
ServerSocket(port);
-
}
-
//开始
-
public
void
start
()
throws IOException {
-
System.out.println(
"服务器已经启动");
-
while(
true){
-
//利用Socket对象来建立连接
-
Socket
clientSocket
= serverSocket.accept();
-
processConnection(clientSocket);
-
}
-
}
-
-
private
void
processConnection
(Socket clientSocket) {
-
System.out.println(
"[" + clientSocket.getInetAddress().toString() +
" "+clientSocket.getPort()+
"]已经建立连接");
-
try(
InputStream
inputStream
= clientSocket.getInputStream()){
-
try(
OutputStream
outputStream
= clientSocket.getOutputStream()){
-
//读取请求
-
Scanner
sc
=
new
Scanner(inputStream);
-
//循环处理请求
-
while(
true){
-
if(!sc.hasNext()){
-
System.out.println(
"[" + clientSocket.getInetAddress().toString() +
"]已经断开连接");
-
break;
-
}
-
String
request
= sc.next();
-
//对请求做出响应
-
String
response
= process(request);
-
//将响应返回给客户端
-
PrintWriter
printWriter
=
new
PrintWriter(outputStream);
-
printWriter.println(response);
-
//刷新缓存区,使客户端尽快得到响应结果
-
printWriter.flush();
-
}
-
}
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
private String
process
(String request) {
-
return request;
-
}
-
-
public
static
void
main
(String[] args)
throws IOException {
-
TCPEchoServer
tcpEchoServer
=
new
TCPEchoServer(
9087);
-
tcpEchoServer.start();
-
}
-
}
客户端代码实现:
整体流程:
- 定义一个Socket对象。
- 定义一个指定服务器的IP地址和端口号的构造方法,并完成对Socket对象的实例。
- 定义start方法,循环发送请求,然后接收响应。
-
public
class
TCPEchoClint {
-
Socket
socket
=
null;
-
public
TCPEchoClint
(int serverPort,String serverIP)
throws IOException {
-
//与指定的服务器端建立连接
-
socket =
new
Socket(serverIP,serverPort);
-
}
-
//启动
-
public
void
start
(){
-
System.out.println(
"客户端已经连接到服务器端");
-
Scanner
sc
=
new
Scanner(System.in);
-
try(
InputStream
inputStream
= socket.getInputStream()) {
-
try(
OutputStream
outputStream
= socket.getOutputStream()){
-
//循环发送请求
-
while(
true){
-
String
request
= sc.next();
-
//发送请求
-
PrintWriter
printWriter
=
new
PrintWriter(outputStream);
-
printWriter.println(request);
-
printWriter.flush();
-
Scanner
scanner
=
new
Scanner(inputStream);
-
String
response
= scanner.next();
-
System.out.println(
"response:"+response);
-
}
-
}
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
public
static
void
main
(String[] args)
throws IOException {
-
TCPEchoClint
tcpEchoClint
=
new
TCPEchoClint(
9087,
"127.0.0.1");
-
tcpEchoClint.start();
-
}
-
-
}
但是这样实现服务器端代码存在BUG, 当再启动一个客户端就连接不到服务器,为什么呢?
原因分析:在服务器端的start方法中,利用while循环来建立连接,并调用processConnection方法,但是在processConnection方法中又有一个while循环处理请求,这样只要一个客户端请求未结束,别的客户端就连接不到服务器端,这显然不符合实际。
解决方案:在start方法中创建线程,使多个客户端能并发连接到服务器端。
-
public
void
start
()
throws IOException {
-
System.out.println(
"服务器已经启动");
-
while(
true){
-
//利用Socket对象来建立连接
-
Socket
clientSocket
= serverSocket.accept();
-
Thread
t
=
new
Thread(
-
()-> processConnection(clientSocket)
-
) ;
-
t.start();
-
}
-
}
转载:https://blog.csdn.net/m0_62404808/article/details/128772267