认真学完,相信你会有收获
是基于请求-响应的
请求/响应模式:基本上所有网络程序都是请求/响应模式的。请求/响应模式把网络程序分为服务器端程序和客户端程序。
服务器端:永无休止的在运行,等待客户端的请求,然后向客户端做出响应。
客户端:向服务器端发出请求,获得服务器端响应。
举个例子:
相信大多人的电脑上都装了“微信”,而我们安装的就是微信的客户端程序。也就是说客户端通常是多个。当我们登录、聊天时,都是在客户端向服务器端发出请求。而服务器端通常只有一个,他安装在一台或一组计算机上(集群)。
总结服务器/客户端特点:
网络编程结构大致分为两种:B/S和C/S,主要区别是客户端程序的形式不同:
当需要与某台计算机的某个应用程序建立连接时,必须要知道IP和端口号。
网络套接字是两台计算机通信的端点。
可以理解为两部通话的手机。服务器端和客户端各有一个Socket对象,就好像各有一个手机一样,可以互相传输数据。
Socket之间通讯是使用IO流完成的。
每个Socket对象都有输入流和输出流对象,他们好比电话的听筒的话筒。其中输入流对应听筒,输出流对应话筒。
客户端使用OutputStream(话筒)向服务器端发送数据,服务器端使用InputStream(听筒)接收数据。反之服务器端使用OutputStream(话筒)发送数据,客户端使用InputStream(听筒)接收数据。
注意: 一个发送数据一个接收数据,必须是对应的。一方发送了数据,另一个方没有接收,或者一方接收等待中,另一个一直不发。这两种情况都无法完成数据的传输。
创建项目就不带你创建了。我们直接lou代码
代码执行完毕后会发现,卡住了,说明服务端启动成功,在等待客户端的连接。继续编写客户端运行后即可看到客户端发送的消息。
注意:两端获取的输入流和输出流不要关闭,否则会提前导致通信连接关闭
package com.manlu.service;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 漫路
*/
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
//1.创建服务器端 12121表示服务端的端口号
ServerSocket serverSocket = new ServerSocket(12121);
//2.与客户端连接,连接成功将获取socket对象
Socket socket = serverSocket.accept();
System.out.println("与客户端连接成功");
//3.读取客户端本次发送的数据
InputStream is = socket.getInputStream();
byte[] arr = new byte[1024];
int len = is.read(arr);
System.out.println(new String(arr,0,len));
//4.使用完毕,关闭本次通信连接
socket.close();
}
}
package com.manlu.customer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author 漫路
*/
public class ClientDemo1 {
public static void main(String[] args) throws IOException {
//1. 创建客户端,和服务器创建连接
Socket socket = new Socket("127.0.0.1",12121);
//2. 向服务器发送消息
OutputStream os = socket.getOutputStream();
os.write("客户端回应".getBytes("utf-8"));
//3. 关闭本次通信
socket.close();
}
}
记得是先运行服务器端,如果先运行客户端,会报错误,找不到服务器端。
package com.manlu.service;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 漫路
*/
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
//1. 创建服务器端
ServerSocket serverSocket = new ServerSocket(12122);
//2. 等待客户端连接
Socket accept = serverSocket.accept();
//3. 获取客户端ip地址
InetAddress inetAddress = accept.getInetAddress();
System.out.println("客户端信息:"+inetAddress.getHostAddress());
//4. 关闭连接
accept.close();
}
}
package com.manlu.customer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author 漫路
*/
public class ClientDemo2 {
public static void main(String[] args) throws IOException {
//1. 创建客户端
Socket socket = new Socket("127.0.0.1",12122);
//2. 获取服务器端的ip地址
InetAddress inetAddress = socket.getInetAddress();
System.out.println("服务端信息:"+inetAddress.getHostAddress());
//3. 关闭连接
socket.close();
}
}
package com.manlu.service;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 漫路h
*/
public class ServerDemo3 {
public static void main(String[] args) throws IOException {
//1. 创建服务器端对象
ServerSocket serverSocket = new ServerSocket(12123);
//2. 等待和客户端连接
Socket socket = serverSocket.accept();
System.out.println("和客户端连接成功");
//3. 获取输入、输出流、IP地址
InputStream in = socket.getInputStream();//输入流
OutputStream out = socket.getOutputStream();//输出流
String ip = socket.getInetAddress().getHostAddress();//ip
//4. 无限循环通信
while (true){
//4.1 读取信息
byte[] arr = new byte[1024];
int len = in.read(arr);
String str = new String(arr,0,len);
//4.2 判断信息
if ("退出".equals(str)) {
System.out.println("服务器端关闭");
socket.close();
break;//停止循环
}else{
System.out.println("服务器端接收到"+ip+"客户端信息: "+str);
out.write("服务器端已接收到信息。".getBytes("utf-8"));
out.flush();
}
}
}
}
package com.manlu.customer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* @author 漫路h
*/
public class ClientDemo3 {
public static void main(String[] args) throws IOException {
//1. 创建客户端
Socket socket = new Socket("127.0.0.1", 12123);
System.out.println("和服务器端连接成功");
//2. 获得输入流、输出流、IP
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
String ip = socket.getInetAddress().getHostAddress();
Scanner scanner = new Scanner(System.in);//控制台输入
//3. 无限循环通信
while (true){
//3.1 发送信息
System.out.println("请输入信息: ");
String str = scanner.nextLine();
out.write(str.getBytes("utf-8"));
out.flush();
if ("退出".equals(str)) {
System.out.println("客户端退出");
break;
}
//3.2. 接收信息
byte[] arr = new byte[1024];
int len = in.read(arr);
System.out.println("服务器端ip:"+ip);
System.out.println("服务器端回应内容:"+new String(arr,0,len));
}
}
}
如果需要一个服务器多个客户端,这就需要服务器端为每个客户端开启一个通讯线程。与客户端通讯的代码写在线程中,而主线程负责继续等待下一个客户端的连接。
对于多客户端聊天无需修改客户端代码,只需要修改服务器端代码即可。下面我们只编写服务器端。
服务端可以接收到不同的客户端发送的信息。客户端3和客户端4是一样的代码,复制一份即可。
可以看到主服务器端并没没有关闭,还在等待客户端发送信息。关闭的只是一个一个对应客户端的服务器端线程。
package com.manlu.service;
import com.manlu.thread.SocketThread;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 漫路h
*/
public class ServerDemo4 {
public static void main(String[] args) throws IOException {
//1. 创建服务器端对象
ServerSocket serverSocket = new ServerSocket(12123);
//2. 无限循环接收不同客户端信息
while (true) {
Socket socket = serverSocket.accept();
new Thread(new SocketThread(socket)).start();//创建线程,并启动线程
}
}
}
package com.manlu.thread;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author 漫路h
* 通信线程类
*/
public class SocketThread implements Runnable {
//客户端对象
private Socket socket;
/**
* 有参构造
* @param socket
*/
public SocketThread(Socket socket) {
this.socket = socket;
}
/**
* 线程
*/
public void run() {
try{
System.out.println("和客户端连接成功");
//3. 获得输入流、输出流、IP
InputStream in = socket.getInputStream();//输入流
OutputStream out = socket.getOutputStream();//输出流
String ip = socket.getInetAddress().getHostAddress();//客户端IP
//4. 无限循环通信
while (true) {
//4.1 读取客户端发送信息
byte[] arr = new byte[1024];
int len = in.read(arr);
String str = new String(arr, 0, len);
//4.2 判断信息
if ("退出".equals(str)){
System.out.println("服务器端关闭");
socket.close();
break;
}else{
System.out.println("接收到"+ip+"客户端信息:"+str);
out.write("服务器已接收到信息".getBytes("utf-8"));
out.flush();
}
}
}catch ( IOException e){
e.printStackTrace();
}
}
}
网络协议:是为了进行有效的网络传输,提前制定的数据传输标准和格式。
我们上面学习的Socket,是基于TCP的通讯协议。(即先建立连接,才能通讯)
TCP协议:全称(Transmission Control Protocol),翻译为:传输控制协议。
UDP协议:全称(User Datagram Protocol),翻译为:用户数据报协议。
TCP&UDP适应场景
现在先运行发送方也不会报错了,因为不管接收方能不能收到,我先发出去再说,然后如果接收方真的没有运行,那数据就丢了。
package com.manlu.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @author 漫路
* 发送方
*/
public class SenderDemo {
public static void main(String[] args) throws Exception {
//1. 创建socket对象
DatagramSocket ds = new DatagramSocket();
//2. 创建接收方的IP地址对象
InetAddress ia = InetAddress.getByName("127.0.0.1");
//3. 定义一个数组,用来保存待发送数据
byte[] arr = "测试发送的数据".getBytes("utf-8");
//4. 创建数据包类对象,用于封装要发送的数据、IP、端口号
DatagramPacket dp = new DatagramPacket(arr, arr.length, ia, 12124);
//5. 发送
ds.send(dp);
//6. 关闭socket对象
ds.close();
}
}
package com.manlu.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author 漫路
* 接收方
*/
public class ReceiverDemo {
public static void main(String[] args) throws Exception {
//1. 创建socket对象,指明接收数据的端口号
DatagramSocket ds = new DatagramSocket(12124);
//2. 创建数据包类
//2.1 创建一个字节数组,用于存储数据
byte[] arr = new byte[1024];
//2.2 创建数据包类,指明接收数据的数组
DatagramPacket dp = new DatagramPacket(arr, arr.length);
//3. 接收数据
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
System.out.println("发送方ip:"+ip);
System.out.println(new String(arr,"utf-8"));
//4. 关闭socket
ds.close();
}
}