一、TCP客户端 tcp客户端实现是比较简单的,大致分为以下几个步骤: (1)申请套接字。 (2)绑定远端服务器的ip地址和端口。 (3)连接远端服务器。 (4)接收和发送数据。 #d
一、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 5001void tcp_client(void *arg){ int sock=-1; struct sockaddr_in Serv_addr; char*recv_data; int recv_data_len; recv_data=(char*)pvPortMalloc(RECV_DATA); if(recv_data==NULL){ printf("Mallo memory failed\r\n"); } while(1){ sock=Socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("Socket error\n"); vTaskDelay(10); continue; } Serv_addr.sin_family=AF_INET; Serv_addr.sin_port=htons(SERV_PORT); Serv_addr.sin_addr.s_addr=inet_addr(SERV_IP_ADDR); 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)接收和发送数据
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; client_addr_len = sizeof(struct sockaddr_in); recv_data = (char *)pvPortMalloc(RECV_DATA); sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0){ printf("Socket 创建失败, 错误代码:%d\n", errno); }else{ printf("Socket 创建成功"); } serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // 小端模式转为大端模式 memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero)); if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { printf("Unable to bind\r\n"); Goto __exit; } if (listen(sock, 5) == -1) { printf("Listen error\r\n"); goto __exit; } while (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, TCP_nodeLAY, (void *) &flag, sizeof(int)); } while (1) { 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)接收、发送数据
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; } sock=socket(AF_INET,SOCK_DGRAM,0); if(sock<0){ printf("Socker error\n"); goto __exit; } udp_addr.sin_family=AF_INET; 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){ recv_data_len=recvfrom(sock,recv_data,RECV_DATA,0,(struct sockaddr*)&client_addr,&client_addr_len); 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";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; 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); //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地址
如果上述文章有帮助到您,麻烦点一个赞叭~
来源地址:https://blog.csdn.net/weixin_46461874/article/details/128201892
--结束END--
本文标题: Lwip TCP/UDP客户端、服务器详解
本文链接: https://www.lsjlt.com/news/416967.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-04-29
2024-04-29
2024-04-29
2024-04-18
2024-04-17
2024-04-11
2024-04-08
2024-04-08
2024-04-03
2024-03-15
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0