嵌入式Linux网络编程与modbus协议使用

        在Linux 中的网络编程是通过socket 接口来进行的。人们常说的socket 是一种特殊的I/O 接口,它也是一种文件描述符。socket 是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。

        每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket 也有一个类似于打开文件的函数调用,该函数返回一个整型的socket 描述符,随后的连接建立、数据传输等操作都是通过socket 来实现的。

        socket编程的基本函数有socket()、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等,其中根据客户端还是服务端,或者根据使用TCP 协议还是UDP 协议,这些函数的调用流程都有所区别,这里先对每个函数进行说明,再给出各种情况下使用的流程图。

        socket():该函数用于建立一个socket 连接,可指定socket 类型等信息。在建立了socket 连接之后,可对sockaddr 或sockaddr_in 结构进行初始化,以保存所建立的socket 地址信息。

        bind():该函数是用于将本地IP 地址绑定到端口号,若绑定其他IP 地址则不能成功。另外,它主要用于TCP 的连接,而在UDP 的连接中则无必要。

        listen():在服务端程序成功建立套接字和与地址进行绑定之后,还需要准备在该套接字上接收新的连接请求。此时调用listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。

        accept():服务端程序调用listen()函数创建等待队列之后,调用accept()函数等待并接收客户端的连接请求。它通常从由bind()所创建的等待队列中取出第一个未处理的连接请求。

        connect():该函数在TCP 中是用于bind()的之后的client 端,用于与服务器端建立连接,而在UDP中由于没有了bind()函数,因此用connect()有点类似bind()函数的作用。

        send()和recv():这两个函数分别用于发送和接收数据,可以用在TCP 中,也可以用在UDP 中。当用在UDP 时,可以在connect()函数建立连接之后再用。

        sendto()和recvfrom():这两个函数的作用与send()和recv()函数类似,也可以用在TCP 和UDP 中。当用在TCP 时,后面的几个与地址有关参数不起作用,函数作用等同于send()和recv();当用在UDP 时,可以用在之前没有使用connect()的情况下,这两个函数可以自动寻找指定地址并进行连接。

        服务器端和客户端使用 TCP 协议的流程如图10.6 所示。

        服务器端和客户端使用 UDP 协议的流程如图10.7 所示。

        在socket的基础上,人们开发了许多应用层协议,比如http协议,ftp协议等等。这里我们以一种工业网络协议modbus协议为代表,介绍应用层网络协议的开发方法。

        Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。

        Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

        标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

        ModbusTCP从机把主机当作一组寄存器,可以通过发送由{读写操作标志,地址,数据}组成的数据帧对主机进行操作。

一、以IO复用和多进程两种方式编写linux服务器程序

        使用IO复用的linux服务器程序如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>

#define SERV_PORT	8080
#define LIST		20      /*服务器最大接受连接 */
#define MAX_FD		10      /* FD_SET支持描述符数量 */


