一、TCP客户端
tcp客户端实现是比较简单的,大致分为以下几个步骤:
(1)申请套接字。
(2)绑定远端服务器的ip地址和端口。
(3)连接远端服务器。
(4)接收和发送数据。
#define PORT 5001
#define RECV_DATA (1024)
#define SERV_IP_ADDR "192.168.31.39"
#define SERV_PORT 5001
/*tcp客户端*/
void tcp_client(void *arg){
int sock=-1;
struct sockaddr_in Serv_addr;
char*recv_data;
int recv_data_len;
/*为recv_data申请内存空间 申请成功返回内存空间首地址 失败返回NULL*/
recv_data=(char*)pvPortMalloc(RECV_DATA);
if(recv_data==NULL){
printf("Mallo memory failed\r\n");
}
while(1){
/* 为sockaddr_in结构体成员赋值,用于以下的connect绑定 参数protocol在TCP/UDP两种协议下均为0 */
/*套接字申请成功返回Socket描述符(int类型) 失败返回-1*/
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
printf("Socket error\n");
vTaskDelay(10);
continue;
}
/*TCP/IP – IPv4*/
Serv_addr.sin_family=AF_INET;
/*绑定远端服务器的端口*/
Serv_addr.sin_port=htons(SERV_PORT);
/*绑定远端服务器的ip*/
Serv_addr.sin_addr.s_addr=inet_addr(SERV_IP_ADDR);
/* 清空sockaddr_in结构体内存空间 sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
memset(&(Serv_addr.sin_zero), 0, sizeof(Serv_addr.sin_zero));
/* 连接远端服务器 */
if (connect(sock, (struct sockaddr *)&Serv_addr, sizeof(struct sockaddr)) == -1)
{
printf("Connect failed!\n");
closesocket(sock);
vTaskDelay(10);
continue;
}
printf("Connect to tcp server successful!\n");
while(1)
{
/* 成功接收到数据,返回接收的数据长度 */
recv_data_len = recv(sock, recv_data, RECV_DATA, 0);
if (recv_data_len <= 0)
break;
/* 串口打印接收的数据内容 */
printf("recv:%s\n",recv_data);
/* 发送数据内容 */
write(sock,recv_data,recv_data_len);
}
}
}
电脑作为TCP服务器,单片机为TCP客户端来连接服务器,通过电脑服务端往单片机发送112233、555533,单片机接收到消息后将消息原路发送给电脑。
部分函数解析:
(1)int socket(int domain,int type,int protocol)
该函数用于申请套接字。
参数domain:套接字采用的协议簇,常用的有AF_INET--ipv4 AF_INET6--ipv6
参数type:套接字采用的服务类型,SOCK_STREAM表示可靠的面对连接的socket连接(TCP),SOCK_DGRAM提供面向消息的无保障连接(UDP),SOCK_RAW表示原始的套接字。
参数protocol:套接字所采用的协议,在TCP/UDP两种协议下均为0。
返回值:套接字申请成功返回Socket描述符(int类型) 失败返回-1。
(2)htons、ntohs、htonl、ntohl
这些函数用于大小端转换。
htons:host to network short long,将主机字节序转化成网络字节序,即将无符号16位整型转化成大端模数。
ntohs:network to host short long,将网络字节序转化成主机字节序,即将大端模式转化成无符号16位整型。
htonl:host to network long
ntohl:network to host long
htonl、ntohl两函数也类似,只不过是在无符号32位整型与网络字节序之间转换。
(3)inet_ntoa、inet_addr
inet_ntoa:将无符号32位地址数据(uint32_t)转化成char*类型的字符串。
inet_addr:将char*类型的字符串转化成无符号32位地址数据(uint32_t)。
(4) memset (void *, int, size_t)
该函数用于将一段内存中的值全转化成指定的值,此处 memset(&(Serv_addr.sin_zero), 0, sizeof(Serv_addr.sin_zero));将结构体sockaddr_in中成员sin_zero[SIN_ZERO_LEN]赋值为0,用于保证sockaddr与sockaddr_in两个数据结构保持大小相同。
(5)connect
该函数用于连接远端服务器,参数1为所申请的套接字,参数2为远端服务器,参数3为远端服务器字节长度。
(6)recv
该函数用于TCP接收数据,参数1为所申请的套接字,参数2为接收内存空间的起始地址,参数3为内存的大小,若成功接收数据返回数据长度,若接收失败返回-1。
(7)write
该函数TCP发送数据,参数1为所申请的套接字,参数2为发送数据的起始地址,参数3为发送数据的大小。
二、TCP服务器
TCP服务器创建步骤如下:
(1)申请套接字
(2)绑定服务器本地的ip、端口等信息
(3)监听连接请求,与客户端连接
(4)接收和发送数据
/*单片机作为服务器*/
/*tcp 服务端*/
void tcp_serv(void *arg){
struct sockaddr_in serv_addr,client_addr;
char *recv_data;
int sock=-1;
int remote_sock;
socklen_t client_addr_len;
int recv_data_len;
/*初始化客户端addr长度 一定要初始化!!!否则会出错,接收到的客户端ip为0.0.0.0*/
client_addr_len = sizeof(struct sockaddr_in);
/*为recv_data申请内存空间 申请成功返回内存空间首地址 失败返回NULL*/
recv_data = (char *)pvPortMalloc(RECV_DATA);
/* 为sockaddr_in结构体成员赋值,用于以下的connect绑定 参数protocol在TCP/UDP两种协议下均为0 */
/*套接字申请成功返回Socket描述符(int类型) 失败返回-1*/
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
printf("Socket 创建失败, 错误代码:%d\n", errno);
}else{
printf("Socket 创建成功");
}
/*将服务器上的网卡ip地址绑定为服务器的ip地址*/
serv_addr.sin_addr.s_addr = INADDR_ANY;
/*TCP/IP – IPv4*/
serv_addr.sin_family = AF_INET;
/*绑定服务器的端口*/
serv_addr.sin_port = htons(PORT); // 小端模式转为大端模式
/* 清空sockaddr_in结构体内存空间 sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero));
/*将服务器的信息与所申请的套接字绑定起来 绑定失败返回-1 */
if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
{
printf("Unable to bind\r\n");
goto __exit;
}
/*sock为服务器套接字 5为等待连接队列的最大长度*/
if (listen(sock, 5) == -1)
{
printf("Listen error\r\n");
goto __exit;
}
while (1){
/*accept为阻塞性函数,一值阻塞直到有客户端连接,所连接的客户端ip等信息存储于client_addr中,成功返回套接字的文件描述符,失败返回-1*/
remote_sock = accept(sock, (struct sockaddr *) &client_addr, &client_addr_len);
printf("new client connected from (%s, %d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
{
int flag = 1;
setsockopt(remote_sock,
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(void *) &flag, /* the cast is historical cruft */
sizeof(int)); /* length of option value */
}
while (1)
{
/*recv为阻塞性函数,一直等待直至接收到数据 成功接收到数据,返回接收的数据长度 */
recv_data_len = recv(remote_sock, recv_data, RECV_DATA, 0);
if (recv_data_len <= 0)
break;
printf("recv %d len data\n",recv_data_len);
/* 发送数据内容 */
write(remote_sock,recv_data,recv_data_len);
}
if (remote_sock >= 0)
closesocket(remote_sock);
remote_sock = -1;
}
__exit:
if (sock >= 0) closesocket(sock);
if (recv_data) free(recv_data);
}
电脑作为TCP客户端,单片机作为TCP服务端,通过串口打印可知其ip地址为192.168.31.48,用TCP客户端 来连接该ip,连接成功后往单片机分别发送5555、6666,单片机收到消息以后原路发回客户端。需要注意TCP连接为面向连接的、可靠的通讯协议,服务器一次只能与一个客户端可靠连接,但是可以同时有多个客户端申请连接(等待连接)。
部分函数解释:
(1) serv_addr.sin_addr.s_addr = INADDR_ANY
INADDR_ANY表示地址0.0.0.0,泛指本地的所有ip地址,上述代码即绑定本地的所有网卡IP地址作为服务器地址,因为有时候本地连接的不止一张网卡。
(2)socklen_t client_addr_len = sizeof(struct sockaddr_in)
此处要特别注意,不能初始化为NULL,否则无法接收客户端的ip等信息!!!!
三、UDP服务器
udp是一种无连接、不可靠的协议,因此其连接与TCP相比更为简单,但是数据的传输没有保障,多用于音频、视频等数据数据传输(传输速度快)。
udp服务器创建步骤如下:
(1)申请套接字
(2)绑定服务器本地ip、端口等信息
(3)接收、发送数据
/*UDP服务端程序*/
static void udp_serv(void *arg){
int sock=-1;;
char*recv_data;
struct sockaddr_in udp_addr,client_addr;
int recv_data_len;
socklen_t client_addr_len=sizeof(struct sockaddr);//必须初始化,否则无法接收
while(1){
recv_data=(char*)pvPortMalloc(RECV_DATA);//开辟接收缓存区 返回缓存区首地址
if (recv_data == NULL)
{
printf("No memory\n");
goto __exit;
}
/*创建udp套接字 此处为UDP连接,参数2为SOCK_DGRAM*/
sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
printf("Socker error\n");
goto __exit;
}
/*TCP/IP – IPv4*/
udp_addr.sin_family=AF_INET;
/*绑定服务器本地ip*/
udp_addr.sin_addr.s_addr=INADDR_ANY;
/*绑定服务器的端口*/
udp_addr.sin_port=htons(PORT);
memset(&(udp_addr.sin_zero),0,sizeof(udp_addr.sin_zero));
/*将服务器本地信息与套接字绑定*/
if(bind(sock,(struct sockaddr*)&udp_addr,sizeof(struct sockaddr)) == -1)
{
printf("Unable to bind\n");
goto __exit;
}
while(1){
/*接收数据 客户端的信息存储于client_addr中 接收成功返回数据长度*/
recv_data_len=recvfrom(sock,recv_data,RECV_DATA,0,(struct sockaddr*)&client_addr,&client_addr_len);
/* 显示发送端的 IP 地址 */
printf("receive from %s\n",inet_ntoa(client_addr.sin_addr));//char *ip4addr_ntoa(const ip4_addr_t *addr);将32位地址数据转化char*类型的字符串
//ipaddr_addr(const char *cp)将char*类型的字符串转化成32位地址数据
/* 显示发送端发来的字串 */
printf("recevce:%s",recv_data);
/* 将字串返回给发送端 */
sendto(sock,recv_data,recv_data_len,0,(struct sockaddr*)&client_addr,client_addr_len);
}
}
__exit:
if (sock >= 0) closesocket(sock);
if (recv_data) free(recv_data);
}
由于UDP是一种无连接的协议,因此UDP服务器可以同时被多个UDP客户端“连接”,这里单片机作为UDP服务器,我分别将手机和电脑作为两个UDP客户端,同时连接单片机,并隔500ms发送一次数据,通过电脑端和手机端的网络调试助手可知,单片机在接收到数据后均原路发回了,串口调试端打印的信息:192.168.31.151为手机端UDP的ip地址,192.168.31.39为电脑端UDP的ip地址,可见UDP协议的传输速度是比较快的。
部分函数解释:
(1)socket
此处采用的是UDP连接,第二个参数为SOCK_DGRAM
(2)recvfrom、sendto
这两个函数用于UDP连接中接收和发送数据。
recvfrom(int s,void *mem,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen)
参数s:连接的套接字
参数mem:保存接收数据的内存首地址
参数len:接收数据缓冲区大小
参数from:保存消息发送端的ip、port等信息
参数fromlen:client_addr的大小
返回值:接收成功,返回接收数据长度,失败返回-1
sendto(int s,const void *dataptr,size_t size,int flags,const struct sockaddr *to,socklen_t tolen)
参数s:连接的套接字
参数dataptr:需要发送的数据首地址
参数size:发送数据缓冲区大小
参数to:接收端的ip、port等信息
参数tolen:client_addr的大小
返回值:接收成功,返回发送数据长度,失败返回-1
四、UDP客户端
因为UDP为无连接协议,因此直接往指定服务器发送数据即可。
创建UDP客户端步骤如下:
(1)申请创建套接字
(2)绑定远端服务器ip、端口等信息
(3)往服务器发送数据和接收数据
char test_buf[]="sense_long is nb";
/*UDP客户端程序*/
static void udp_client(void *arg){
int sock=-1;
//char*recv_data;
struct sockaddr_in Serve_addr;
//int recv_data_len;
socklen_t addrlen=sizeof(struct sockaddr);//必须初始化,否则无法接收**************************
while(1){
//recv_data=(char*)pvPortMalloc(RECV_DATA);//开辟接收缓存区
//if (recv_data == NULL)
// {
// printf("No memory\n");
// goto __exit;
// }
sock=socket(AF_INET,SOCK_DGRAM,0);//创建udp套接字
if(sock<0){
printf("Socker error\n");
goto __exit;
}
Serve_addr.sin_family=AF_INET;
/*绑定服务器远端ip*/
Serve_addr.sin_addr.s_addr=inet_addr("192.168.31.151");
/*绑定服务器远端端口*/
Serve_addr.sin_port=htons(PORT);
memset(&(Serve_addr.sin_zero),0,sizeof(Serve_addr.sin_zero));
//sendto(sock,test_buf,sizeof(test_buf),0,(struct sockaddr*)&Serve_addr,addrlen);
while(1){
// recv_data_len=recvfrom(sock,recv_data,RECV_DATA,0,(struct sockaddr*)&Serve_addr,&addrlen);
/* 显示发送端的 IP 地址 */
//printf("receive from %s\n",inet_ntoa(Serve_addr.sin_addr));//char *ip4addr_ntoa(const ip4_addr_t *addr);将32位地址数据转化char*类型的字符串
//ipaddr_addr(const char *cp)将char*类型的字符串转化成32位地址数据
/* 显示发送端发来的字串 */
// printf("recevce:%s",recv_data);
/* 将字串返回给发送端 */
sendto(sock,test_buf,sizeof(test_buf),0,(struct sockaddr*)&Serve_addr,addrlen);
vTaskDelay(1000);
}
}
__exit:
if (sock >= 0) closesocket(sock);
//if (recv_data) free(recv_data);
}
现象如下:
单片机作为udp服务器,向服务器1s发送一条“sense_long is nb”。
电脑本地的ip地址可以通过CMD来获得,在cmd中输入ipconfig即可打印本地的ip地址
如果上述文章有帮助到您,麻烦点一个赞叭~