LWIP配置
服务器端实现
客户端实现
错误分析
一。LWIP配置(FREERTOS配置,ETH配置,LWIP配置)
1.FREERTOS配置
为什么要修改定时源为Tim1?不用systick?
原因:HAL库与FREERTOS都需要使用systick,两者冲突,所以修改时钟源,让FREERTOS使用Tim1。
2.ETH配置
3.LWIP配置
不使用DHCP
4.步骤:
(1)freertos.c中会自己出现一个Lwip初始化
运行后结果:命令行中输入ping 192.168.1.10有回复
二。服务器端
实验一:《stm32作为服务器端,COMMBOX串口作为客户端》
1.功能分析
小写转大写
2.步骤:
(1)建立socket_tcp_server.h
#ifndef SOCKET_TCP_SERVER_H
#define SOCKET_TCP_SERVER_H
#define SERVER_IP "192.168.1.11"
#define SERVER_PORT 6666
#define BUFF_SIZE 1024
void vTcpServerTask(void);
#endif
(2)建立socket_tcp_server.c,并添加到文件中
#include "socket_tcp_server.h"
#include "lwip/sockets.h"
#include "ctype.h"
char ReadBuff[BUFF_SIZE];
/**
* @brief TCP 服务器任务
* @param None
* @retval None
*/
void vTcpServerTask(void){
int sfd, cfd, n, i;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len;
//创建socket
sfd = socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定socket
bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
//监听socket
listen(sfd, 5);
//等待客户端连接
client_addr_len = sizeof(client_addr);
cfd = accept(sfd, (struct sockaddr *)&client_addr, &client_addr_len);
printf("client is connect cfd = %d\r\n",cfd);
while(1){
//等待客户端发送数据
n = read(cfd, ReadBuff, BUFF_SIZE);
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回客户端
write(cfd, ReadBuff, n);
}
}
(3)freertos.c中网络任务中,添加服务器运行函数。
不要忘记添加头文件
vTcpServerTask();
3.现象演示
(1)使用COMMBOX串口调试工程,添加socket网络客户端 ,目标ip为stm32的ip.端口在.h中
(2)stm32客户端发送的字母变大写
三。客户端创建
实验二:《stm32作为客户端,COMMBOX串口作为服务器端》
1.创建socket_tcp_client.c与socket_tcp_client.h
(1)socket_tcp_client.c
#include "socket_tcp_server.h"
#include "socket_tcp_client.h"
#include "lwip/sockets.h"
#include "ctype.h"
static char ReadBuff[BUFF_SIZE];
void vTcpClientTask(void)
{
int cfd, n, i;
struct sockaddr_in server_addr;
//创建socket
cfd = socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//连接到服务器
connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
printf("server is connect ok\r\n");
while(1){
//等待服务器发送数据
n = read(cfd, ReadBuff, BUFF_SIZE);
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回服务器
write(cfd, ReadBuff, n);
}
}
(2)socket_tcp_client.h
#ifndef _SOCKET_TCP_CLIENT_H
#define _SOCKET_TCP_CLIENT_H
void vTcpClientTask(void);
#endif
可能遇到的问题:
由于打开了防火墙,所以无法连接
补充:上述代码的问题
1.代码封装性不好
2.代码对边界错误提示太少
四。代码的优化
1.函数在封装,文件为socket_warp.c与socket_warp.h
(1)socket_warp.h
#ifndef _SOCKET_WRAP_H
#define _SOCKET_WRAP_H
#include "lwip/sockets.h"
int Socket(int domain, int type, int protocol);
int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int Listen(int sockfd, int backlog);
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int Write(int fd,const void *buf,size_t nbytes);
int Read(int fd,void *buf,size_t nbyte);
int Sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int Recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, socklen_t *fromlen);
#endif
(2)socket_warp.c
#include "socket_wrap.h"
#include "FreeRTOS.h"
#include "task.h"
int Socket(int domain, int type, int protocol){
int fd;
fd=socket(domain,type,protocol);
if(fd<0){
printf("create socket error\n");
//没有创建完成,那么这个任务也没有必要运行,直接切换上下文
//返回一个小于零的数
vTaskDelete(NULL);
}
return fd;
}
int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen){
int ret;
ret=bind(sockfd,addr,addrlen);
if(ret < 0){
printf("bind socket error\r\n");
//当调用删除任务,就会切换上下文,CPU执行其他任务
vTaskDelete(NULL);
}
return ret;
}
int Listen(int sockfd, int backlog){
int ret;
ret=listen(sockfd,backlog);
if(ret<0){
printf("listen socket error\n");
vTaskDelete(NULL);
}
return ret;
}
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
int fd;
again:
fd=accept(sockfd,addr,addrlen);
if(fd<0){
printf("accept socket error\n");
goto again;
}
return fd;
}
int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){
int ret;
ret=connect(sockfd,addr,addrlen);
if(ret<0){
printf("connect socket error\n");
//先关闭当前的socket,其实内部是删除这个socket的内存块,不删除会导致下次无法生成
close(sockfd);
}
return ret;
}
int Write(int fd,const void *buf,size_t nbytes){
int ret;
ret=write(fd,buf,nbytes);
//没有书写完成就关闭socket
if(ret<0){
printf("write socket error\n");
close(fd);
}
return ret;
}
int Read(int fd,void *buf,size_t nbyte){
int ret;
ret=read(fd,buf,nbyte);
if(ret==0){
printf("read socket is close\n");
close(fd);
}else if(ret<0){
printf("read socket error\n");
close(fd);
}
}
int Sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen){
int ret;
again:
ret=sendto(sockfd,msg,len,flags,to,tolen);
if(ret<0){
printf("sendto socket error\n");
goto again;
}
return ret;
}
int Recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, socklen_t *fromlen){
int ret;
again:
ret=recvfrom(sockfd,buf,len,flags,from,fromlen);
if(ret<0){
printf("recvfrom socket error\n");
goto again;
}
return ret;
}
上述为封装函数,包括tcp_server,tcp_client已经后续的udp_server
2.socket_tcp_server.c与socket_tcp_client.c
1.socket_tcp_server.c
#include "socket_udp_server.h"
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "ctype.h"
static char ReadBuff[BUFF_SIZE];
void vUdpServerTask(){
int sfd, n, i;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len;
int optval=1;
//创建socket
sfd=Socket(AF_INET, SOCK_DGRAM, 0);
setsockopt(sfd,SOL_SOCKET ,SO_BROADCAST,&optval,sizeof(optval));
//绑定socket
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
//处理
client_addr_len=sizeof(client_addr);
while(1){
//等待客户端发送数据
n = Recvfrom(sfd, ReadBuff, BUFF_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
ReadBuff[n] = '\0';
printf("recv data:%s\r\n",ReadBuff);
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回客户端
Sendto(sfd, ReadBuff, n, 0, (struct sockaddr *)&client_addr, client_addr_len);
}
}
2.socket_tcp_client.c
#include "socket_tcp_server.h"
#include "socket_tcp_client.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "FreeRTOS.h"
#include "task.h"
#include "string.h"
static char ReadBuff[BUFF_SIZE];
void vTcpClientTask(void)
{
int cfd, n, i, ret;
struct sockaddr_in server_addr;
// int so_reuseaddr_val = 1;
again:
//创建socket
cfd = Socket(AF_UNSPEC, SOCK_STREAM, 0);
//使能socket层 心跳检测
// setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr_val, sizeof(int));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//连接到服务器
//connect 其实是一个阻塞接口,内部要完成TCP的三次握手,当然有超时机制,所以我们需要等一段时间,才能重新连接到服务器
ret = connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret < 0){
//100ms去连接一次服务器
vTaskDelay(1000);
printf("connect fail\r\n");
goto again;
}
printf("server is connect ok\r\n");
while(1){
//等待服务器发送数据
n = Read(cfd, ReadBuff, BUFF_SIZE);
if(n <= 0){
goto again;
}
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回服务器
n = Write(cfd, ReadBuff, n);
if(n <= 0){
goto again;
}
}
}
结果:pc端的COMMBOX创建或者关闭服务器或者客户端时,串口都会打印出内容,提示打开或者关闭。