int main( int argc, char *argv[] )
{
	int	sockfd;
	int	err;
	int	i;
	int	connfd;
	int	fd_all[MAX_FD];                 /* 保存所有描述符,用于select调用后,判断哪个可读 */

	/*下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值 */
	fd_set	fd_read;                        /* FD_SET数据备份 */
	fd_set	fd_select;                      /* 用于select */

	struct timeval	timeout;                /* 超时时间备份 */
	struct timeval	timeout_select;         /* 用于select */

	struct sockaddr_in	serv_addr;      /*服务器地址 */
	struct sockaddr_in	cli_addr;       /* 客户端地址 */
	socklen_t		serv_len;
	socklen_t		cli_len;

	/* 超时时间设置 */
	timeout.tv_sec	= 10;
	timeout.tv_usec = 0;

	/* 创建TCP套接字 */
	sockfd = socket( AF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 )
	{
		perror( "fail to socket" );
		exit( 1 );
	}

	/* 配置本地地址 */
	memset( &serv_addr, 0, sizeof(serv_addr) );
	serv_addr.sin_family		= AF_INET;              /* ipv4 */
	serv_addr.sin_port		= htons( SERV_PORT );   /* 端口, 8080 */
	serv_addr.sin_addr.s_addr	= htonl( INADDR_ANY );  /* ip */

	serv_len = sizeof(serv_addr);

	/* 绑定 */
	err = bind( sockfd, (struct sockaddr *) &serv_addr, serv_len );
	if ( err < 0 )
	{
		perror( "fail to bind" );
		exit( 1 );
	}

	/* 监听 */
	err = listen( sockfd, LIST );
	if ( err < 0 )
	{
		perror( "fail to listen" );
		exit( 1 );
	}

	/* 初始化fd_all数组 */
	memset( fd_all, -1, sizeof(fd_all) );

	fd_all[0] = sockfd;             /* 第一个为监听套接字 */

	FD_ZERO( &fd_read );            /* 清空 */
	FD_SET( sockfd, &fd_read );     /* 将监听套接字加入fd_read */

	int maxfd = fd_all[0];          /* 监听的最大套接字 */

	while ( 1 )
	{
		/* 每次都需要重新赋值,fd_select,timeout_select每次都会变 */
		fd_select	= fd_read;
		timeout_select	= timeout;

		/*
		 * 检测监听套接字是否可读,没有可读,此函数会阻塞
		 * 只要有客户连接,或断开连接,select()都会往下执行
		 */
		err = select( maxfd + 1, &fd_select, NULL, NULL, NULL );
		/* err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select); */
		if ( err < 0 )
		{
			perror( "fail to select" );
			exit( 1 );
		}

		if ( err == 0 )
		{
			printf( "timeout\n" );
		}

		/* 检测监听套接字是否可读 */
		if ( FD_ISSET( sockfd, &fd_select ) ) /* 可读,证明有新客户端连接服务器 */

		{
			cli_len = sizeof(cli_addr);

			/* 取出已经完成的连接 */
			connfd = accept( sockfd, (struct sockaddr *) &cli_addr, &cli_len );
			if ( connfd < 0 )
			{
				perror( "fail to accept" );
				exit( 1 );
			}

			/* 打印客户端的 ip 和端口 */
			char cli_ip[INET_ADDRSTRLEN] = { 0 };
			inet_ntop( AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN );
			printf( "----------------------------------------------\n" );
			printf( "client ip=%s,port=%d\n", cli_ip, ntohs( cli_addr.sin_port ) );

			/* 将新连接套接字加入 fd_all 及 fd_read */
			for ( i = 0; i < MAX_FD; i++ )
			{
				if ( fd_all[i] != -1 )
				{
					continue;
				}else{
					fd_all[i] = connfd;
					printf( "client fd_all[%d] join\n", i );
					break;
				}
			}

			FD_SET( connfd, &fd_read );

			if ( maxfd < connfd )
			{
				maxfd = connfd; /* 更新maxfd */
			}
		}

		/* 从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd) */
		for ( i = 1; i < maxfd; i++ )
		{
			if ( FD_ISSET( fd_all[i], &fd_select ) )
			{
				printf( "fd_all[%d] is ok\n", i );

				char	buf[1024]	= { 0 }; /* 读写缓冲区 */
				int	num		= read( fd_all[i], buf, 1024 );
				if ( num > 0 )
				{
					/* 收到 客户端数据并打印 */
					printf( "receive buf from client fd_all[%d] is: %s\n", i, buf );

					/* 回复客户端 */
					num = write( fd_all[i], buf, num );
					if ( num < 0 )
					{
						perror( "fail to write " );
						exit( 1 );
					}else{
						/* printf("send reply\n"); */
					}
				}else if ( 0 == num ) /* 客户端断开时 */

				{ /* 客户端退出,关闭套接字,并从监听集合清除 */
					printf( "client:fd_all[%d] exit\n", i );
					FD_CLR( fd_all[i], &fd_read );
					close( fd_all[i] );
					fd_all[i] = -1;

					continue;
				}
			}else {
				/* printf("no data\n"); */
			}
		}
	}
	return(0);
}

        使用多进程的linux服务器程序如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>


/**
 * TCP并发服务器,预先建立进程,同时到来的客户端分别由不同的进程并发处理
 **/
#define PORT		8080
#define BUFFERSIZE	1024
#define PIDNUM		2
static void handle( int s )
{
	int			sc;
	struct sockaddr_in	client_addr; /* 客户端地址 */
	char			buffer[BUFFERSIZE];
	int			len;
	int			ret;
	int			size;
	len = sizeof(client_addr);
	time_t now;
/* 接收客户端的连接 */
	while ( 1 )
	{
		memset( buffer, 0, BUFFERSIZE );
		sc	= accept( s, (struct sockaddr *) &client_addr, &len );
		size	= recv( sc, buffer, BUFFERSIZE, 0 );
		if ( size > 0 && !strncmp( buffer, "TIME", 4 ) )
		{
			memset( buffer, 0, BUFFERSIZE );
			now = time( NULL );
			sprintf( buffer, "%24s\r\n", ctime( &now ) );
			send( sc, buffer, strlen( buffer ), 0 ); /* 发送到客户端 */
		}
		close( sc );
	}
}


