详细课设报告以及 C#、java 源码见
码云: https://gitee.com/xyy9/socket
github: https://github.com/XYYhub/socket
建立数据库略过
服务端设计的总体框架主要为以下几个步骤:
a. 创建用于监听的套接字(socket)。
b. 将套接字绑定到本地地址和端口上(bind)。
c. 将套接字设为监听模式(listen)。
d. 等待客户请求(accept),此处要不断的调用accept。
e. 通信(send/receive)。
PHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
刚开始做的时候一直都找不到正确的IP,后遍历ip地址列表获取到的所有ip地址然后筛选ipv4地址,将最后显示出来的ipv4地址赋给ipAddress当作本机IP;并且将客户端的ip地址设置为此地址
主要函数:
1.socket()函数;
2.connect()函数,填写服务端的地址与端口;
3.send()函数;
4.recv()函数。
与服务端基本相同知识点,可比较文末源码
需要添加MySql.Data.dll引用连接数据库
代码页添加using MySql.Data.MySqlClient
string str = "server=localhost; User Id=root; password=root; Database=server";
//连接MySQL的字符串
MySqlConnection mycon = new MySqlConnection(str);//实例化链接
mycon.Open();//开启连接
MySqlCommand mycmd = new MySqlCommand("insert into tcp(type,id,sn,power,state,time) values('" + type1 + "','" + id1 + "','" + sn1 + "','" + power1 + "','" + state1 + "','" + time1 + "')", mycon);
if (mycmd.ExecuteNonQuery() > 0)
{
Console.WriteLine("数据插入成功!");
}
mycon.Close();//关闭
服务端和客户端的双向通信,在基于服务端接收完客户端的信息后,需要向客户端发送反馈信息,以告知客户端,信息已经成功被接收,建立此反向通信机制后,也可通过从服务端发送相关控制信息给客户端,来对客户端进行相关操作。
在服务端接收完毕客户端数据,并将数据存入数据库成功后。服务端开始发送反馈信息
1、服务端发送:
byte[] dataToC = System.Text.Encoding.ASCII.GetBytes("Received successfully");
handler.Send(dataToC);
2、客户端接收:
byte[] bytes = new Byte[1024];
int bytesRec = clientSocket.Receive(bytes);
string dataBack = Encoding.ASCII.GetString(bytes, 0, bytesRec);
Console.WriteLine(dataBack);
就此即可完成服务端的自定义语句,在客户端接收并显示。
3.设计思想:
在服务端建立控制开关机制,以判断向客户端传输的具体反馈信息,客户端根据收到的反馈信息判断具体的执行任务。
服务端控制函数:
初步采用命令台内,键盘输入进行控制,在每一次循环建立端口监听时,都进行对键盘的监听,键盘输入相应的终止按键,则switch语句便执行相应的终止语句,否则默认跳出判断语句。以下为相应代码。
//非阻塞式监听键盘输入
if (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.F2:
Console.WriteLine("You pressed F2!");
byte[] data = System.Text.Encoding.ASCII.GetBytes("ShutDown");
handler.Send(data);
break;
default:
break;
}
}
客户端判断函数:
if (dataBack == "ShutDown")
{
clientSocket.Close();
}
进一步优化:
为便于连接GUI控制,应将判断内容改为具体的某一个变量,而非对键盘输入的监听,在服务端与客户端都应以语句中的state的值为“on”或“off”来控制客户端的状态。
设计思想:在服务端分割信息存入数据库后,用JAVA连接数据库,运用JfreeChart实现设备电压变化的可视化以及动态更新。
主要步骤如下:
用于连接Mysql数据库。
主要代码如下:
Connection conn = null;
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/server?serverTimezone=UTC";
String username = "root";
String password = "******";
conn = DriverManager.getConnection(url,username,password);
并包裹try/catch异常获取,打印错误提示以及错误信息。
用于创造存储Mysql中每条数据的类,并构造get函数用于获取确定信息,构造set函数用于设定或者修改具体信息。
类内的具体属性值如以下代码,一一对应于信息传递中规定的信息格式,即数据库中的数据格式,全部定义为String字符串类型。为了避免外部的随意干涉,设定为private私有属性。
public class Entity {
private String type;
private String id;
private String sn;
private String power;
public String state;
private String time;
为了方便对类内的属性进行获取和设定,需要对每个属性都构造get以及set函数,以下为Type属性的get以及set函数,其余省略,不再赘述。
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
…………
}
用于构造各类与数据库相关的功能函数,比如getAll() 获取数据库中的全部数据,getTen() 获取数据库中最新的十条数据,以及update() 修改函数,用于修改数据库中的某条数据。
以下为主要源代码,因为获取的结果为多个类,所以设定获取函数的类型为java中的ArrayList数组链表,此函数既拥有数组的特性,也拥有链表的特性,极为方便。
public ArrayList<Entity> getAll(){
ArrayList<Entity> ar = new ArrayList<Entity>();
设定conn调用DBUtil中的连接数据库函数,ps为执行语句函数,rs为结果存放函数。
Connection conn = DBUtil.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
设定sql语句如下,并执行,且存放执行结果。
String sql = "select type,id,sn,power,state,time from tcp";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
以while循环不断调用Entity中的set函数,保存获取的数据库信息至新建的ArrayList类中。
while(rs.next()) {
Entity ent = new Entity();
ent.setType(rs.getString("type"));
ent.setId(rs.getString("id"));
ent.setSn(rs.getString("sn"));
ent.setPower(rs.getString("power"));
ent.setState(rs.getString("state"));
ent.setTime(rs.getString("time"));
ar.add(ent);
}
至此数据全部保存在ArrayList ar中。最后return ar函数即可获得结果。需要注意的是一定要记得释放rs、ps、conn三个函数,并且注意要依次释放。
将SQL语句改为
"select * from tcp Order By time Desc limit 10"
即可编写获取最新的十个数据的功能函数 getTen(),为了给gui控制界面做准备,也写了一个修改函数update,用以从java更改数据库中的某个ID的设备的状态,以进行开启和关闭操作,具体仍有待实现,此为java数据库接口,主要源码如下:
String sql = "UPDATE tcp SET state= ? WHERE id=?";
ps = conn.prepareStatement(sql);
ps.setString(1, ent.getState());
用于将数据通过JfreeChart实现数据库中的信息可视化,绘制成折线图显示近十条数据的变化趋势。并构造定时器,实现每隔一秒动态刷新,重新获取最新的十条数据库内数据。
JFreeChart是JAVA平台上的一个开放的图表绘制类库。它完全使用JAVA语言编写,是为applications, applets, servlets 以及JSP等使用所设计。JFreeChart可生成饼图(pie charts)、柱状图(bar charts)、散点图(scatter plots)、时序图(time series)、甘特图(Gantt charts)等等多种图表,并且可以产生PNG和JPEG格式的输出。
本次我们使用它的折线图绘制。要使用JfreeChart需要官网下载Jcommon以及JfreeChart两个包,目前最新的包为jcommon-1.0.23.jar以及jfreechart-1.0.19.jar下载完毕后导入工程,并构造路径,即可使用JfreeChart。
通过StandardChartTheme 可以设置Chart的外观格式,如字体大小等。
StandardChartTheme mChartTheme = new StandardChartTheme("CN");
mChartTheme.setLargeFont(new Font("黑体", Font.BOLD, 20));
mChartTheme.setExtraLargeFont(new Font("宋体", Font.PLAIN, 15));
mChartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 15));
通过for循环依次设定折线图的数据集。
ArrayList<Entity> ar=new DAO().getTen();
int index = ar.size()-1;
for(Entity ne; index>=0 ;index--) {
ne = ar.get(index);
mDataset.addValue(Double.valueOf(ne.getPower()), "设备"+ne.getId(), ne.getTime().split(" ")[1]);
}
新建折线图,设定表标题及x轴y轴及其他相关信息。
JFreeChart mChart = ChartFactory.createLineChart(
"电压波动折线图",//图名字
"时间",//横坐标
"电压",//纵坐标
mDataset,//数据集
PlotOrientation.VERTICAL,
true, // 显示图例
true, // 采用标准生成器
false);// 是否生成超链接
最终实现以下画面4.22,以电压为纵坐标,时间每秒为横坐标,显示电压波动状况。
将数据集设置函数放置在while无限循环中,并设置线程休眠1000ms,以实现每秒动态更新折线图。
while(true){
mPlot.setDataset(GetDataset());
Thread.sleep(1000);
}
在动态实现java折线图显示最新的十条数据的时候,由于SQL语句为
select * from tcp Order By time Desc limit 10
即将数据按时间顺序倒序排序,并输出前十个,这导致了越新的数据会保存在数组的越前面,使输出绘制折线图的时候,最新的数据显示在了折线图的左侧,整个折线图动态更新时,会整体从左向右移动,这不符合传统的用户使用逻辑,所以需要对从数据库中提取出的数据,在赋值给折线图数据集时,进行逆序操作。具体更改如下:
原代码:
ArrayList<Entity> ar=new DAO().getTen();
for(Entity ne:ar) {
mDataset.addValue(Double.valueOf(ne.getPower()), "设备"+ne.getId(), ne.getTime().split(" ")[1]);
}
更改后代码:
ArrayList<Entity> ar=new DAO().getTen();
int index = ar.size()-1;
for(Entity ne; index>=0 ;index--) {
ne = ar.get(index);
mDataset.addValue(Double.valueOf(ne.getPower()), "设备"+ne.getId(), ne.getTime().split(" ")[1]);
}
先用size()获取数组大小,再以for循环依次从数组最末尾依次递减,进行数据集添加。更改后实现了动态刷新的方向更改。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
using MySql.Data;
using MySql.Data.MySqlClient;
public class SynchronousSocketListener {
// Incoming data from the client.
public static string data = null;
public static void StartListening() {
// Data buffer for incoming data.
byte[] bytes = new Byte[1024];
// Establish the local endpoint for the socket.
// Dns.GetHostName returns the name of the
// host running the application.
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
Console.WriteLine("本机主机名:"+Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
Console.WriteLine("本机IPv4地址:"+ipAddress);
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8023);
// Create a TCP/IP socket.
Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
// Bind the socket to the local endpoint and
// listen for incoming connections.
try {
listener.Bind(localEndPoint);
listener.Listen(10);
int allCount = 0;
// Start listening for connections.
while (true) {
Console.WriteLine("Waiting for a connection...");
// Program is suspended while waiting for an incoming connection.
Socket handler = listener.Accept();
data = null;
//存放即时信息
string msgnow = null;
// An incoming connection needs to be processed.
while (true)
{
bytes = new byte[1024];
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
//赋值即时信息
msgnow = Encoding.ASCII.GetString(bytes, 0, bytesRec);
//打印即时信息
Console.WriteLine("Text received now: {0}", msgnow);
//非阻塞式监听键盘输入
if (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.F2:
Console.WriteLine("You pressed F2!");
byte[] data = System.Text.Encoding.ASCII.GetBytes("ShutDown");
handler.Send(data);
break;
default:
break;
}
}
string type1 = msgnow.Split(' ')[1];
string id1 = msgnow.Split(' ')[3];
string sn1 = msgnow.Split(' ')[5];
string power1 = msgnow.Split(' ')[7];
string state1 = msgnow.Split(' ')[9];
string time1 = msgnow.Split(' ')[11] + ' ' + msgnow.Split(' ')[12];
string str = "server=localhost;User Id=root;password=oipopo09;Database=server";//连接MySQL的字符串
MySqlConnection mycon = new MySqlConnection(str);//实例化链接
mycon.Open();//开启连接
MySqlCommand mycmd = new MySqlCommand("insert into tcp(type,id,sn,power,state,time) values('" + type1 + "','" + id1 + "','" + sn1 + "','" + power1 + "','" + state1 + "','" + time1 + "')", mycon);
if (mycmd.ExecuteNonQuery() > 0)
{
Console.WriteLine("数据插入成功!");
}
// Console.ReadLine();
mycon.Close();//关闭
byte[] dataToC = System.Text.Encoding.ASCII.GetBytes("Received successfully");
handler.Send(dataToC);
}
// Show the data on the console.
Console.WriteLine( "Text received all: {0}", data);
// Echo the data back to the client.
byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
Console.WriteLine("\nPress ENTER to continue...");
Console.Read();
}
public static int Main(String[] args) {
StartListening();
return 0;
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace tcp
{
class Program
{
private const int portnum = 8023;
private const string hostname = "169.254.12.232";
//hostname是服务器当前网络下的ip
static void Main(string[] args)
{
while (true)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(hostname, portnum);
//Connect方法使用指定的IP地址和端口号将客户端连接到远程TCP主机
Console.WriteLine("Connect Complete");
try
{
if (clientSocket.Connected)
{
// 如果客户与服务器有连接,并且还未断,则允许发送信息
while (true)
{
String guid = Guid.NewGuid().ToString();//唯一标识
DateTime dt = new DateTime();
dt = System.DateTime.Now;
String strTime = dt.ToString("yyyy-MM-dd HH:mm:ss");
Random powerRan = new Random();
int power = powerRan.Next(100, 1000);
bool state = true;
String stateStr = null;
if (state)
{
stateStr = "on";
}
else if(!state)
{
stateStr = "off";
}
string s = "type:" + " data " + "id:" + " 1001 " + "sn: " + guid + " power: " + power + " state: " + stateStr + " time: " + strTime + "\r\n";
byte[] data = System.Text.Encoding.Default.GetBytes(s);
clientSocket.Send(data);
Console.WriteLine("Send Complete");
// 接收数据
byte[] bytes = new Byte[1024];
int bytesRec = clientSocket.Receive(bytes);
string dataBack = Encoding.ASCII.GetString(bytes, 0, bytesRec);
if (dataBack == "ShutDown")
{
clientSocket.Close();
}
Console.WriteLine(dataBack);
System.Threading.Thread.Sleep(1000);
}
}
}
catch (Exception e)
{
Console.WriteLine("Error!" +e.StackTrace);
Console.ReadLine();
}
System.Threading.Thread.Sleep(4000);
}
}
}
}
import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import dao.DAO;
import entity.Entity;
public class charts {
@SuppressWarnings("unused")
public static void main(String[] args) {
StandardChartTheme mChartTheme = new StandardChartTheme("CN");
mChartTheme.setLargeFont(new Font("黑体", Font.BOLD, 20));
mChartTheme.setExtraLargeFont(new Font("宋体", Font.PLAIN, 15));
mChartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 15));
ChartFactory.setChartTheme(mChartTheme);
CategoryDataset mDataset = GetDataset();
JFreeChart mChart = ChartFactory.createLineChart(
"电压波动折线图",//图名字
"时间",//横坐标
"电压",//纵坐标
mDataset,//数据集
PlotOrientation.VERTICAL,
true, // 显示图例
true, // 采用标准生成器
false);// 是否生成超链接
CategoryPlot mPlot = (CategoryPlot)mChart.getPlot();
mPlot.setBackgroundPaint(Color.LIGHT_GRAY);
mPlot.setRangeGridlinePaint(Color.BLUE);//背景底部横虚线
mPlot.setOutlinePaint(Color.RED);//边界线
ChartFrame mChartFrame = new ChartFrame("电压波动折线图", mChart);
mChartFrame.pack();
mChartFrame.setVisible(true);
while(true){
mPlot.setDataset(GetDataset());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static CategoryDataset GetDataset()
{
DefaultCategoryDataset mDataset = new DefaultCategoryDataset();
ArrayList<Entity> ar=new DAO().getTen();
int index = ar.size()-1;
for(Entity ne; index>=0 ;index--) {
ne = ar.get(index);
//System.out.println("消息类型:"+ne.getType()+"\t设备ID:"+ne.getId()+"\t设备序列号:"+ne.getSn()+"\t电压:"+ne.getPower()+"\t设备状态:"+ne.getState()+"\t时间:"+ne.getTime());
mDataset.addValue(Double.valueOf(ne.getPower()), "设备"+ne.getId(), ne.getTime().split(" ")[1]);
}
return mDataset;
}
}