Modbus Tcp Server

// mbserver.c V2.1 1/18/01
// example multi-session Modbus/TCP server supporting class 0 commands

// This program should work under UNIX (as C or C++) or Win32 (as C or C++)

// the symbol WIN32 will be defined if compiling for Windows. Assume if it is not
// set that we are compiling for some form of UNIX

// V2.0 1/14/00 added 10 second idle timeout option
// V2.1 1/18/01 timeout was not being reset on successful traffic
// V2.2 7/27/01 defaults for EXE set to sessions = 100, listen() backlog set to same, timeout on

#ifndef WIN32

// various flavors of UNIX may spread their include files in different ways
// the set below is correct for Redhat Linux 5.1 and 6.1 and Ultrix V4.3A

#include <stdio.h> // printf
#include <errno.h> // errno
#include <unistd.h> // close
#include <sys/time.h> // timeval
#include <sys/socket.h> // socket bind listen accept recv send
#include <sys/ioctl.h> // FIONBIO
#include <netinet/in.h> // sockaddr_in sockaddr INADDR_ANY

typedef int SOCKET;
const int INVALID_SOCKET=(~(int)0);

// the following fake out the Winsock-specific routines

typedef struct WSAData { int w;} WSADATA;
int WSAStartup(int v, WSADATA *pw) { return 0;}
int WSACleanup() {}
int WSAGetLastError() { return (errno);}
int closesocket(SOCKET s) { close(s); }
int ioctlsocket(SOCKET s, long cmd, unsigned long *valp) { ioctl(s, cmd, valp); }
#define WSAEWOULDBLOCK EWOULDBLOCK

#else // must be WIN32

#include <winsock.h>
#include <stdio.h> // printf
#include <time.h> // time, difftime

#endif



///
// configuration settings
///

#define numSockets 100	/* number of concurrent server sessions */
#define num4xRegs 10000	/* number of words in the 'register table' maintained by this server */
#define IDLE_TIMEOUT 1	/* set if the session idle timeout to be enforced */

///
// data structure definitions
///

// maintain a data structure per session,into which can be stored partial msgs

struct fragMsg 
{
    int fragLen;                // length of request assembled so far
    unsigned char fragBuf[261]; // request so far assembled
};

///
// global data definition
///

struct fragMsg frag[numSockets];
time_t openTime[numSockets];

// make a 'state table' to read and write into

unsigned short reg4x[num4xRegs];

///
// utility routines
///


// extract a 16-bit word from an incoming Modbus message

unsigned short getWord(unsigned char b[], unsigned i) 
{
    return (((unsigned short)(b[i])) << 8) | b[i+1];
}

// write a 16-bit word to an outgoing Modbus message

void putWord(unsigned char b[], unsigned i, unsigned short w) 
{
    b[i] = (unsigned char)(w >> 8);
    b[i+1] = (unsigned char)(w & 0xff);
}

///
// process legitimate Modbus/TCP requests
///

int processMsg(unsigned char b[],   // message buffer, starting with prefix
               unsigned len)        // length of incoming messsage
                                    // returns length of response
{

    // if you wish to make your processing dependent upon unit identifier
    // use b[6]. Many PLC devices will ignore this field, and most clients
    // default value to zero. However gateways or specialized programs can
    // use the unit number to indicate what type of precessing is desired

    unsigned i;

    // handle the function codes 3 and 16

    switch(b[7]) 
    {
        case 3:     // read registers
                    // request   03 rr rr nn nn
                    // response  03 bb da ta ......
        {
            unsigned regNo = getWord(b, 8);
            unsigned regCount = getWord(b, 10);
            if (len != 12) 
            {
                // exception 3 - bad structure to message
                b[7] |= 0x80;
                b[8] = 3;
                b[5] = 3; // length
                break;
            }
            if (regCount < 1 || regCount > 125 || 
				regNo >= num4xRegs || (regCount + regNo) > num4xRegs) 
            {
                // exception 2 - bad register number or length
                b[7] |= 0x80;
                b[8] = 2;
                b[5] = 3; // length
                break;
            }
            // OK so prepare the 'OK response'
            b[8] = 2 * regCount;
            b[5] = b[8] + 3;
            for (i=0;i<regCount;i++) 
            {
                putWord(b, 9+i+i, reg4x[i + regNo]);
            }
        }
        break;

        case 16:    // write registers
                    // request 
        {
            unsigned regNo = getWord(b, 8);
            unsigned regCount = getWord(b, 10);
            if (len != 13 + regCount + regCount || 
            b[12] != regCount + regCount) 
            {
                // exception 3 - bad structure to message
                b[7] |= 0x80;
                b[8] = 3;
                b[5] = 3; // length
                break;
            }
            if (regCount < 1 || regCount > 100 || 
				regNo >= num4xRegs || (regCount + regNo) > num4xRegs) 
            {
                // exception 2 - bad register number or length
                b[7] |= 0x80;
                b[8] = 2;
                b[5] = 3; // length
                break;
            }
            // OK so process the data
            for (i=0;i<regCount;i++) 
            {
                reg4x[i + regNo] = getWord(b, 13+i+i);
            }
            // and the OK response is a copy of the request to byte 11
            b[5] = 6;
        }
        break;

        default:
        // generate exception 1 - unknown function code
        b[7] |= 0x80;
        b[8] = 1;
        b[5] = 3; // length
        break;
    }

    // return the total size of the MB/TCP response
    // notice that bytes 0-4 and 6 will be identical to those of the request

    return 6+b[5];
}