int main( int argc, char*argv[] )
{
	int			s;
	int			ret;
	struct sockaddr_in	server_addr;
	pid_t			pid[PIDNUM];
	int			i;
	s = socket( AF_INET, SOCK_STREAM, 0 );
	if ( s < 0 )
	{
		perror( "socket error" );
		return(-1);
	}
/* 绑定地址到套接字 */
	server_addr.sin_family		= AF_INET;
	server_addr.sin_port		= htons( PORT );
	server_addr.sin_addr.s_addr	= htonl( INADDR_ANY );
	ret				= bind( s, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if ( ret < 0 )
	{
		perror( "bind error" );
		return(-1);
	}
	ret = listen( s, 10 ); /* 监听 */
/* 建立子进程处理同时到来的客户端请求 */
	for ( i = 0; i < PIDNUM; i++ )
	{
		pid[i] = fork();
		if ( pid[i] == 0 )
		{
			handle( s );
		}
	}

	while ( 1 )
		;
	close( s );
}

        为了验证两个服务器程序是否可用,我们还需要编写一个客户端程序,客户端程序如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT		8080
#define BUFFERSIZE	1024
int main( int argc, char*argv[] )
{
	int			s;
	int			ret;
	int			size;
	struct sockaddr_in	server_addr;
	char			buffer[BUFFERSIZE];
	s = socket( AF_INET, SOCK_STREAM, 0 );
	if ( s < 0 )
	{
		perror( "socket error" );
		return(-1);
	}
	bzero( &server_addr, sizeof(server_addr) );
/* 将地址结构绑定到套接字 */
	server_addr.sin_family		= AF_INET;
	server_addr.sin_port		= htons( PORT );
	server_addr.sin_addr.s_addr	= htonl( INADDR_ANY );
/* 连接服务器 */
	ret = connect( s, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if ( ret == -1 )
	{
		perror( "connect error" );
		return(-1);
	}
	memset( buffer, 0, BUFFERSIZE );
	printf( “ \ nenter something: \ n ” );
	scanf( “ % s ”, buffer );
	size = send( s, buffer, strlen( buffer ), 0 );
	if ( size < 0 )
	{
		perror( "send error" );
		return(-1);
	}
	memset( buffer, 0, BUFFERSIZE );
	size = recv( s, buffer, BUFFERSIZE, 0 );
	if ( size < 0 )
	{
		perror( "recv error" );
		return;
	}

	printf( "%s\n", buffer );
	close( s );
	return(0);
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C是一种编程语言,它是由美国贝尔实验室的Dennis Ritchie和Ken Thompson在20世纪70年代初开发的。C语言是一种通用的高级编程语言,也是最常用的编程语言之一。 C语言具有简洁、高效和可移植的特点。它的语法规则相对简单,易于学习和理解。C语言的设计初衷是为了开发操作系统,因此在系统级编程方面表现出色,可以直接访问硬件资源,执行底层的操作。 C语言在计算机科学教育中具有重要的地位。许多大学的计算机科学专业都将C语言作为教学的第一门编程语言。这是因为学习C语言可以帮助学生掌握基本的编程概念和技巧,如变量、数据类型、控制结构和函数等。 C语言的应用范围非常广泛。它可以用于开发各种软件和应用程序,如操作系统、编译器、数据库、图形界面、嵌入式系统和游戏等。许多著名的软件和操作系统都是用C语言开发的,例如UNIX、Linux和Windows。 此外,C语言也被广泛用于算法和数据结构的实现。许多经典的算法和数据结构都是用C语言描述和实现的,如排序算法、搜索算法和链表等。学习C语言可以帮助开发者掌握算法和数据结构的基本原理和实现方法。 总之,C语言是一种重要的编程语言,具有简洁、高效和可移植的特点。它在计算机科学教育和软件开发领域都有广泛的应用。学习和掌握C语言对于计算机专业的学生和开发者来说是非常重要的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值