最近学期末了,Java要求做一个课程设计,需要用Java网络编程、JDBC、SWT进行设计。研究了 很久之后终于在各路大佬的指引下成功完工。
根据在计网学习的知识,通信大致分为两种,用Socket,ServerSocket类实现的TCP服务,和用DatagramSocket类实现的UDP通信服务。
详细代码使用可以参考这位大佬的文章。Java 网络编程_ZaynFox的博客-CSDN博客_java网络编程
首先来说一下如何使用Socket,ServerSocket类实现的TCP服务。实现原理如下面这位大佬的图。
Socket实现即时通讯服务(一)_一只菜鸟.....的博客-CSDN博客_socket实时通信
从这里我们可以很清晰的看出来分为两个步骤。
第一步是监听,也就是accept()之前的内容
第二步是收发数据。是在建立连接之后的内容。
首先我们来讲第一步——监听,Socket类是如何建立通信的。
Socket的通信方式类似于传统的C/S模式,首先Client客户端要创建一个Socket类的对象,通过构造函数Socket(String 发送IP,int 发送进程占用的端口号)生成一个Socket对象。
然后客户机创建一个ServerSocket类的对象,通过构造函数ServerSocket(int 接收占用的端口号)。并且使用serverSocket.accept()方法进入“接听阻塞”状态。
Socket在使用构造函数创建对象的同时,会占用发送端口和ServerSocket尝试进行TCP连接。如果创建Socket时使用的端口,恰好有ServerSocket在监听,就可以建立连接。
建立连接的方式是,通过serverSocket.accept()返回一个Socket对象。
//Client 端
Socket socket = new Socket("127.0.0.1",1314);
//Server 端
ServerSocket serverSocket = new ServerSocket(1314);//与上面的端口要一致。
Socket socket2 = serverSocket.accept();
到此连接部分就成功了,接下来我们就进入了第二步。如何通过serverSocket.accept()返回的Socket对象进行消息的接收发送。
//输出数据 拿过话筒
/*
* 连接好的socket获取输出流获取的是一个话筒, 返回值是OutputStream
* 但是DataOutputStream用的更多,所以类型转化一下
*/
OutputStream outToServer = null;
System.out.println("接收输出流,此时" + socket);
outToServer = socket.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF(message); // 向流中写入数据
out.flush(); // 清空流
看过一个形象的说法,通过socket.getOutputStream()输出数据就像拿过了话筒一样。
socket.getOutputStream()返回的是一个OutputStream类型的输出流,通过实例化一个对象 outToServer接收Socket通信中的输出流,就可以得到“发言权”。就是直接在outToServer里面写数据就可以实现通信发数据的功能了。
非常的简单和神奇,这个流就好像C中指针一样,指向通信中的通信信道。
String receiveMsg = "";
InputStream inFromServer = socket.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
receiveMsg = in.readUTF(); // 从输入流中读取数据
和接收数据一样,通过socket.getInputStream()可以得到一个输入流,通过输入流的读取函数我们就可以读出其中的字符,实现接收信息的功能。
看到这里你肯定觉得实现实时通信,至少是实现通信是一件非常简单的事情了。下面给你一串代码,我们来一起试试会有什么样的问题。
//Client 端
public class Client{
}
//Server 端
public class Server{
}
//主函数
public class TCPCommunication{
public void main(){
}
}
可以看出来,这个只能实现一对一的C/S通信(也就是只能一个人一直说话,另一个人只能听不能说)。并且还不能断开连接,毕竟我们确实没有设置让while(true)停止的语句。
此时比较聪明的读者就发下了,只要在同一台电脑上同时运行两种代码不就好了吗?他就可以实现发送和接收两种功能了。
上面的想法很好也是我一开始的想法,因此我们再放一段代码我们来一次看看。问题远不止于此。
可以看出来有 xxx 问题。
接下来我们加入我们的主角Thread类。
这样就解决了连接不能多次监听的问题。但是这样依然没有解决 ServerSocket.accept()阻塞的进程 以及 GetInputStraem带来的阻塞问题。
我们可以通过调用线程池来解决,通过最终解决了问题。
最终代码如下
github 链接:
gitee 链接:
百度云链接:
package NetCommunication;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import DATABASE.database;
public class Net {
public Socket socket = null; // 全局的socket变量 用于表示当前对象所建立的唯一的连接
ServerSocket serverSocket;
/*
* 流程就是首先打开页面的时候就创建一个ServerSocket 尝试接收
* 返回一个Socket后就成功建立连接了 接收成功就断开ServerSocket连接
*/
// 连接相关的各种标志
public boolean LinkSuccessFlag = false;// 没连接成功的时候就是0
// 端口和IP参数
public int ReceivePort = 12101; // 所有主机的接收端口和发送端口用一样的。
public int SendPort = 12101;
// 存入数据库的相关信息
public String myUserName;
public String toUserName;
// 设置一个set函数
public void setmyUserName(String myUserName) {
this.myUserName = myUserName;
}
public void settoUserName(String toUserName) {
this.toUserName = toUserName;
}
// 创建一个发送方 发送只需要一个单独的函数方法不需要线程类
public int createClient(String SendIP) {
// 主动连接他人。连接成功时候将成功连接的Flag赋值为1
// 参数都用外界传入
if (LinkSuccessFlag == false) {// 还没连接成功
System.out.println("还没被被动连接,现在开始主动连接" + SendIP);
try {
socket = new Socket(SendIP, SendPort);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
LinkSuccessFlag = true;
System.out.println("主动连接成功");
// 创建成功返回1
return 1;
} else {
System.out.println("已经被动连接成功了,不需要主动连接了");
System.out.println("被动连接到:" + socket);
return 1;
}
}
// 创建一个接收方 监听需要放在线程里面,因为会线程阻塞。
Thread TServer = new Server();
public class Server extends Thread {
public void run() {
createServer();
}
}
public void startServer() {
TServer.start();
}
public void createServer() {
if (LinkSuccessFlag == false) {// 还没连接成功 但是本项目使用过程中其实就直接在每个窗口打开的时候就进行连接了。
if (serverSocket != null) {
System.out.println("已经开始监听了,不需要在创建一个");
} else {
System.out.println("还没连接,现在开始被动监听,等待别人主动连接。监听端口是" + ReceivePort);
try {
serverSocket = new ServerSocket(ReceivePort);
serverSocket.setSoTimeout(0);//
System.out.println("开始accept监听");
socket = serverSocket.accept();
System.out.println("被连上了" + socket);
LinkSuccessFlag = true;
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
} else {
System.out.println("已经连接成功了,不需要被动监听了");
System.out.println("已经被动连接到了 socket:" + socket);
}
}
// 关闭接收方
public void endServer() {
try {
if (serverSocket != null) {
serverSocket.close(); // 关闭监听
System.out.println("进入endServer了,关闭的效果" + serverSocket);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*--------------------收发信息阶段 已经获取连接用的socket了-----------------------------------*/
/*
* 实现思路是通过连接的输入输出流进行通信。 断开通信的方式是使用标签不在允许通信
*/
// 收发相关的标签
public boolean AllowSRFlag = true;//允许收和发送信息
public void sendString(String message) {
if (AllowSRFlag == true) {
try {
/*
* 连接好的socket获取输出流获取的是一个话筒, 返回值是OutputStream
* 但是DataOutputStream用的更多,所以类型转化一下
*/
OutputStream outToServer = null;
System.out.println("接收输出流,此时" + socket);
outToServer = socket.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF(message); // 向流中写入数据
out.flush(); // 清空流
} catch (IOException ioException) {
ioException.printStackTrace();
}
} else {
System.out.println("收发已经不被允许了,已经点过断开连接按钮辽");
}
}
/* 终止函数 */
public void endSendandReceive() {
AllowSRFlag = false;
TReceiveStr.interrupt();
}
// 接收需要用线程,因为有监听和接收socket的原因一样
Thread TReceiveStr = new ReceiveString();
public class ReceiveString extends Thread {
public void run() {
receiveString();
}
}
/* 调用函数 */
public void startReceiveString() {
TReceiveStr.start();
}
public void receiveString() {
/* 接收线程里面开一个定时任务 循环接收 */
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("调用到接收监听函数");
if (AllowSRFlag == true) {
String receiveMsg = "没接收到";
try {
InputStream inFromServer = socket.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
receiveMsg = in.readUTF(); // 从输入流中读取数据
System.out.println("接收到对方发来" + receiveMsg);
/* 聊天记录插入数据库 */
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ");
Date date = new Date(System.currentTimeMillis());
String time = formatter.format(date);
System.out.println("输入数据库的内容为" + myUserName + toUserName + time + receiveMsg);
database insert = new database();
insert.InsertTalkRecord(myUserName, toUserName, time, receiveMsg);
// result = ""; //结果清空 便于下一次取新的数据
} catch (IOException e) {
receiveMsg = "接收出现异常,没接收到";
e.printStackTrace();
}
} else {
return;
}
}
};
// 在指定延迟0毫秒后开始,随后地执行以2000毫秒间隔执行timerTask
new Timer().schedule(timerTask, 0L, 2000L);
// return receiveMsg;
}
}
看完此文不知道你有没有一点收获呢?如果有的话,希望可以收获你一个免费的点赞~~。你的点赞和支持是我创作的最大动力。