Socket编程深入

通过Socket封装和TCP优化,使得代码可以应对许多突发情况,同时可以丰富了代码的提示功能,更加便于调试和使用

socket封装

#include "socket_wrap.h"
#include "FreeRTOS.h"
#include "task.h"

/**
  * @brief  创建套接字
  * @param  domain: 	协议域
  * @param  type: 		协议类型 
  * @param  protocol: 协议版本
  * @retval int:			0
  */
int Socket(int domain, int type, int protocol)
{
	int fd;
	fd = socket(domain, type, protocol);
	//当返回值为-1的时候,基本是lwip的内存不够
	if(fd < 0){
		printf("create socket error\r\n");
		//当调用删除任务,就会切换上下文,CPU执行其他任务
		vTaskDelete(NULL);
	}
	return fd;
}


/**
  * @brief  绑定套接字
  * @param  sockfd: 	文件描述符
  * @param  addr: 		绑定的地址信息 
  * @param  addrlen: 	地址结构体长度
  * @retval int:			0
  */
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;
}

/**
  * @brief  监听套接字
  * @param  sockfd: 	要监听的文件描述符
  * @param  backlog: 	监听队列的大小
  * @retval int:			0
  */
int Listen(int sockfd, int backlog)
{
	int ret;
	ret = listen(sockfd, backlog);
	if(ret < 0){
		printf("listen socket error\r\n");
		//当调用删除任务,就会切换上下文,CPU执行其他任务
		vTaskDelete(NULL);		
	}
	return ret;

}

/**
  * @brief  等待客户端建立好连接
  * @param  sockfd: 	文件描述符
  * @param  addr: 		绑定的地址信息 
  * @param  addrlen: 	地址结构体长度---指针
  * @retval int:			0
  */
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){

	int fd;
again:
	//accept 是阻塞函数,只有客户端连接成功后,才会返回,或者错误返回
	fd = accept(sockfd, addr, addrlen);
	//客戶端连接错误
	if(fd < 0){
		printf("accept socket error\r\n");
		goto again;
	
	}
	return fd;
}

/**
  * @brief  向目标服务器建立连接
  * @param  sockfd: 	文件描述符
  * @param  addr: 		绑定的地址信息 
  * @param  addrlen: 	地址结构体长度---指针
  * @retval int:			正确:0,错误小于0
  */
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\r\n");
		//先关闭当前的socket,其实内部是删除这个socket的内存块
		close(sockfd);
	}
	return ret;
}
/**
  * @brief  向套接字发送数据
  * @param  fd: 	文件描述符
  * @param  buf: 		要发送的缓冲区 
  * @param  nbytes: 	发送数据的大小,单位为字节
  * @retval int:			正确:返回已经发生的数据长度,错误小于0
  */
int Write(int fd,const void *buf,size_t nbytes)
{
	int ret;
	ret = write(fd, buf, nbytes);
	//基本上是socket错误了,比如对方socket关闭了
	if(ret < 0){
		printf("Write socket error\r\n");
		//先关闭当前的socket,其实内部是删除这个socket的内存块
		close(fd);	
	}
	return ret;

}
/**
  * @brief  从套接字读取数据
  * @param  fd: 	文件描述符
  * @param  buf: 		要接收的缓冲区 
  * @param  nbytes: 	接收数据的大小,单位为字节
  * @retval int:			正确:返回已经接收的数据长度,错误小于0,socket关闭等于0
  */
int Read(int fd,void *buf,size_t nbyte)
{
	int ret;
	ret = read(fd, buf, nbyte);
	if(ret == 0){
		printf("read socket is close\r\n");
		close(fd);
	}else if(ret < 0){
	
		printf("read socket error\r\n");
		close(fd);
	}
	return ret;

}

/**
  * @brief  发送数据到指定地址
  * @param  sockfd: 文件描述符
  * @param  msg: 		要发送的缓冲区 
  * @param  len: 		要发送大小
  * @param  flags: 	标志 默认传0
  * @param  to: 		发送的地址信息
  * @param  tolen: 	地址结构体长度
  * @retval int:			正确:返回已经发送的数据长度,错误小于0
  */
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\r\n");
		goto again;
	}
	return ret;

}
/**
  * @brief  从socket接收数据
  * @param  sockfd: 	文件描述符
  * @param  buf: 			要接收的缓冲区 
  * @param  len: 			接收缓冲区的大小
  * @param  flags: 		标志 默认传0
  * @param  from: 		接收到的地址信息
  * @param  fromlen: 	地址结构体大小
  * @retval int:			正确:返回已经发送的数据长度,错误小于0
  */
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\r\n");
		goto again;
	
	}
	return ret;
}

TCP Server优化

#include "socket_tcp_server.h"
#include "socket_wrap.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);
again:
	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);
		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;
		}
	}
}






TCP Client优化

#include "socket_tcp_server.h"
#include "socket_tcp_client.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "FreeRTOS.h"
#include "task.h"

static char ReadBuff[BUFF_SIZE];


void vTcpClientTask(void)
{

	int 	 cfd, n, i, ret;
	struct sockaddr_in server_addr;
again:	
	//创建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 其实是一个阻塞接口,内部要完成TCP的三次握手,当然有超时机制,所以我们需要等一段时间,才能重新连接到服务器
	ret = Connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(ret < 0){
		//100ms去连接一次服务器
		vTaskDelay(100);
		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;
		
		}		
	}
}

UDP 编程模型

UDP C/S模型

Socket编程深入_第1张图片

UDP API

socket

int socket(int domain, int type, int protocol);
domain:
	AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
	AF_INET6 与上面类似,不过是来用IPv6的地址
	AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
	SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
	SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
	SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
	SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
	SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
	传0 表示使用默认协议。
返回值:
	成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
        

sendto

int sendto(int sockfd, const void *msg,int len, unsigned int flags, const struct sockaddr *to, int tolen);
to: 表示目地机的IP地址和端口号信息,
tolen: 常常被赋值为sizeof(struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

recvfrom

int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from:是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
fromlen: 常置为sizeof (struct sockaddr)。
当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。

UDP Server端实现

#include "socket_udp_server.h"
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "ctype.h"

static char ReadBuff[BUFF_SIZE];

/**
  * @brief  udp 服务器任务
  * @param  None
  * @retval None
  */
void vUdpServerTask(void){

	int 	 sfd, n, i;
	struct sockaddr_in server_addr, client_addr;
	socklen_t	client_addr_len;
	
	//创建socket	udp通信
	sfd = Socket(AF_INET, SOCK_DGRAM, 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));
	client_addr_len = sizeof(client_addr);
	while(1){
		//等待客户端发送数据
		n = Recvfrom(sfd, ReadBuff, BUFF_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
		//进行大小写转换
		for(i = 0; i < n; i++){
		
			ReadBuff[i] = toupper(ReadBuff[i]);		
		}
		//写回客户端
		Sendto(sfd, ReadBuff, BUFF_SIZE, 0, (struct sockaddr *)&client_addr, client_addr_len);
	}
	
}

你可能感兴趣的:(物联网lwIP网络开发,物联网,单片机,stm32,tcp/ip,网络,服务器)