Java网络编程学习笔记

文章目录

  • 1 网络基础
    • 1.1 网络通信
    • 1.2 网络
    • 1.3 IP地址
    • 1.5 域名
    • 1.6 端口号
    • 1.7 网络通信协议
    • 1.8 TCP协议
    • 1.9 UDP协议
  • 2 IntetAddress IP地址
    • 2.1 getLocalHost()
    • 2.2 getByName()
    • 2.3 getHostAddress()
    • 2.4 getHostName()
  • 3 Socket
  • 4 TCP编程
    • 4.1 基本介绍
    • 4.2 应用案例1(使用字节流)
    • 4.3 应用案例2(使用字节流)
    • 4.4 应用案例3(使用字符流)
    • 4.5 应用案例4(上传文件)
    • 4.6 应用案例5(下载文件)
    • 4.7 netstat指令
    • 4.8 TCP连接秘密
  • 5 UDP编程
    • 5.1 基本介绍
    • 5.2 基本流程
    • 5.3 应用案例1
    • 5.4 应用案例2
  • 6 多用户通信系统
    • 6.1 为什么选择这个项目
    • 6.2 项目开发流程
    • 6.3 多用户通信需求
    • 6.4 界面设计和思路分析
      • 6.4.2 拉取在线用户列表
      • 6.4.3 无异常退出
      • 6.4.4 私聊
      • 6.4.5 群聊
      • 6.4.6 发文件
      • 6.4.7 服务器推送新闻
      • 6.4.8 离线留言和发文件
    • 6.5 代码实现
      • 6.5.1 客户端QQClient工程
      • 6.5.2 服务端QQServer工程

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.关闭流和socketinputStream.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.getOutputStreamOutputStream outputStream = socket.getOutputStream();//3.通过输出流,写入数据到数据通道outputStream.write("hello server".getBytes());//4.关闭流对象和socketoutputStream.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.关闭流和socketinputStream.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.getOutputStreamOutputStream 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.关闭流对象和socketoutputStream.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.关闭流和socketbufferedReader.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.getOutputStreamOutputStream 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.关闭流对象和socketbufferedWriter.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.关闭流和socketbufferedWriter.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.关闭流对象和socketbufferedReader.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.关闭流和socketbos.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.关闭流对象和socketbos.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最大64Kbyte[] 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;  //用户idprivate 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)){ //登录成功//创建一个和服务器端保存通信的线程-->创建一个线程类 ClientConnectServerThreadClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);//启动客户端的线程clientConnectServerThread.start();//我们将线程放入到集合管理ManageClientConnectServerThread.addClientConnectServerThread(userId,clientConnectServerThread);b = true;}else{//如果登录失败,我们就不能启动和服务器通信的线程,关闭socketsocket.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{//该线程需要持有Socketprivate Socket socket;public ClientConnectServerThread(Socket socket){this.socket = socket;}//@Overridepublic 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();}}}//为了更方便的得到socketpublic 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;  //用户idprivate 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<>();//在静态代码块,初始化  validUsersstatic {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; //连接到服务端的用户idpublic ServerConnectClientThread(Socket socket,String userId){this.socket = socket;this.userId = userId;}@Overridepublic 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{@Overridepublic 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();}}}}}
}

