SOCKET编写客户端和服务端通信,连接Mysql数据库,java实现动态监控

详细课设报告以及 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)。

  1. 定义一个数组 Byte[],目的是接收从客户端发送的数据的ASCLL码。
    用下面所示语句在服务端获取服务器当前网络下的ip,
PHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());

刚开始做的时候一直都找不到正确的IP,后遍历ip地址列表获取到的所有ip地址然后筛选ipv4地址,将最后显示出来的ipv4地址赋给ipAddress当作本机IP;并且将客户端的ip地址设置为此地址

  1. 将服务端的地址设置为ipAddress端口号设置为8023,以便和客户端实现交互。使用语句创建一个TCP/IP套接字,并且将本地ip地址ipAddress和端口号8023与套接字使用bind()语句绑定起来,
  2. 使用listen()监听并输出【Waiting for a connection…】等待连接请求。
  3. 使用while循环语句,让里面的判断值一直为ture来实现当客户端发送的消息包含结束符\r\n需要break出循环时,只能跳出4步骤的while()语句,转而会继续执行下面的语句(关闭连接)而不是服务端只接受一次数据就关闭连接。并定义一个字符串变量msgnow用来保存即时信息。
  4. 通过Receive语句从连接处接收一些数据,由于客户端在发送时字符串是无法直接发送给服务器的,因此客户端用Encoding将字符串编码为byte数组后才能发送,所以要将接收的byte数组通过ASCLL转化成原来的数据,并且赋值给msgnow打印输出,并且判断发来的数据是否带有结束符\r\n,如果有则跳出当前代码段转而执行下面的语句即关闭连接。

客户端

主要函数:
1.socket()函数;
2.connect()函数,填写服务端的地址与端口;
3.send()函数;
4.recv()函数。
与服务端基本相同知识点,可比较文末源码

C#连接数据库

需要添加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和MySQL实现数据的动态实时监控

设计思想:在服务端分割信息存入数据库后,用JAVA连接数据库,运用JfreeChart实现设备电压变化的可视化以及动态更新。
主要步骤如下:

设计DBUtil工具类模块。

用于连接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异常获取,打印错误提示以及错误信息。

设计Entity模块,

用于创造存储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;
		}
		…………
}

设计DAO模块

用于构造各类与数据库相关的功能函数,比如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());

设计Charts模块

用于将数据通过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循环依次从数组最末尾依次递减,进行数据集添加。更改后实现了动态刷新的方向更改。

服务端C#源码

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;
    }
}

客户端C#源码

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);

            }
        }
    }
}

JAVA中Charts类源码

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;
    }
}

发布了31 篇原创文章 · 获赞 2 · 访问量 6195

猜你喜欢

转载自blog.csdn.net/Yuyao_Xu/article/details/104162160