///
// main entry point for the program
///

int main(int argc, char **argv) // arguments ignored for now
{
    int i;
    SOCKET csa[numSockets];
    SOCKET s;
    struct sockaddr_in server;
    static WSADATA wd;

    unsigned long nbiotrue = 1;

    printf("mbserver V2.2 7/27/01 Reference Class 0 Modbus/TCP Server\n"
			"sessions = %u, registers = %u, idle timeout = %s\n", 
			numSockets, num4xRegs, IDLE_TIMEOUT?"true":"false");

    // initialize WinSock
    if (WSAStartup(0x0101, &wd))
    {
        printf("cannot initialize WinSock\n");
        return 1;
    }
    // set up an array of socket descriptors, initially set to INVALID_SOCKET (not in use)
    // and initialize the fragment buffer

    for (i=0;i<numSockets;i++) 
    {
        csa[i] = INVALID_SOCKET;
        frag[i].fragLen = 0;
    }

    // set up listen socket

    s = socket(PF_INET, SOCK_STREAM, 0);
    server.sin_family = AF_INET;
    server.sin_port = htons(502); // ASA standard port
    server.sin_addr.s_addr = INADDR_ANY;

    i = bind(s, (struct sockaddr *)&server, sizeof(struct sockaddr_in));
    if (i<0)
    {
        printf("bind - error %d\n",WSAGetLastError());
        closesocket(s);
        WSACleanup();
        return 1;
    }

    // set socket non-blocking in case a client has second thoughts about
    // establishing a connection later on. In this case the accept() will return
    // with an error rather than with a socket.

    if (ioctlsocket (s, FIONBIO, &nbiotrue)) 
    {
        printf("ioctlsocket - error %d\n", WSAGetLastError());
    }

    i = listen(s, numSockets);
    if (i<0)
    {
        printf("listen - error %d\n",WSAGetLastError());
        closesocket(s);
        WSACleanup();
        return 1;
    }

    // at this point, be prepared to handle incoming requests on socket s
    // by doing accept() and processing them independently

    for (;;) 
    {

        SOCKET cs;
        fd_set fds;
        int si;
        struct timeval tv;

        // set up a 1 second timeout to allow for (future) background activity

        tv.tv_sec = 1;
        tv.tv_usec = 0;

        // be prepared for incoming messages on the listen port and any active ports

        FD_ZERO(&fds);

        // wait for incoming connection
        FD_SET(s, &fds);

        // and add any Modbus sessions
        for (i=0;i<numSockets;i++) 
        {
            if (csa[i] != INVALID_SOCKET) 
            {
                cs = csa[i];
                FD_SET(cs, &fds);
            }
        }

        i = select(32, &fds, NULL, NULL, &tv); // read

        // note that the fd_set will have been updated to select only those sockets 
        // requiring attention right now

        if (i<0)
        {
            printf("select - error %d\n",WSAGetLastError());
            closesocket(s);
            WSACleanup();
            return 1;
        }

        // any listen work?

        if (FD_ISSET(s, &fds)) 
        {
            printf("new connection received\n");
            cs = accept(s,NULL,0);
            if (cs<0)
            {
                int e = WSAGetLastError();
                if (e != WSAEWOULDBLOCK) 
                {
                    printf("accept - error %d\n",e);
                    closesocket(s);
                    WSACleanup();
                    return 1;
                } else 
                {
                    // the connection is no longer pending so it must have been
                    // abandoned by the client
                }
            } else 
            {

                // add the newly opened socket to the list. If nowhere to put it, throw it 
                // away

                for (i=0;i<numSockets;i++) 
                {
                    if (csa[i] == INVALID_SOCKET) 
                    {
                        csa[i] = cs;
                        frag[i].fragLen = 0;
						openTime[i] = time(NULL);
                        break;
                    }
                }

                if (i >= numSockets) 
                {
                    // nowhere to put it
                    printf("connection abandoned - maximum concurrent sessions reached\n");
                    closesocket(cs);
                }

                // set socket non-blocking just in case anything happens which might make the 
                // recv() operation block later on. This should not be necessary.

                if (ioctlsocket (cs, FIONBIO, &nbiotrue)) 
                {
                    printf("ioctlsocket - error %d\n", WSAGetLastError());
                }

            }
        }

        // any socket level work?

        for (si=0;si<numSockets;si++) 
        {
            struct fragMsg *thisFrag;
            unsigned char *ibuf;

            cs = csa[si];

            if (cs == INVALID_SOCKET) 
            {
                // nothing to do right now on this one
                continue;
            }

#if IDLE_TIMEOUT
			if (10 <= difftime(time(NULL), openTime[si])) 
			{
				// the 10 second idle timer has expired. Close the incoming session
				printf("closing session %d because idle time expired\n");
                // remove session from active list
                csa[si] = INVALID_SOCKET;
                // close connection
                closesocket(cs);
                continue; // abandon any further processing on this session
			}
#endif // IDLE_TIMEOUT

            if (!FD_ISSET(cs, &fds)) 
            {
                // nothing to do right now on this one
                continue;
            }

#if IDLE_TIMEOUT
			// ags 1/18/01
			// update timeout so that shhutdown only occurs after 10 sec of IDLE
			// and not arbitrarily 10 sec from session open!
			openTime[si] = time(NULL);
#endif // IDLE_TIMEOUT

            cs = csa[si];

            // account for any fragment outstanding from a previous cycle
            // (this stupidity would not be necessary if all clients would send
            // their messages in one piece)

            thisFrag = &frag[si];
            ibuf = thisFrag->fragBuf;

            if (thisFrag->fragLen < 6) 
            {
                // don't know the length yet, just read the prefix
                i = recv(cs, (char *)&thisFrag->fragBuf[thisFrag->fragLen], 6 - thisFrag->fragLen, 0);
                if (i <= 0) 
                {
                    // this session has been closed or damaged at the remote end
                    // this may be a normal condition

                    // remove session from active list
                    csa[si] = INVALID_SOCKET;
                    // close connection
                    closesocket(cs);
                    continue;
                }
                thisFrag->fragLen += i;

                // unfortunately, we are not sure if there are any more bytes
                // so continue this processing on the next cycle
                continue;
            }

            if (ibuf[2] != 0 || ibuf[3] != 0 || ibuf[4] != 0 || ibuf[5] < 2) 
            {
                // this is not legitimate Modbus/TCP
                // possibly your client is very confused
                // close down the connection

                // remove session from active list
                csa[si] = INVALID_SOCKET;
                printf("bad MB/TCP protocol - closing\n");
                // close connection
                closesocket(cs);
                continue;
            }

            // the real length is in ibuf[5]

            if (thisFrag->fragLen < 6+ibuf[5]) 
            {
                i = recv(cs, (char *)&thisFrag->fragBuf[thisFrag->fragLen], 6 + ibuf[5] - thisFrag->fragLen, 0);
                if (i <= 0) 
                {
                    // this session has been closed or damaged at the remote end
                    printf("session closed with partial request outstanding\n", i);
                    // remove session from active list
                    csa[si] = INVALID_SOCKET;
                    // close connection
                    closesocket(cs);
                    continue;
                }
                thisFrag->fragLen += i;
            }
            if (thisFrag->fragLen < 6+ibuf[5]) 
            {
                // still waiting for completion of the message
                continue;
            }

            // if we get here, the message is complete and it looks like MB/TCP

            // process the incoming request, generating a response
            // note that there is no requirement to keep track of which connection
            // the request was received on - you must only use the same one for 
            // sending the response

            i = processMsg(ibuf, thisFrag->fragLen);

            i = send(cs, (char *)ibuf, i, 0);

            thisFrag->fragLen = 0;

        }
        // note that this outer loop will run forever unless cancelled
        // and that if it is cancelled, you must close outstanding sockets
        // and call WSACleanup()
    }
}

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值