嵌入式应用实例→电子产品量产工具→网络输入系统的代码分析和上机测试记录(socket-套接字网络编程)

前言

在这里,我们是把网络设备当成输入设备,由于使用的是套接字网络编程,套接字编程通常分为客户端(Client)和服务器(Server端)两部分,在这里应用程序的主体作为Server端,另写一个简单的Client端配合进行测试。
主用到的网络设备Server端的功能,所以没有用到Client的功能,所以只需写其作为Server端的代码即可。

完整源代码

Server端代码

Server端整个代码都在文件09_input_netinput_unittest\input\netinput.c中,如下:



#include <input_manager.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* socket
 * bind
 * sendto/recvfrom
 */

#define SERVER_PORT 8888

static int g_iSocketServer;

static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{
	struct sockaddr_in tSocketClientAddr;
	int iRecvLen;
	char aRecvBuf[1000];
	
	unsigned int iAddrLen = sizeof(struct sockaddr);
	
	iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
	if (iRecvLen > 0)
	{
		aRecvBuf[iRecvLen] = '\0';
		//printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
		ptInputEvent->iType 	= INPUT_TYPE_NET;
		gettimeofday(&ptInputEvent->tTime, NULL);
		strncpy(ptInputEvent->str, aRecvBuf, 1000);
		ptInputEvent->str[999] = '\0';
		return 0;
	}
	else
		return -1;
}