15Java网络编程学习笔记相关推荐

  1. java 网络编程学习笔记

    java 网络编程学习笔记 C/S模式:客户端和服务器 客户端创建流程 1 1.建立Socket端点 2 3 Socket s = new Socket(绑定地址, 绑定端口); 2.确认源数据方式和 ...

  2. [Linux网络编程学习笔记]索引

    一.Linux基本知识 [学习笔记]Linux平台的文件I/O操作 [学习笔记]Linux平台的文件,目录及操作 [Linux学习笔记]标准输入输出 [Linux学习笔记]进程概念及控制 [Linux ...

  3. 编程开发:Linux网络编程学习笔记

    非常全面.通俗易懂.值得借鉴的Linux网络编程学习笔记.关键字:linux linux编程 网络编程 linux网络编程 下载地址:点我下载 特别说明:本资源收集于网络,版权归原作者及版权商所有,仅 ...

  4. python网络编程学习笔记(二)

    python网络编程学习(四) 多用途客户端协议 一.ftp 功能:上传下载文件,删除命名文件,建立删除目录,自动压缩,保存目录 1.代码: #coding=utf-8 '''连接远程ftp服务器,显 ...

  5. Linux网络编程学习笔记

    声明:1.未经过原作者许可,不可用于商业行为:2.本笔记仅用于知识学习,如有侵权,立即删除. 1.学习链接 黑马程序员-Linux网络编程:https://www.bilibili.com/video ...

  6. python网络编程需要学什么,python网络编程学习笔记(五):socket的一些补充 Python 网络编程需要学习哪些网络相关的知识...

    python的socket编程问题hdr = recvall(s, 5) if hdr is None: print 'Unexpected EOF receivingstruct在unpack的时候 ...

  7. Linux网络编程学习笔记(TCP)

    文章目录 1 字节序 1.1 定义 1.2 字节序转换函数 2 Socket地址 2.1 通用socket地址(实际开发不使用) 2.2 专用socket地址 2 IP地址转换 3 TCP通信流程 3 ...

  8. Linux 网络编程学习笔记

    前言: 本文是学习<Linux 高性能服务器编程(游双 著)>时所记录的重点知识. 一.TCP/IP 协议族 二.IP 协议详解 三.TCP 协议详解 四.HTTP 通信 五.Linux ...

  9. 网络编程学习笔记--1.socket可读可写条件

    欢迎转载,转载请注明原文地址:http://blog.csdn.net/majianfei1023/article/details/45788591 socket可读可写条件,经常做为面试题被问,因为 ...

最新文章

  1. 你有一张世界互联网大会的门票待领取!数字经济人才专场报名开启
  2. excel函数大全_让你的EXCEL工作效率翻倍的函数大全
  3. PHP-Zend引擎剖析之Hello World(二)
  4. Spring的lazy-init详解
  5. 事务策略: 了解事务陷阱--转
  6. C# 对程序窗口进程和进程ID
  7. Mathematica图片局部变色
  8. 网站本地调试工具_一款Web调试代理工具:Fiddler
  9. php可以更改html后缀名嘛,请问你们怎么将html的文件的内容改变为php
  10. soapui连接oracle,myeclipse 安装soapui插件
  11. win10系统,使用Windows照片查看器打开图片
  12. python弹幕拼脸_50行代码帮你搞定!手把手教你把视频弹幕变成想要的形状
  13. android拼图小游戏代码,Android实现拼图小游戏
  14. PDF提取页面方法,如何从PDF文件中提取页面
  15. java se运行环境_Java运行环境Java SE Runtime Environment (JRE) 下载
  16. python timeit.timer_python之timeit模块
  17. 电子电路笔记----基本放大电路--半导体三极管
  18. 数据库(笔记)——关系代数以及相关运算
  19. 图形学数学基础之1D采样分布计算方法Inverse Method
  20. Flutter 多渠道打包详解(埋点统计系列文章)

热门文章

  1. 饥荒服务器怎么改成无限模式,tgp饥荒服务器搭建怎么搭建无尽模式
  2. 枪毙病狗问题(看帽子问题类型)
  3. linux bc 找不到命令,Linux bc命令
  4. vue实现一个具有添加、删除、按关键字过滤的列表管理
  5. 陈数:拥有“闺蜜”是幸运的
  6. app调用微信支付接口第一次成功,后面就调取不成功
  7. 评点一下新浪微博的技术架构
  8. c语言源码转流程图工具在线,c语言源码转流程图工具_应用|1+1构造地表最强流程图工具...
  9. 华山全敏还是全劲_楚留香:最全的华山攻略,一个老华山的毕身所学!
  10. 数据库 (学习笔记)