网络应用模型
- C/S:
这里的C是指Client,即客户端。而S是指Server,即服务端。网络上的的应用本质上就是两台计算机上的软件进行交互。而客户端和服务端就是对应的两个应用程序,即客户端应用程序和服务端应用程序 - B/S:
这里的B是Browser,即浏览器,而S是指Server。浏览器是一个通用的客户端,可以与不同的服务端进行交互。但是本质上B/S还是C/S结构,只不过浏览器是一个通用的客户端而已
可靠传输与不可靠传输
TCP协议与UDP协议都是传输协议,客户端程序与服务端程序基于这些协议完成网络间的数据交互
- TCP是可靠传输协议,是面向连接的协议,保证数据传输中的可靠性和完整性。
TCP保证可靠传输,但是传输效率低,占用带宽高。 - UDP是不可靠传输协议,不保证数据传输的完整性。
UDP不保证可靠传输,但是传输速度块,占用带宽小。
java.net.Socket
Socket套接字:封装了TCP协议的通讯细节,可以使用它与服务端建立网络连接,并通过它获取两个流,然后使用两个流的读写操作完成与服务端的数据交互
java.net.ServerSocket
- 向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
- 监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。
实例化Socket时要传入两个参数
- 参数1:
服务端的地址信息 可以是IP地址,如果链接本机可以写"localhost" - 参数2:
服务端开启的服务端口 我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上 的服务端应用程序。 - 实例化的过程就是链接的过程,如果链接失败会抛出异常: java.net.ConnectException: Connection refused: connect
实例化ServerSocket时要指定服务端口
-
该端口不能与操作系统其他 应用程序占用的端口相同,否则会抛出异常: java.net.BindException:address already in use
-
端口是一个数字,取值范围:0-65535之间。 6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
服务端开始工作的方法
ServerSocket提供了接受客户端链接的方法:
- Socket accept()
这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
通过这个Socket就可以与客户端进行交互了。 可以理解为此操作是接电话,电话没响时就一直等。
Socket的重要方法:
- InputStream getInputStream()
通过Socket获取的字节输入流可以读取对方发送过来的字节数据
缓冲输入流的readLin方法:
- readLine()
当调用缓冲输入流的readLine方法读取对方发送过来一行字符串的操作时,可能由于对方的断开方法不同,导致不同的效果:
如果对方是异常断开(没有进行4次挥手):此时readLine方法会抛出异常:java.net.SocketException: Connection reset
如果对方正常断开(进行了4次挥手):此时readLine方法会返回null
该方法也存在阻塞现象
当我们调用readLine方法读取对方发送过来一行字符串时,该方法会进入阻塞等待,直到对方确实发送了一行数据过来才会解除阻塞并将这一行字符串立即返回
聊天工作室
服务端
package Socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
private ServerSocket serverSocket;
// private List<PrintWriter> allOut = new ArrayList<>();
private Map<String,PrintWriter> allOut = new HashMap<>();
private ExecutorService threadPool = Executors.newFixedThreadPool(50);
public Server() {
try {
System.out.println("正在启动服务端...");
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start(){
try {
while(true){
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了");
ClientHandler handler = new ClientHandler(socket);
// Thread t = new Thread(handler);
// t.start();
threadPool.execute(handler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
private class ClientHandler implements Runnable{
private Socket socket;
private String host;
private String nickName;
public ClientHandler(Socket socket) {
this.socket = socket;
host = socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
PrintWriter pw;//null
try {
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
nickName = br.readLine();
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
synchronized (allOut) {
// allOut.add(pw);
allOut.put(nickName, pw);
}
sendMessage(nickName +"("+ host +")上线了!当前在线人数:"+allOut.size() );
String line;
while((line = br.readLine()) != null){
if(line.startsWith("@")){ //想想办法加上接收方的名字 格式 @接收方名字:聊天内容
sendMessageToSomeone(line,pw);
}else {
sendMessage(nickName + "(" + host + ")说:" + line);
saveMessage(nickName, null, line);
}
}
} catch (IOException e) {
System.out.println(nickName +"("+ host +")异常断开了");
}finally {
synchronized (allOut) {
// allOut.remove(pw);
allOut.remove(nickName);
}
sendMessage(nickName + "(" + host + ")下线了!当前在线人数:"+allOut.size());
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendMessageToSomeone(String message,PrintWriter pw){
String toNickName = message.substring(1,message.indexOf(":"));
String toMessage = message.substring(message.indexOf(":")+1);
if(allOut.containsKey(toNickName)){
pw = allOut.get(toNickName);
pw.println(nickName+"悄悄对你说:"+toMessage);
saveMessage(nickName, toNickName, toMessage);
}else{
pw.println("[系统提示]:"+toNickName+",不存在");
}
}
/**
* 将消息广播给所有客户端
* @param message 给客户端发送消息
*/
private void sendMessage(String message){
System.out.println(message);
synchronized (allOut) {
// for(PrintWriter o : allOut){
// o.println(message);
// }
Collection<PrintWriter> c = allOut.values();
for (PrintWriter pw : c) {
pw.println(message);
}
}
}
/**
* 将聊天消息保存到数据库中
* @param from 发送方
* @param to 接收方
* @param message 聊天内容
*/
private void saveMessage(String from,String to,String message){
try(
Connection connection = DBUtil.getConnection();
){
String sql = "INSERT INTO chatinfo(fromuser,touser,content) " +
"VALUES(?,?,?) ";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,from);
ps.setString(2,to);
ps.setString(3,message);
ps.executeUpdate();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
}
}
客户端
package Socket;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Client {
private Socket socket;
public Client(){
try{
System.out.println("正在连接服务端...");
// socket = new Socket("192.168.1.19",8088);
socket = new Socket("localhost",8088);
System.out.println("服务端已连接");
}catch (Exception e){
e.printStackTrace();
}
}
public void start(){
try{
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
String nickName;
while(true){
System.out.println("请输入您的昵称:");
nickName = scanner.nextLine();
if(nickName.trim().length()>0){
pw.println(nickName);
break;
}else{
System.out.println("昵称至少输入一个文字");
}
}
System.out.println("欢迎你["+nickName+"],开始聊天吧!");
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.setDaemon(true);
t.start();
while (true){
String line = scanner.nextLine();
if("EXIT".equalsIgnoreCase(line)){
pw.println("exit");
break;
}
pw.println(line);
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
private class ServerHandler implements Runnable{
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String message;
while((message = br.readLine()) != null){
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
数据库连接
CREATE DATABASE chatDB;
CREATE TABLE `chatinfo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fromuser` varchar(30) DEFAULT NULL,
`touser` varchar(30) DEFAULT NULL,
`content` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
package Socket2;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/chatdb?" +
"characterEncoding=utf8" +
"&useSSL=false" +
"&serverTimezone=Asia/Shanghai" +
"&rewriteBatchedStatements=true",
"root",
"root"
);
return connection;
}
}