static int NetinputDeviceInit(void)
{
	struct sockaddr_in tSocketServerAddr;
	int iRet;
	
	g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == g_iSocketServer)
	{
		printf("socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);
	
	iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (-1 == iRet)
	{
		printf("bind error!\n");
		return -1;
	}

	return 0;
}

static int NetinputDeviceExit(void)
{
	close(g_iSocketServer);	
	return 0;
}


static InputDevice g_tNetinputDev ={
	.name = "touchscreen",
	.GetInputEvent  = NetinputGetInputEvent,
	.DeviceInit     = NetinputDeviceInit,
	.DeviceExit     = NetinputDeviceExit,
};

#if 1

int main(int argc, char **argv)
{
	InputEvent event;
	int ret;
	
	g_tNetinputDev.DeviceInit();

	while (1)
	{
		ret = g_tNetinputDev.GetInputEvent(&event);
		if (ret) {
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
			printf("Type      : %d\n", event.iType);
			printf("str       : %s\n", event.str);
		}
	}
	return 0;
}

#endif

Client端的代码

Client端的代码在文件\unittest\client.c中,代码比较简单:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	int iSendLen;
	int iAddrLen;

	if (argc != 3)
	{
		printf("Usage:\n");
		printf("%s <server_ip> <str>\n", argv[0]);
		return -1;
	}

	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
 	{
		printf("invalid server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);

#if 0
	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	if (-1 == iRet)
	{
		printf("connect error!\n");
		return -1;
	}
#endif

	iAddrLen = sizeof(struct sockaddr);
	iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
	              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

	close(iSocketClient);
	
	return 0;
}

头文件include\input_manager.h

这个工程用到的头文件和嵌入式应用实例→电子产品量产工具→触摸屏输入系统的头文件是相同的,所以这个工程的头文件的分析就略过,详情见 https://blog.youkuaiyun.com/wenhao_ir/article/details/144609033

服务端的C文件input\netinput.c的分析

函数 NetinputDeviceInit()

函数 NetinputDeviceInit()用于对网络设备进行初始化。
在阅读这个函数的代码前建议把我写的另一篇博文:
Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析
认真读一遍,认真读一遍之后看下面的代码就简单了。

static int NetinputDeviceInit(void)
{
	struct sockaddr_in tSocketServerAddr;
	int iRet;
	
	g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); //SOCK_DGRAM代表使用UDP网络协议
	if (-1 == g_iSocketServer)
	{
		printf("socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);
	
	iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (-1 == iRet)
	{
		printf("bind error!\n");
		return -1;
	}

	return 0;
}

下面这句代码:

	struct sockaddr_in tSocketServerAddr;

定义了一个类型为sockaddr_in的结构体。关于sockaddr_in的详细介绍见我的另一篇博文:https://blog.youkuaiyun.com/wenhao_ir/article/details/144660421

为什么不需要进入监听状态?

我的疑问:
为什么没有像博文:
Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析
提供的例子中,调用函数listen()使服务端的套接字进入监听状态?难道不进入监听状态也可以收到数据?

答:这个问题问的好,因为这里用的是UDP协议,它是一种无连接的协议,不需要临听也可以收数据的。

函数NetinputGetInputEvent()的分析

说明:在阅读这个函数的代码前同样建议把我写的另一篇博文:
Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析
认真读一遍,认真读一遍之后看下面的代码就更容易理解了。
源代码:

static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{
	struct sockaddr_in tSocketClientAddr;
	int iRecvLen;
	char aRecvBuf[1000];
	
	unsigned int iAddrLen = sizeof(struct sockaddr);
	
	iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
	if (iRecvLen > 0)
	{
		aRecvBuf[iRecvLen] = '\0';
		//printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
		ptInputEvent->iType 	= INPUT_TYPE_NET;
		gettimeofday(&ptInputEvent->tTime, NULL);
		strncpy(ptInputEvent->str, aRecvBuf, 1000);
		ptInputEvent->str[999] = '\0';
		return 0;
	}
	else
		return -1;
}

代码iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);的理解

这句代码主要就是第三个参数999和第四个参数0可能不知道是什么意思。
关于第三个参数999的理解见我的另一篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/144669577?
第四个参数0的解释如下:
这是 recvfrom() 函数的 标志位(flags),它控制接收数据的方式。该参数通常用于指定不同的接收行为。0 表示使用默认行为,即不应用任何特殊的接收标志。如果你需要特定的行为(例如接收更多数据,或者想要暂时不删除缓冲区中的数据),可以设置不同的标志,如 MSG_WAITALL、MSG_PEEK 等。

代码gettimeofday(&ptInputEvent->tTime, NULL);

这行代码:

gettimeofday(&ptInputEvent->tTime, NULL);

调用了 gettimeofday 函数,用于获取当前的系统时间,并将其存储在 ptInputEvent->tTime 中。我们可以逐步理解:

gettimeofday 是一个获取当前时间的标准库函数,它的原型是:

int gettimeofday(struct timeval *tv, struct timezone *tz);
  • tv:这是一个指向 struct timeval 类型的指针,用来存储返回的当前时间。
  • tz:这是一个指向 struct timezone 类型的指针,表示时区信息。通常情况下,可以将其设置为 NULL,表示不需要时区信息(即不关心时区偏差)。

struct timeval 结构体定义如下:

struct timeval {
    time_t      tv_sec;   /* 秒数 */
    suseconds_t tv_usec;  /* 微秒数 */
};
  • tv_sec:自1970年1月1日(即Unix纪元)以来的秒数。
  • tv_usec:当前秒内的微秒数(范围为0到999,999微秒)。

代码解释:

  • gettimeofday(&ptInputEvent->tTime, NULL) 获取当前的系统时间(秒和微秒),并将其存储在 ptInputEvent 指向的 InputEvent 结构体中的 tTime 字段里。这里 tTime 是一个 struct timeval 类型。
  • NULL 作为第二个参数,表示不关心时区信息。

作用:
这行代码的作用是将当前的系统时间(精确到微秒)记录到 ptInputEvent->tTime 中,通常用于时间戳记录,方便后续进行时间比较、日志输出或其他需要时间信息的操作。

代码ptInputEvent->str[999] = '\0';的分析

我对函数NetinputGetInputEvent()的下面这句代码有疑问:

ptInputEvent->str[999] = '\0';

分两种情况:
第1种:
假如收到的数据为最大长度999,那么通过语句aRecvBuf[iRecvLen] = ‘\0’;已经把第1000个的值置为’\0’了,为什么还要作一次把第1000个的值置为’\0’的操作?
第2种:
假如收到的数据最大长度没有超过999,比如只有99个,那么此时把第1000个的值置为’\0’有何意义?

答:
在阅读下面的回答之前,可以先看下博文:
C语言的strncpy()函数在什么情况下会添加字符串末尾符‘\0‘,又什么情况下不会添加?

让我们分别分析您提出的两种情况:

第1种情况:收到的数据长度为最大值 999

在这种情况下:

  1. recvfrom() 接收到的数据填充了 aRecvBuf 的前 999 个字节。
  2. 紧接着,代码通过 aRecvBuf[iRecvLen] = '\0'; 将第 1000 个位置(aRecvBuf[999])置为 '\0',将接收到的字节流终结为一个以 '\0' 结尾的 C 字符串。
  3. 然后,代码执行:
    ptInputEvent->str[999] = '\0';
    
    这里的 ptInputEvent->str 是另一个字符串缓冲区,大小同样为 1000 字节。通过 strncpy()aRecvBuf 的内容复制到 ptInputEvent->str 中。

此时的关键在于:

  • 虽然 aRecvBuf 已经正确终结,但 strncpy() 的行为不会自动为目标缓冲区(ptInputEvent->str)添加 '\0',如果 aRecvBuf 的内容正好是 999 个字符,strncpy() 会将目标字符串填满,但可能无法添加终结符。
  • 因此,为了确保 ptInputEvent->str 无论何种情况都以 '\0' 终结,额外执行 ptInputEvent->str[999] = '\0'; 是一种防御性编程手段。

第2种情况:收到的数据长度小于 999(比如只有 99 个字节)

在这种情况下:

  1. recvfrom() 接收到 99 个字节,将它们填充到 aRecvBuf 的前 99 个位置。
  2. 接着,通过 aRecvBuf[iRecvLen] = '\0'; 将第 100 个位置(aRecvBuf[99])置为 '\0',完成字符串终结。
  3. 然后,strncpy()aRecvBuf 的内容复制到 ptInputEvent->str 中,这时目标缓冲区的第 100 个位置(ptInputEvent->str[99])是 '\0'
  4. 接下来,代码执行 ptInputEvent->str[999] = '\0';,尽管前 100 个字节已经正确设置,但额外将第 1000 个字节(ptInputEvent->str[999])置为 '\0'

此时的行为:

  • 在这种情况下,设置 ptInputEvent->str[999] = '\0'; 实际上没有实际意义,因为之前 strncpy() 已经正确处理了拷贝的部分内容,且字符串的第一个终结符('\0')已经足够满足需求。
  • 这通常是编程习惯的一部分,用于统一逻辑,确保无论任何情况,ptInputEvent->str 的最后一个字节始终是 '\0',即便这个位置没有被实际使用。

为什么还要额外设置 ptInputEvent->str[999] = '\0';

总结两种情况,这样设计的原因主要是:

  1. 安全性和鲁棒性

    • 无论数据长度是多少,这一行代码都确保目标字符串缓冲区 ptInputEvent->str 的最后一个字节(第 1000 个位置)始终是 '\0'
    • 如果程序的其他部分无意中读取了缓冲区的第 1000 个字节(尽管它不属于有效数据),仍然会看到一个安全的终结符,而不是随机垃圾数据。
  2. 防御性编程

    • 防止未来修改程序时,strncpy() 的行为或缓冲区的其他使用逻辑导致潜在问题。
  3. 无害性

    • 即便数据不足 999 字节,这一操作不会产生任何副作用,因为设置缓冲区末尾的额外字节并不影响之前的数据内容。

改进建议

如果明确知道 strncpy() 的行为以及上下文不需要多余的防御性措施,可以省略这行代码。但对于复杂嵌入式系统而言,这种冗余操作虽然看似多余,却是一种确保健壮性的小技巧。

主函数代码分析

主函数代码

int main(int argc, char **argv)
{
	InputEvent event;
	int ret;
	
	g_tNetinputDev.DeviceInit();

	while (1)
	{
		ret = g_tNetinputDev.GetInputEvent(&event);
		if (ret) {
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
			printf("Type      : %d\n", event.iType);
			printf("str       : %s\n", event.str);
		}
	}
	return 0;
}

结构体InputEvent

这个结构体是在头文件include\input_manager.h中定义的,如下:

typedef struct InputEvent {
	struct timeval	tTime;
	int iType;
	int iX;
	int iY;
	int iPressure;
	char str[1024];
}InputEvent, *PInputEvent;

主函数后面的代码没什么好说的,只要读懂了函数NetinputGetInputEvent()就很简单了。

客户端的C文件unittest\client.c的分析

客户端的源代码

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	int iSendLen;
	int iAddrLen;

	if (argc != 3)
	{
		printf("Usage:\n");
		printf("%s <server_ip> <str>\n", argv[0]);
		return -1;
	}

	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
 	{
		printf("invalid server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);

#if 0
	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	if (-1 == iRet)
	{
		printf("connect error!\n");
		return -1;
	}
#endif

	iAddrLen = sizeof(struct sockaddr);
	iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
	              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

	close(iSocketClient);
	
	return 0;
}

可见,只有一个主函数,我们就来分析这个主函数,注意,这次的分析默认你是读了上面的服务端的分析内容,所以有些上面出现的知识点或代码这里就不再赘述了。
另外,强烈建议看这里之前去看下:
Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析

代码段 if (argc != 3)...的分析

	if (argc != 3)
	{
		printf("Usage:\n");
		printf("%s <server_ip> <str>\n", argv[0]);
		return -1;
	}

如果 argc 的值不为 3,那么代码会输出以下内容:

Usage:
<程序名称> <server_ip> <str>

其中 <程序名称> 是程序被执行时的名称,通常是运行该程序时输入的文件名。例如,如果执行命令为 ./udp_client 192.168.1.1 message,那么 <程序名称> 就是 ./udp_client
假设程序名为 udp_client,则可能输出:

Usage:
./udp_client <server_ip> <str>

解释

  1. argc 表示命令行参数的数量,包含程序名本身。
  2. 如果 argc != 3,说明参数数量不正确,程序打印用法信息,并退出。

具体在这里,第一个参数是程序名,第二个参数是服务器的IP(即你想把数据发到哪个IP地址上),第三个参数是要发送的字符串。

代码inet_aton(argv[1], &tSocketServerAddr.sin_addr)

要注意与博文 :
Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析
中给的客户端的示例代码相区分,在上面这篇博文中,相关代码如下:

inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

而这里是:

inet_aton(argv[1], &tSocketServerAddr.sin_addr)

可见,前者是pton,后者是aton,inet_atoninet_pton 功能相似,都用于将命令行参数中的字符串IP地址转换为网络字节序的二进制地址,适配于 sockaddr_in 的 sin_addr 字段,但用法有一些不同。下面对比一下两者:

  1. inet_aton
  • 用于处理 IPv4 地址
  • 原型
    int inet_aton(const char *cp, struct in_addr *inp);
    
  • 特点
    • 只能处理IPv4地址,无法处理IPv6。
    • 将字符串形式的IPv4地址(如 "192.168.1.1")转换为 struct in_addr 类型。
  • 示例
    struct in_addr addr;
    if (inet_aton("192.168.1.1", &addr)) {
        printf("Conversion successful\n");
    } else {
        printf("Invalid IP address\n");
    }
    

  1. inet_pton
  • 用于处理 IPv4 和 IPv6 地址
  • 原型
    int inet_pton(int af, const char *src, void *dst);
    
  • 参数
    • af:地址族,通常为 AF_INETAF_INET6
    • src:字符串形式的IP地址。
    • dst:存储转换结果的指针(对于IPv4,是 struct in_addr *;对于IPv6,是 struct in6_addr *)。
  • 特点
    • 更通用,支持IPv4和IPv6。
    • 如果 afAF_INET,功能类似于 inet_aton
  • 示例
    struct in_addr addr;
    if (inet_pton(AF_INET, "192.168.1.1", &addr) == 1) {
        printf("Conversion successful\n");
    } else {
        printf("Invalid IP address\n");
    }
    

  1. 二者的区别总结
  • 地址支持inet_aton 仅支持IPv4,inet_pton 支持IPv4和IPv6。
  • 参数结构
    • inet_aton 不需要指定地址族,直接操作IPv4地址。
    • inet_pton 需要指定地址族,适配IPv4和IPv6。
  • 兼容性
    • inet_aton 是较旧的API,可能在某些环境中不被支持。
    • inet_pton 是更现代、更通用的API。

如果需要兼容IPv6,建议使用 inet_pton。如果仅处理IPv4,inet_atoninet_pton 都可以使用。

后面的代码就没有啥好说的了。

代码iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0, (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

这行代码:

iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0, (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

调用了 sendto 函数,用于向指定的目标地址发送数据。我们可以逐步分析每个参数的作用:

sendto 函数原型:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfdiSocketClient,这是一个已打开的套接字描述符,代表客户端套接字,数据会通过这个套接字发送。
  • bufargv[2],这是一个指向数据的指针,表示要发送的数据内容。在这个代码中,argv[2] 是程序运行时传入的第二个参数,即一个字符串。
  • lenstrlen(argv[2]),这是要发送的数据的长度。在这个代码中,strlen(argv[2]) 返回 argv[2] 字符串的长度(即字符串中的字符数,不包括末尾的空字符 \0)。
  • flags0,这是发送数据时的一些标志参数,通常为0,表示没有特别的发送选项。
  • dest_addr(const struct sockaddr *)&tSocketServerAddr,这是目标地址(服务器地址)的指针。在这个代码中,tSocketServerAddr 是已经配置好的服务器地址结构体,它包含了服务器的IP地址和端口号。
  • addrleniAddrLen,这是目标地址结构的长度。在这个代码中,iAddrLen 的值是 sizeof(struct sockaddr),即目标地址的大小。

sendto 的作用:
sendto 函数用于将数据从客户端发送到指定的服务器地址。在这行代码中,客户端向由 tSocketServerAddr 定义的服务器发送一个字符串(argv[2]),并传递该字符串的长度(strlen(argv[2]))。由于使用的是UDP(无连接),因此不需要建立连接,直接将数据包发送到目标地址。

返回值:

  • iSendLen 存储了 sendto 函数的返回值,这个返回值表示成功发送的字节数。如果成功发送,返回值等于 len(即字符串的长度);如果发生错误,返回值会是 -1,并且可以通过 errno 获取具体的错误原因。

总结:
这行代码的目的是通过UDP协议将命令行参数中的第二个字符串(argv[2])发送到指定的服务器地址(argv[1]指定的IP和固定的端口8888)。

交叉编译生成可执行程序

对网络套接字通信的服务器端程序进行交叉编译生成可执行程序

服务端修改Makefile文件进行编译,修改的方法不再赘述,请参考下面两篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/144532544
https://blog.youkuaiyun.com/wenhao_ir/article/details/144532544
顶层的Makfile:
在这里插入图片描述
子目录中的Makefile
在这里插入图片描述
在这里插入图片描述
工程复制到Ubuntu中的目录/home/book/mycode下,并重命名为:C0007_input_netinput_unittest
在这里插入图片描述
进入目录C0007_input_netinput_unittest,然后make

cd /home/book/mycode/C0007_input_netinput_unittest
make

在这里插入图片描述
把生成的test可执行文件重命名为net_server_test,然后复制到nfs文件下,以备待用
在这里插入图片描述

对网络套接字通信的客户端程序进行交叉编译生成可执行程序

客户端的程序我们进行手动编译,因为它其实只有一个C文件,命令如下:

cd /home/book/mycode/C0007_input_netinput_unittest/unittest
arm-buildroot-linux-gnueabihf-gcc -o net_client_test client.c

生成之后复制到NFS目录中,待用:
在这里插入图片描述

上板测试

打开开发板,挂载网络文件系统:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

为两个文件添加可执行权限:

cd /mnt
chmod +x net_server_test
chmod +x net_client_test

然后首先启动服务端,并且让其在后台运行:

./net_server_test &

在这里插入图片描述
然后执行客户端程序,注意有参数:

./net_client_test 127.0.0.1 "my name is SuWenhao"

在这里插入图片描述
这样就是测试成功了。

附完整工程文件

https://pan.baidu.com/s/1vOcngL_a4CuCwI9yg8ZvGA?pwd=mumv

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值