Java网络编程学习笔记
1 网络基础
1.1 网络通信
1.概念:两台设备之间通过网络实现实现数据传输
2.网络通信:将数据通过网络从一台设备传输到另一台设备
3.java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信
1.2 网络
1.概念:两台或多台设置通过一定物理设备连接起来构成了网络
2.根据网络的覆盖范围不同,对网络进行分类:
。局域网:覆盖范围小,仅仅覆盖一个教室或一个机房
。城域网:覆盖范围较大,可以覆盖一个城市
。广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表
1.3 IP地址
1.概念:用于唯一标识网络中的每台计算机/主机
2.查看ip地址:ipconfig
3.ip地址的表示形式:点分十进制 xxx.xxx.x.xx 比如:192.168.1.101
4.每个十进制数的范围是:0-255
IP表示:对于IPv4 4个字节(32位)表示 192.168.1.101
1个字节的范围是0-255
第1个字节 | 第2个字节 | 第3个字节 | 第4个字节 |
---|---|---|---|
1111 1111(8位) | 1111 1111(8位) | 1111 1111(8位) | 1111 1111(8位) |
0-255 2^8 = 256 | 0-255 | 0-255 | 0-255 |
192. | 168. | 1. | 101 |
5.ip地址的组成=网络地址+主机地址,比如:192.168.16.69
6.IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,
其地址数量号称可以为全世界的每一粒沙子编上一个地址
IPv6 16个字节 128位 表示一个地址
7.由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。
IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍。
IPv4地址分类:
1.5 域名
IP:182.61.200.61
1.域名:www.baidu.com
2.好处:为了方便记忆,解决记ip的困难
3.概念:将ip映射成域名,这里怎么映射上,属于http协议的内容
1.6 端口号
1.概念:用于标识计算机上某个特定的网络程序
2.表示形式:以整数形式,端口范围0-65535 (2个字节:2^16 = 65535)
3.0-1024已经被占用,比如 ssh 22,ftp 21,smtp 25,http 80
4.常见的网络程序端口号:
tomcat: 8080
oracle: 1521
mysql: 3306
sqlserver:1433
5.HTTP协议的默认端口号是:80
小知识:有的公司,不想让员工上网登录QQ,就会通过防火墙将QQ的端口号禁掉。
1.7 网络通信协议
协议:语言本身就是一种协议,比如:中文 英文 韩语 日语
在计算机中,数据的组织形式,就是协议。
协议(tcp/ip):(Transmission Control Protocol/Internet Protocol的简写,中文译名:传输控制协议/因特网互联协议,又叫网络通讯协议。
这个协议是Internet最基本的协议,Internet国际互联网络的基础,
简单地说,就是由传输层的TCP协议和网络层的IP协议组成的。
网络通讯协议模型:
现在用的协议:并没有用理论型的OSI模型,ISO模型分得太细了,现实的网络中,用的是TCP/IP协议。
1.8 TCP协议
TCP/IP协议: 传输控制协议,是一种面向连接的保证可靠传输的协议。
通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,
以便在TCP协议的基础上进行通信,当一个socket等待建立连接时,另一个socket可以要求进行连接,
一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收数据。
TCP是基于连接的协议,它能够提供两台计算机之间的可靠的数据流,HTTP、FTP、Telnet等应用都需要这种可靠的通信通道。
1.使用TCP协议前,须先建立TCP连接,形成传输数据通道
2.传输前,采用“三次握手”方式,是可靠的
3.TCP协议进行通信的两个应用进程:客户端,服务端
4.在连接中可进行大数据量的传输
5.传输完毕,需释放已建立的连接,效率低
1.9 UDP协议
UDP协议: 用户数据协议,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,
它在网络上以任何可能的路径传往目的地,因此能否到达目的地,
到达目的地的时间以及内容的正确性都是不被保证的。
UDP是从一台计算机向另一台计算机发送称为数据报的独立数据报的协议,该协议并不保证数据报是否能正确地到达目的地。
它是一个非面向连接的协议。
通常用于传输音频或者视频,视频会议,抖音直播,数据量大的情况,这种都用的UDP协议。
1.将数据、源、目的封装成数据包,不需要建立连接
2.每个数据报的大小限制在64K内,不适合传输大量数据
3.因无需连接,故是不可靠的
4.发送数据结束时无需释放资源(因为不是面向连接的),速度快
5.举例:厕所通知, 发短信
2 IntetAddress IP地址
2.1 getLocalHost()
getLocalHost():获取本机InetAddress对象 获取本机的IP地址对象
//获取本机的InetAddress对象
InetAddress localHost = InetAddress.getLocalHost();
2.2 getByName()
getByName():根据指定主机名/域名获取 InetAddress对象
//根据主机名 获取InetAddress对象
InetAddress localHost2 = InetAddress.getByName("tangguanlin2006");
//根据域名 获取InetAddress对象
InetAddress localHost3 = InetAddress.getByName("www.baidu.com");
2.3 getHostAddress()
localHost3.getHostAddress():获取InetAddress对象的IP地址
//根据InetAddress对象,获取IP地址
String hostAddress = localHost3.getHostAddress();
2.4 getHostName()
localHost3.getHostName():获取InetAddress对象的主机名/域名
//根据InetAddress对象 获取主机名/域名
String hostName = localHost3.getHostName();
代码:
package com.tangguanlin.javanet;
import java.net.InetAddress;
/**
* 说明:InetAddress类
* 作者:汤观林
* 日期:2022年01月15日 15时
*/
public class InetAddressTest {
public static void main(String[] args) throws Exception{
//1.获取本机的InetAddress对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("getLocalHost():"+localHost); //tangguanlin2006/192.168.1.101
//2.根据主机名 获取InetAddress对象
InetAddress localHost2 = InetAddress.getByName("tangguanlin2006");
System.out.println("getByName():"+localHost2); //tangguanlin2006/192.168.1.101
//3.根据域名 获取InetAddress对象
InetAddress localHost3 = InetAddress.getByName("www.baidu.com");
System.out.println("localHost3:"+localHost3); //www.baidu.com/163.177.151.109
//4.根据InetAddress对象,获取IP地址
String hostAddress = localHost3.getHostAddress();
System.out.println("getHostAddress():"+hostAddress); //163.177.151.109
//5.根据InetAddress对象 获取主机名/域名
String hostName = localHost3.getHostName();
System.out.println("getHostName():"+hostName); //www.baidu.com
}
}
运行结果:
getLocalHost():tangguanlin2006/192.168.1.101
getByName():tangguanlin2006/192.168.1.101
localHost3:www.baidu.com/182.61.200.7
getHostAddress():182.61.200.7
getHostName():www.baidu.com
3 Socket
Socket:
socket的出现,使程序员可以很方便地访问TCP/IP协议,从而开发各种网络应用的程序。
socket是连接运行在网络上的两个程序间的双向通讯的端点。
。服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户的连接请求。
。客户程序根据服务器程序所在的主机名和端口号发出连接请求。
。如果一切正常,服务器接收连接请求。并获得一个新的绑定到不同端口地址的套接字。
。客户和服务器通过读、写套接字进行通讯。
1.套接字(Socket)开发网络应用程序被广泛采用,以至于称为事实上的标准,
2.通信的两端都要有Socket,是两台机器间通信的端点
3.网络通信其实就是Socket间的通信
4.Socket运行程序把网络连接当成一个流,数据在两个Socket建通过IO传输
5.一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端
6.TCP编程和UDP编程,都是借助Socket实现的
TCP协议类似于 打电话
UDP协议类似于 写信 发邮件
7.JDK对TCP协议和UDP协议都提供了内置的支持。
示意图:
总结:相当于 JDK用Socket屏蔽底层的TCP/IP协议通信实现的细节,我们开发时,只要操作Socket就可以了。
JDK会完成Socket到TCP/IP协议细节的转换。
4 TCP编程
4.1 基本介绍
1.基于客户端–服务端的网络通信
2.底层使用的是TCP/IP协议
3.应用场景举例:客户端发送数据,服务端接收并显示到控制台
4.基于Socket的TCP编程
4.2 应用案例1(使用字节流)
1.编写服务器端,和一个客户端
2.服务器端在9999端口监听
3.客户端连接到服务器端,发送“hello,server”,然后退出
4.服务器端接收到客户端发送的信息,输出,并退出
示意图:
服务端TCPServer1.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 说明:TCP编程 服务端
* 作者:汤观林
* 日期:2022年01月15日 17时
*/
public class TCPServer1 {
public static void main(String[] args) throws IOException {
//1.在本机的9999端监听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端 在9999端监听,等待连接....");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接时,则会返回socket对象,程序继续往下执行
Socket socket = serverSocket.accept();
System.out.println("服务端 socket:"+socket.getClass());
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//4.读取客户端写入到数据通道的数据
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen=inputStream.read())!=-1){
System.out.println(new String(buf,0,readLen));
}
//5.关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();
}
}
运行结果:
服务端 在9999端监听,等待连接....
服务端 socket:class java.net.Socket
hello server
客户端TCPClient1.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:TCP编程 客户端
* 作者:汤观林
* 日期:2022年01月15日 17时
*/
public class TCPClient1 {
public static void main(String[] args) throws IOException {
//1.连接服务端(服务端ip,服务端端口)
//解读:连接服务器的9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999); //要跟谁连,端口号是多少
System.out.println("客户端 socket返回"+socket.getClass());
//2.连接上后,生成socket,通过socket.getOutputStream
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流,写入数据到数据通道
outputStream.write("hello server".getBytes());
//4.关闭流对象和socket
outputStream.close();
socket.close();
System.out.println("客户端已退出");
}
}
运行结果:
客户端 socket返回class java.net.Socket
客户端已退出
4.3 应用案例2(使用字节流)
1.编写一个服务器端,和一个客户端
2.服务器端在9999端口监听
3.客户端连接到服务器端,发送“hello,server”,并接收服务器端回发的“hello,client”,再退出
4.服务器端接收到客户端发送的信息,输出,并发送“hello,client”,再退出
示意图:
代码:
服务端TCPServer2.java
package com.tangguanlin.javanet;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 说明:TCP编程 服务端
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPServer2 {
public static void main(String[] args) throws Exception{
//1.在本机的9999端监听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端 在9999端监听,等待连接....");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接时,则会返回socket对象,程序继续往下执行
Socket socket = serverSocket.accept();
System.out.println("服务端 socket:"+socket.getClass());
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//4.读取客户端写入到数据通道的数据
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen=inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
//5.获取到输出流
OutputStream outputStream = socket.getOutputStream();
//6.写入数据到输出流
outputStream.write("hello client".getBytes());
//设置结束标记
socket.shutdownOutput();
//7.关闭流和socket
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();
}
}
运行结果:
服务端 在9999端监听,等待连接....
服务端 socket:class java.net.Socket
hello server
客户端TCPClient2.java
package com.tangguanlin.javanet;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:TCP编程 客户端
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPClient2 {
public static void main(String[] args) throws Exception{
//1.连接服务端(服务端ip,服务端端口)
//解读:连接服务器的9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回"+socket.getClass());
//2.连接上后,生成socket,通过socket.getOutputStream
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流,写入数据到数据通道
outputStream.write("hello server".getBytes());
//设置结束标记
socket.shutdownOutput();
//4.获取输入流
InputStream inputStream = socket.getInputStream();
//5.从输入流中读取数据
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen=inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
//6.关闭流对象和socket
outputStream.close();
inputStream.close();
socket.close();
System.out.println("客户端已退出");
}
}
运行结果:
客户端 socket返回class java.net.Socket
hello client
客户端已退出
4.4 应用案例3(使用字符流)
1.编写一个服务器端,和一个客户端
2.服务器端在9999端口监听
3.客户端连接到服务器端,发送“hello,server”,并接收服务器端回发的“hello,client”,再退出
4.服务器端接收到客户端发送的信息,输出,并发送“hello,client”,再退出
示意图:
代码:
服务器端TCPServer3.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 说明:TCP编程 服务端 字符流
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPServer3 {
public static void main(String[] args) throws Exception{
//1.在本机的9999端监听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端 在9999端监听,等待连接....");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接时,则会返回socket对象,程序继续往下执行
Socket socket = serverSocket.accept();
System.out.println("服务端 socket:"+socket.getClass());
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//使用转换流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//4.读取客户端写入到数据通道的数据
String str = bufferedReader.readLine();
System.out.println("服务端 字符流"+str);
//5.获取到输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
//6.写入数据到输出流
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();
bufferedWriter.flush();
//7.关闭流和socket
bufferedReader.close(); //关闭外侧流
bufferedWriter.close();
socket.close();
serverSocket.close();
}
}
运行结果:
服务端 在9999端监听,等待连接....
服务端 socket:class java.net.Socket
服务端 字符流hello server
客户端TCPClient3.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:TCP编程 客户端 字符流
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPClient3 {
public static void main(String[] args) throws Exception{
//1.连接服务端(服务端ip,服务端端口)
//解读:连接服务器的9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回"+socket.getClass());
//2.连接上后,生成socket,通过socket.getOutputStream
OutputStream outputStream = socket.getOutputStream();
//使用转换流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
//3.通过输出流,写入数据到数据通道
bufferedWriter.write("hello server");
//插入一个换行符,表示写入的内容结束,注意:要求对方使用readLine()
bufferedWriter.newLine();
//如果使用字符流,需要手动刷新,否则数据不会写入数据通道
bufferedWriter.flush();
//4.获取输入流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//5.从输入流中读取数据
String str = bufferedReader.readLine();
System.out.println("客户端 字符流"+str);
//6.关闭流对象和socket
bufferedWriter.close(); //关闭外层流
bufferedReader.close();
socket.close();
System.out.println("客户端已退出");
}
}
运行结果:
客户端 socket返回class java.net.Socket
客户端 字符流hello client 字符流
客户端已退出
4.5 应用案例4(上传文件)
1.编写一个服务端,和一个客户端
2.服务器端在8888端口监听
3.客户端连接到服务端,发送 一张图片 e:\qie.png
4.服务器端接收到客户端发送的图片,保存到src下,发送“收到图片”再退出
5.客户端接收到服务端发送的“收到图片”,再退出
6.该程序要求使用StreamUtils.java
说明:使用BufferedInputStream和BufferedOutputStream字节流
示意图:
代码:
服务端TCPServer4.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 说明:TCP编程 服务端 上传图片
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPServer4 {
public static void main(String[] args) throws Exception{
//1.在本机的9999端监听,等待连接
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端 在9999端监听,等待连接....");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接时,则会返回socket对象,程序继续往下执行
Socket socket = serverSocket.accept();
System.out.println("服务端 socket:"+socket.getClass());
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//使用转换流
BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len=bis.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
byte[] bytes1 = byteArrayOutputStream.toByteArray();
//4.读取客户端写入到数据通道的数据
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\112.jpg"));
bos.write(bytes1);
bos.flush();
//5.获取到输出流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//6.写入数据到输出流
bufferedWriter.write("收到图片");
bufferedWriter.newLine();
bufferedWriter.flush();
//7.关闭流和socket
bufferedWriter.close(); //关闭外侧流
bos.close();
bis.close();
socket.close();
serverSocket.close();
}
}
运行结果:
服务端 在9999端监听,等待连接....
服务端 socket:class java.net.Socket
客户端TCPClient4.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:TCP编程 客户端 上传图片
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPClient4 {
public static void main(String[] args) throws Exception{
//1.连接服务端(服务端ip,服务端端口)
//解读:连接服务器的9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
System.out.println("客户端 socket返回"+socket.getClass());
//文件字节数组
FileInputStream fileInputStream = new FileInputStream("D:\\11.jpg");
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len=bis.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
byte[] bytes1 = byteArrayOutputStream.toByteArray();
//2.连接上后,生成socket,通过socket.getOutputStream
//使用转换流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//3.通过输出流,写入数据到数据通道
bos.write(bytes1);
bos.flush();
socket.shutdownOutput();
//4.获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//5.从输入流中读取数据
String str = bufferedReader.readLine();
System.out.println("客户端 "+str);
//6.关闭流对象和socket
bufferedReader.close();
bos.close();
bis.close();
byteArrayOutputStream.close();
socket.close();
System.out.println("客户端已退出");
}
}
运行结果:
客户端 socket
返回class java.net.Socket
客户端 收到图片
客户端已退出
4.6 应用案例5(下载文件)
1.编写客户端程序和服务端程序
2.客户端可以输入一个音乐文件名,比如:高山流水,
服务端收到音乐名后,可以给客户端返回这个音乐名称,
如果服务器端没有这个文件,就返回一个默认的音乐即可。
3.客户端收到文件后,保存到本地E:\
示意图:
代码:
服务端TCPServer6.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 说明:TCP编程 服务端 下载文件
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPServer6 {
public static void main(String[] args) throws Exception{
//1.在本机的9999端监听,等待连接
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端 在9999端监听,等待连接....");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接时,则会返回socket对象,程序继续往下执行
Socket socket = serverSocket.accept();
System.out.println("服务端 socket:"+socket.getClass());
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//使用转换流
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//4.读取客户端写入到数据通道的数据
String str = reader.readLine();
System.out.println(str);
//文件字节数组
FileInputStream fileInputStream = new FileInputStream("F:\\02mp3\\002.Beyond-情人.mp3");
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len=bis.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
byte[] bytes1 = byteArrayOutputStream.toByteArray();
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//5.通过输出流,写入数据到数据通道
bos.write(bytes1);
bos.flush();
socket.shutdownOutput();
//6.关闭流和socket
bos.close();
bis.close();
socket.close();
serverSocket.close();
}
}
运行结果:
服务端 在9999端监听,等待连接....
服务端 socket:class java.net.Socket
高山流水
客户端TCPClient6.java
package com.tangguanlin.javanet;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:TCP编程 客户端 文件下载
* 作者:汤观林
* 日期:2022年01月15日 23时
*/
public class TCPClient6 {
public static void main(String[] args) throws Exception{
//1.连接服务端(服务端ip,服务端端口)
//解读:连接服务器的9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
System.out.println("客户端 socket返回"+socket.getClass());
//2.连接上后,生成socket,通过socket.getOutputStream
//使用转换流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//3.通过输出流,写入数据到数据通道
bos.write("高山流水".getBytes());
bos.flush();
socket.shutdownOutput();
//3.通过socket.getInputStream()获取输入流
InputStream inputStream = socket.getInputStream();
//使用转换流
BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len=bis.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
byte[] bytes1 = byteArrayOutputStream.toByteArray();
//4.将数据写入到磁盘
bos = new BufferedOutputStream(new FileOutputStream("F:\\002.Beyond-情人.mp3"));
bos.write(bytes1);
bos.flush();
//5.关闭流对象和socket
bos.close();
bis.close();
byteArrayOutputStream.close();
socket.close();
System.out.println("客户端已退出");
}
}
运行结果:
客户端 socket返回class java.net.Socket
客户端已退出
4.7 netstat指令
1.netstat 可以查看当前主机网络情况
2.netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
3.netstat -an|more 可以分页显示
4.要求在dos控制台执行
4.8 TCP连接秘密
1.当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,
这个端口是TCP/IP来分配的,是不确定的,是随机的。
2.示意图:
3.程序验证+netstat
5 UDP编程
5.1 基本介绍
Datagram数据报是网上传输的独立数据报,数据报是否能正确地到达目的地,到达的时间,顺序,内容的正确性均没有保障。
Java中使用Datagram与DatagramPacket来实现UDP协议
1.类DatagramSocke[]t和DatagramPacket[数据包/数据报]实现了基于UDP协议网络程序。
2.UDP数据报通过数据报套接字DatagramSocket发送和接收,
系统不保证UDP数据报一定能够安全到达目的地,也不能确定什么时候可以到达。
3.DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
4.UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。
5.2 基本流程
1.核心的两个类/对象DatagramSocket与DatagramPacket
2.建立发送端,接收端(没有服务端和客户端概念)
3.发送数据前,建立数据包/报,DatagramPacket对象
4.调用DatagramSocket的发送、接收方法
5.关闭DatagramSocket
示意图:
5.3 应用案例1
1.编写一个接收端,和一个发送端
2.接收端在9999端口等待接收数据(receive)
3.发送端向接收端发送数据“hello,明天吃火锅”
4.接收端接收到发送端发送的数据,回复“好的,明天见”,再退出
5.发送端接收回复的数据,再退出
分析思路:
代码:
发送端UDPSender1.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.net.*;
/**
* 说明: 发送端====>也可以接收数据
* 作者:汤观林
* 日期:2022年01月16日 21时
*/
public class UDPSender1 {
public static void main(String[] args) throws IOException {
//1.创建一个DatagraSocket对象,准备在9998端口接收数据
DatagramSocket socket = new DatagramSocket(9998);
//2.将需要发送的数据 封装到DatagramPacket对象中
byte[] bytes = "hello,明天吃火锅".getBytes();
InetAddress inetAddress = InetAddress.getByName("192.168.1.101");
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, inetAddress, 9999);
//3.发送数据
socket.send(packet);
System.out.println("信息已发出");
//4.接收回复数据
byte[] buf = new byte[64*1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
//5.可以把packet进行拆包,取出数据并显示
int length = packet.getLength(); //实际接收到的数据长度
bytes = packet.getData();
String str = new String(bytes,0,length);
System.out.println("发送端收到:"+str);
//6.关闭资源
socket.close();
System.out.println("发送端退出");
}
}
运行结果:
信息已发出
发送端收到:好的,明天见
发送端退出
接收端UDPReceiver1.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 说明:接收端
* 作者:汤观林
* 日期:2022年01月16日 21时
*/
public class UDPReceiver1 {
public static void main(String[] args) throws IOException {
//1.创建一个DatagraSocket对象,准备在9999端口接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2.创建一个DatagramPacket对象,准备接收数据
//UDP最大64K
byte[] buf = new byte[64*1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3.调用接收方法 将通过网络传输的DatagramPacket对象
//填充到packet对象
//当有数据包发送到本机的9999端口时,就会接收到数据
//如果没有数据包发送到本机的9999端口时,就会阻塞等待
System.out.println("接收端等待接收数据");
socket.receive(packet);
//4.可以把packet进行拆包,取出数据并显示
int length = packet.getLength(); //实际接收到的数据长度
byte[] bytes = packet.getData();
String str = new String(bytes,0,length);
System.out.println("接收端收到:"+str);
//5.将回复的数据 封装到DatagramPacket对象中
bytes = "好的,明天见".getBytes();
//从接收的packet从获取对方的地址和端口号
packet = new DatagramPacket(bytes, 0, bytes.length, packet.getAddress(), packet.getPort());
//6.发送数据
socket.send(packet);
//7.关闭资源
socket.close();
}
}
运行结果:
接收端等待接收数据
接收端收到:hello,明天吃火锅
5.4 应用案例2
编程题:
1.编写一个接收端,和一个发送端,使用UDP协议完成
2.接收端在8888端口等待接收数据
3.发送端想接收端发送数据“四大名著是哪些”
4.接收端接收到发送端发送的问题后,返回“四大名著是《红楼梦》《三国演义》《西游记》《水浒传》”,
否则返回what?
5.接收端和发送端程序退出
代码:
发送端UDPSender2.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 说明: 发送端====>也可以接收数据
* 作者:汤观林
* 日期:2022年01月16日 21时
*/
public class UDPSender2 {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8887);
byte[] bytes = "四大名著是哪些".getBytes();
InetAddress inetAddress = InetAddress.getByName("192.168.1.101");
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length, inetAddress,8888);
socket.send(packet);
bytes = new byte[64*1024];
packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
int len = packet.getLength();
byte[] data = packet.getData();
String str = new String(data, 0, len);
System.out.println(str);
//释放资源
socket.close();
}
}
运行结果:
四大名著是《红楼梦》《三国演义》《西游记》《水浒传》
接收端UDPReceiver2.java
package com.tangguanlin.javanet;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 说明:接收端
* 作者:汤观林
* 日期:2022年01月16日 21时
*/
public class UDPReceiver2 {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8888);
byte[] bytes = new byte[64*1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
int len = packet.getLength();
byte[] data = packet.getData();
String str = new String(data, 0, len);
System.out.println(str);
String replayMessge = "";
if("四大名著是哪些".equals(str)){
replayMessge ="四大名著是《红楼梦》《三国演义》《西游记》《水浒传》";
}else{
replayMessge="what";
}
bytes = replayMessge.getBytes();
//从接收的packet从获取对方的地址和端口号
packet = new DatagramPacket(bytes,0, bytes.length,packet.getAddress(),packet.getPort());
socket.send(packet);
//释放资源
socket.close();
}
}
运行结果:
四大名著是哪些
6 多用户通信系统
6.1 为什么选择这个项目
1.有趣
2.涉及到java各个方面的技术
-项目框架设计
-java面向对象编程
-网络编程
-多线程
-IO流
-MySQL/使用集合充当内存数据库
3.巩固旧知识,学习新知识
6.2 项目开发流程
需求分析—>设计阶段—>实现阶段---->测试阶段---->实施阶段—>维护阶段
500万项目 (30%) (20%) (20%) (20%) (5%) (5%)
在国内,真正花时间最多的节点,还是实现阶段,很多都是一边开发,一边确认需求,一边修改设计,一边补充文档。
需求分析:
1.需求分析师:懂技术+懂行业(业务知识:税务,金融)
2.出一个需求分析说明文档:该项目功能,客户具体要求
设计阶段:
1.架构师/项目经理
2.设计工作(UML类图,流程图,模块设计,数据库设计,架构选型:SSH,Spring Boot)
3.画原型图(Axure RP)
4.组建团队
实现阶段:
1.程序员
2.完成架构师的模块功能
3.测试自己的模块
测试阶段:
1.测试人员
2.编写测试用例
3.黑盒测试
实施阶段:
1.实施人员
2.项目正确的部署到客户的平台,并保证运行正常
3.环境配置部署能力/网络拓扑结构
4.经常出差,经常不着家,对家庭不好
5.对自身发展不好
6.不建议去从事实施人员
维护阶段:
1.运维人员
2.现场运维
3.现场开发:发现bug/项目升级,增加部分功能
6.3 多用户通信需求
QQ聊天系统
1.用户登录
2.拉取在线用户列表
3.无异常退出(客户端,服务器端)
4.私聊
5.群聊
6.发文件
7.服务器推送新闻
6.4 界面设计和思路分析
### 6.4.1 用户登录
1.功能说明
因为还没有学习数据库,我们人为规定 用户名/id = 100,密码:123456就可以的登录,其他用户不能登录
后面使用HashMap模拟数据库,可以多个用户登录
2.思路分析+程序框架图
6.4.2 拉取在线用户列表
6.4.3 无异常退出
思路分析:
6.4.4 私聊
思路分析:
6.4.5 群聊
6.4.6 发文件
思路分析:
6.4.7 服务器推送新闻
思路分析:
6.4.8 离线留言和发文件
(1).实现离线留言,如果某个用户没有在线,当登录后,可以接收离线的消息
(2).实现离线发文件,如果某个用户没有在线,当登录后,可以接收离线的文件
思路分析:
6.5 代码实现
6.5.1 客户端QQClient工程
1.User.java
package com.tangguanlin.common;
import java.io.Serializable;
/**
* 说明:用户信息
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId; //用户id
private String password; //用户密码
public User() {
}
public User(String userId, String password) {
this.userId = userId;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.Message.java
package com.tangguanlin.common;
import java.io.Serializable;
/**
* 说明:客户端和服务端的通信消息对象
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender; //发送者
private String receiver; //接收者
private String content; //消息内容
private String mesType; //消息类型[可以在接口中定义消息类型]
private String sendTime; //发送时间
private byte[] fileBytes; //文件字节
private int fileLen; //文件长度
private String src ; //文件源路径
private String desc; //文件目的地
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
public byte[] getFileBytes() {
return fileBytes;
}
public void setFileBytes(byte[] fileBytes) {
this.fileBytes = fileBytes;
}
public int getFileLen() {
return fileLen;
}
public void setFileLen(int fileLen) {
this.fileLen = fileLen;
}
}
3.MessageType.java
package com.tangguanlin.common;
/**
* 说明:消息类型:表现消息类型有哪些
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public interface MessageType {
//1.在接口中,定义了一些常量
//2.这些常量的值,代表不用的消息类型
String MESSAGE_LOGIN_SUCCESSED = "1"; //表示登录成功
String MESSAGE_LOGIN_FAIL = "2"; //表示登录失败
String MESSAGE_COMM_MES = "3"; //私聊
String MESSAGE_GET_ONLINE_FRIEND = "4" ; // 要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND = "5" ; //返回在线用户列表
String MESSAGE_CLIENT_EXIT = "6"; //客户端请求退出
String MESSAGE_MANY_MES = "7"; //群聊
String MESSAGE_FILE_MES = "8"; //文件
String MESSAGE_NEWS_MES = "9"; //推送新闻
}
4.ManageClientConnectServerThread.java
package com.tangguanlin.service;
import java.util.HashMap;
/**
* 说明:管理 客户端连接到服务器端的线程的管理类
* 作者:汤观林
* 日期:2022年01月29日 20时
*/
public class ManageClientConnectServerThread {
//把多个线程,放入到hashmap集合中,key就是用户id,value就是一个线程
private static HashMap<String,ClientConnectServerThread> hashmap = new HashMap<String,ClientConnectServerThread>();
//添加线程到集合
public static void addClientConnectServerThread(String userId,ClientConnectServerThread clientConnectServerThread){
hashmap.put(userId,clientConnectServerThread);
}
//获取线程
public static ClientConnectServerThread getClientConnectServerThread(String userId){
return hashmap.get(userId);
}
}
5.QQView.java
package com.tangguanlin.view;
import com.tangguanlin.service.MessageChatService;
import com.tangguanlin.service.UserClientService;
import java.security.Key;
import java.util.Scanner;
/**
* 说明:客户端的菜单界面
* 作者:汤观林
* 日期:2022年01月29日 16时
*/
public class QQView {
private boolean loop = true; //控制是否显示菜单
private String key = ""; //接收用户的键盘输入
//用于登录服务器
private UserClientService userClientService = new UserClientService();
private MessageChatService messageChatService = new MessageChatService();
public static void main(String[] args) throws Exception {
new QQView().mainView();
System.out.println("客户端退出系统");
}
//显示主菜单
private void mainView() throws Exception {
while(loop){
System.out.println("==========欢迎登录网络通信系统========");
System.out.println(" 1 登录系统");
System.out.println(" 9 退出系统");
System.out.println("请输入你的选择:");
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
key = next;
//根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
System.out.println("请输入用户号:");
String userId = scanner.next();
System.out.println("请输入密 码:");
String psw = scanner.next();
//需要去服务端验证该用户是否合法
if(userClientService.checkUser(userId,psw)){
System.out.println("=========欢迎(用户"+userId+"登录成功)===============");
//进入到二级菜单
while (loop){
System.out.println("=========网络通讯系统二级菜单(用户 "+userId+")==================");
System.out.println(" 1 显示在线用户列表 ");
System.out.println(" 2 群发消息 ");
System.out.println(" 3 私聊消息 ");
System.out.println(" 4 发送文件 ");
System.out.println(" 9 退出系统 ");
System.out.println("请输入你的选择:");
String key = scanner.next();
switch (key){
case "1":
//System.out.println("显示在线用户列表");
userClientService.onlineFriendList();
break;
case "2":
//群发消息
System.out.println("请输入想说的话:");
String content = scanner.next(); //聊天的内容
messageChatService.sendMessageToMany(content,userId);
break;
case "3":
//私聊消息
System.out.println("请输入想聊天的用户号(在线):");
String receiverId = scanner.next();
System.out.println("请输入想说的话:");
String content1 = scanner.next(); //聊天的内容
messageChatService.sendMessageToOne(content1,userId,receiverId);
break;
case "4":
//发送文件
System.out.println("请输入要发送的用户号(在线):");
String receiverId2 = scanner.next();
//System.out.println("请输入要上传的文件路径:");
//String filepath = scanner.next();
String filepath = "H:\\117Java IO\\from\\koala.jpg";
messageChatService.sendFileToOne(filepath,userId,receiverId2);
break;
case "9":
loop = false;
userClientService.sendExitMessage();
break;
}
}
}else{
//登录服务器失败
System.out.println("=====登录失败========");
break;
}
break;
case "9":
loop = false;
userClientService.sendExitMessage();
break;
}
}
}
}
6.UserClientService.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import com.tangguanlin.common.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 说明:用户登录验证 用户注册 等功能
* 作者:汤观林
* 日期:2022年01月29日 17时
*/
public class UserClientService {
//因为可能在其他地方也会使用user信息,因此作成一个成员属性
private User user = new User();
private Socket socket;
//根据userId,pwd到服务器验证该用户是否合法
public boolean checkUser(String userId,String pwd) throws Exception {
boolean b = false;
//创建user对象
user.setUserId(userId);
user.setPassword(pwd);
//连接到服务端,发送user对象
socket = new Socket(InetAddress.getLocalHost(), 9999);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(user); //发送user对象
//读取从服务器回复的message对象
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Message msg = (Message)objectInputStream.readObject();
if(msg.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCESSED)){ //登录成功
//创建一个和服务器端保存通信的线程-->创建一个线程类 ClientConnectServerThread
ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
//启动客户端的线程
clientConnectServerThread.start();
//我们将线程放入到集合管理
ManageClientConnectServerThread.addClientConnectServerThread(userId,clientConnectServerThread);
b = true;
}else{
//如果登录失败,我们就不能启动和服务器通信的线程,关闭socket
socket.close();
}
return b;
}
//向服务器端请求在线用户列表
public void onlineFriendList() throws IOException {
//发送一个message,类型:MESSAGE_GET_ONLINE_FRIEND = "4" ; // 要求返回在线用户列表
Message message = new Message();
message.setSender(user.getUserId());
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
//发送给服务器
//得到当前线程的socket 对应的 ObjectOutputStream对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket().getOutputStream());
objectOutputStream.writeObject(message);//发送消息给服务器
}
//想服务器发送退出消息
public void sendExitMessage() throws IOException {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getUserId()); //一定要指明我是哪个客户端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
System.out.println(user.getUserId()+"退出系统");
System.exit(0); //退出系统,结束进程
}
}
7.MessageChatService.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import java.io.*;
import java.util.Date;
/**
* 说明: 私聊,群聊服务类
* 作者:汤观林
* 日期:2022年01月30日 14时
*/
public class MessageChatService {
//私聊 发消息
public void sendMessageToOne(String content,String senderId,String receiverId) throws IOException {
Message message = new Message();
message.setSender(senderId);
message.setReceiver(receiverId);
message.setMesType(MessageType.MESSAGE_COMM_MES);
message.setContent(content);
message.setSendTime(new Date().toString()); //发送时间
System.out.println(senderId+"对"+receiverId+"说:"+content);
//发送给服务端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
}
//群聊 发消息
public void sendMessageToMany(String content,String senderId) throws IOException {
Message message = new Message();
message.setSender(senderId);
message.setMesType(MessageType.MESSAGE_MANY_MES);
message.setContent(content);
message.setSendTime(new Date().toString()); //发送时间
System.out.println(senderId+"对大家说:"+content);
//发送给服务端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
}
public void sendFileToOne(String filepath,String senderId,String receiverId) throws IOException {
FileInputStream fileInputStream = new FileInputStream(filepath);
byte[] buff =new byte[(int)new File(filepath).length()];
fileInputStream.read(buff);
fileInputStream.close();
Message message = new Message();
message.setSender(senderId);
message.setReceiver(receiverId);
message.setMesType(MessageType.MESSAGE_FILE_MES);
message.setFileBytes(buff);
message.setSendTime(new Date().toString()); //发送时间
//发送给服务端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
}
}
8.ClientConnectServerThread.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* 说明:
* 作者:汤观林
* 日期:2022年01月29日 18时
*/
public class ClientConnectServerThread extends Thread{
//该线程需要持有Socket
private Socket socket;
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
//
@Override
public void run() {
//因为Thread需要在后台和服务器通信,因此我们需要while循环
while(true){
System.out.println("客户端线程,等待从读取从服务器端发送的消息");
try {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程会阻塞在这里
Message message = (Message)objectInputStream.readObject();
//判断这个message的类型,然后做相应的业务处理
if(message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
//取出在线列表信息,并显示
String[] onlineUsers = message.getContent().split(",");
System.out.println("======当前在线用户列表========");
for(int i=0;i<onlineUsers.length;i++){
System.out.println("用户:"+onlineUsers[i]);
}
}else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){ //普通聊天消息
System.out.println(message.getSender()+"对"+message.getReceiver()+"说:"+message.getContent());
}else if(message.getMesType().equals(MessageType.MESSAGE_MANY_MES)){ //群聊
System.out.println(message.getSender()+"对大家说:"+message.getContent());
}else if(message.getMesType().equals(MessageType.MESSAGE_FILE_MES)){ //文件
byte[] fileBytes = message.getFileBytes();
FileOutputStream fileOutputStream = new FileOutputStream("H:\\117Java IO\\to\\koala.jpg");
fileOutputStream.write(fileBytes);
fileOutputStream.close();
}else if(message.getMesType().equals(MessageType.MESSAGE_NEWS_MES)){ //推送新闻
System.out.println("系统推送的消息:"+message.getContent());
}else{
System.out.println("是其他类型的message,暂时不处理....");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//为了更方便的得到socket
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
6.5.2 服务端QQServer工程
1.User.java
package com.tangguanlin.common;
import java.io.Serializable;
/**
* 说明:用户信息
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId; //用户id
private String password; //用户密码
public User() {
}
public User(String userId, String password) {
this.userId = userId;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.Message.java
package com.tangguanlin.common;
import java.io.Serializable;
/**
* 说明:客户端和服务端的通信消息对象
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender; //发送者
private String receiver; //接收者
private String content; //消息内容
private String mesType; //消息类型[可以在接口中定义消息类型]
private String sendTime; //发送时间
private byte[] fileBytes; //文件字节
private int fileLen; //文件长度
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
public byte[] getFileBytes() {
return fileBytes;
}
public void setFileBytes(byte[] fileBytes) {
this.fileBytes = fileBytes;
}
public int getFileLen() {
return fileLen;
}
public void setFileLen(int fileLen) {
this.fileLen = fileLen;
}
}
3.MessageType.java
package com.tangguanlin.common;
/**
* 说明:消息类型:表示消息类型有哪些
* 作者:汤观林
* 日期:2022年01月29日 15时
*/
public interface MessageType {
//1.在接口中,定义了一些常量
//2.这些常量的值,代表不用的消息类型
String MESSAGE_LOGIN_SUCCESSED = "1"; //表示登录成功
String MESSAGE_LOGIN_FAIL = "2"; //表示登录失败
String MESSAGE_COMM_MES = "3"; //私聊
String MESSAGE_GET_ONLINE_FRIEND = "4" ; // 要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND = "5" ; //返回在线用户列表
String MESSAGE_CLIENT_EXIT = "6"; //客户端请求退出
String MESSAGE_MANY_MES = "7"; //群聊
String MESSAGE_FILE_MES = "8"; //文件
String MESSAGE_NEWS_MES = "9"; //推送新闻
}
4.ManageServerConnectClientThread.java
package com.tangguanlin.service;
import java.util.HashMap;
import java.util.Set;
/**
* 说明:管理 服务器端连接到客户端的线程的管理类
* 作者:汤观林
* 日期:2022年01月29日 20时
*/
public class ManageServerConnectClientThread {
//把多个线程,放入到hashmap集合中,key就是用户id,value就是一个线程
private static HashMap<String,ServerConnectClientThread> hashmap = new HashMap<String,ServerConnectClientThread>();
//返回所有线程
public static HashMap<String,ServerConnectClientThread> getHashmap(){
return hashmap;
}
//添加线程到hm集合
public static void addServerConnectClientThread(String userId,ServerConnectClientThread serverConnectClientThread){
hashmap.put(userId,serverConnectClientThread);
}
//从集合中删除进程
public static void removeServerConnectClientThread(String userId){
hashmap.remove(userId);
}
//根据userId获取线程
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hashmap.get(userId);
}
//返回在线用户列表
public static String getOnlineUser(){
//集合遍历
Set<String> onlineUsers = hashmap.keySet();
StringBuffer sb = new StringBuffer();
for(String key:onlineUsers){
sb.append(key).append(",");
}
String sb_temp = sb.toString();
return sb_temp.substring(0,sb_temp.length()-1);
}
}
5.QQFrame.java
package com.tangguanlin.frame;
import com.tangguanlin.service.QQServer;
/**
* 说明:该类创建了QQServer对象
* 作者:汤观林
* 日期:2022年01月29日 21时
*/
public class QQFrame {
public static void main(String[] args) throws Exception {
new QQServer();
}
}
6.QQServer.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import com.tangguanlin.common.User;
import javax.sound.midi.Soundbank;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**
* 说明:这是服务器,在监听9999,等待客户端的连接,,并保持通信
* 作者:汤观林
* 日期:2022年01月29日 20时
*/
public class QQServer {
private ServerSocket serverSocket = null;
//创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法的
private static HashMap<String,User> validUsers = new HashMap<>();
//在静态代码块,初始化 validUsers
static {
validUsers.put("100",new User("100","123456"));
validUsers.put("200",new User("200","123456"));
validUsers.put("300",new User("300","123456"));
validUsers.put("至尊宝",new User("至尊宝","123456"));
validUsers.put("紫霞仙子",new User("紫霞仙子","123456"));
validUsers.put("菩提老祖",new User("菩提老祖","123456"));
}
//验证用户是否有效 过关斩将
public boolean checkUser(String userId,String password){
User user = validUsers.get(userId);
if(user==null){
return false;
}
if(!password.equals(user.getPassword())){
return false;
}
return true;
}
public QQServer() throws Exception {
System.out.println("服务器端在9999端口监听...");
//推送新闻线程
ServerPushNewsThread serverPushNewsThread = new ServerPushNewsThread();
serverPushNewsThread.start();
serverSocket = new ServerSocket(9999);
while(true){
//当和某个客户端建立连接后,会继续监听所以用while循环
Socket socket = serverSocket.accept();
//得到socket关联的对象的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//读取客户端发送的user对象
User user = (User)objectInputStream.readObject();
Message message = new Message();
//验证用户名和密码 用户名:100 密码:123456 先死后活
if(checkUser(user.getUserId(),user.getPassword())){ //验证成功
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESSED);
//将message对象回复给客户端
objectOutputStream.writeObject(message);
//创建一个线程,和客户端保持通信,该线程持有socket对象
ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
//启用该线程
serverConnectClientThread.start();
//把该线程对象,放入到一个集合中进行管理
ManageServerConnectClientThread.addServerConnectClientThread(user.getUserId(),serverConnectClientThread);
}else{ //验证失败
System.out.println("用户id="+user.getUserId()+"验证失败");
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESSED);
//将message对象回复给客户端
objectOutputStream.writeObject(message);
socket.close();
}
}
}
}
7.ServerConnectClientThread.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import javax.sound.midi.Soundbank;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Set;
/**
* 说明:服务器保持通信的线程类,必须持有socket
* 作者:汤观林
* 日期:2022年01月29日 20时
*/
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId; //连接到服务端的用户id
public ServerConnectClientThread(Socket socket,String userId){
this.socket = socket;
this.userId = userId;
}
@Override
public void run() { //线程处于一个run的状态,可以发送/接收消息
while(true){
System.out.println("服务端和客户端保持通信"+userId+",读取数据...");
try {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Message message = (Message)objectInputStream.readObject();
//判断这个message的类型,然后做相应的业务处理
if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
//客户端要在线用户列表
System.out.println(message.getSender()+"要在线用户列表");
String onlineUser = ManageServerConnectClientThread.getOnlineUser();
Message message1 = new Message();
message1.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message1.setContent(onlineUser);
message1.setReceiver(message.getSender());
//返回给客户端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message1);
}else if(message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
//从线程集合中删除线程
System.out.println(userId+"退出系统");
ManageServerConnectClientThread.removeServerConnectClientThread(userId);
socket.close(); //关闭当前前程的socket 相当于断了连接 关闭连接
break; //跳出循环,线程执行完毕,不需要人为关闭,运行完会自动结束
}else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){ //私聊
//消息不用动,直接转发
ObjectOutputStream objectOutputStream = new ObjectOutputStream( ManageServerConnectClientThread.getServerConnectClientThread(message.getReceiver()).getSocket().getOutputStream());
//转发,如果客户不在线,可以保存到数据库,这样就可以实现离线留言
objectOutputStream.writeObject(message);
}else if(message.getMesType().equals(MessageType.MESSAGE_MANY_MES)){ //群聊
HashMap<String, ServerConnectClientThread> hashmap = ManageServerConnectClientThread.getHashmap();
Set<String> keySet = hashmap.keySet();
for(String userId:keySet){
if(!userId.equals(message.getSender())){
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageServerConnectClientThread.getServerConnectClientThread(userId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
}
}
}else if(message.getMesType().equals(MessageType.MESSAGE_FILE_MES)){
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
ManageServerConnectClientThread.getServerConnectClientThread(userId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
} else{
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
8.ServerPushNewsThread.java
package com.tangguanlin.service;
import com.tangguanlin.common.Message;
import com.tangguanlin.common.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
/**
* 说明: 服务器端推送新闻线程
* 作者:汤观林
* 日期:2022年01月30日 22时
*/
public class ServerPushNewsThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("请输入要推送的新闻:");
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
Message message = new Message();
message.setMesType(MessageType.MESSAGE_NEWS_MES);
message.setContent(next);
HashMap<String, ServerConnectClientThread> hashmap = ManageServerConnectClientThread.getHashmap();
Set<String> keySet = hashmap.keySet();
for(String userId:keySet){
if(!userId.equals(message.getSender())){
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream( ManageServerConnectClientThread.getServerConnectClientThread(userId).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}