简介
1、Socket 英文原意是“孔”或者“插座”的意思,在网络编程中,通常将其称之为“套接字”,当前网络中的主流程序设计都是使用 Socket 进行编程的,因为它简单易用,更是一个标准,能在不同平台很方便移植。
2、socket是统一的编程接口,具有高移植性(但是没有Windows或者linux上这么完善,但是一般的应用都是够了的),而netconn是lwip所独有的。
3、在 Socket 中,它使用一个套接字来记录网络的一个连接,套接字是一个整数,就像我们操作文件一样,利用一个文件描述符,可以对它打开、读、写、关闭等操作,类似的,在网络中,我们也可以对 Socket 套接字进行这样子的操作,比如开启一个网络的连接、读取连接主机发送来的数据、向连接的主机发送数据、终止连接等操作。
4、在 LwIP 中,Socket API 是 基 于 NETCONN API 之 上 来 实 现 的, 系 统 最 多 提 供MEMP_NUM_NETCONN 个 netconn 连接结构,因此 Socket 套接字的个数也是那么多个。
5、为了更好对 netconn 进行封装,LwIP 还定义了一个套接字结构体——lwip_sock(我称之为 Socket连接结构),每个 lwip_sock 内部都有一个 netconn 的指针,实现了对 netconn 的再次封装
6、LwIP 定义了一个 lwip_sock 类型的 sockets 数组,通过套接字就可以直接索引并且访问这个结构体了,这也是为什么套接字是一个整数的原因,lwip_sock 结构体是比较简单的,因为基本上全是依赖 netconn 实现。
7、socket是一套用于不同主机间通信的API。网络套接字是IP地址与端口的组合,IP地址用来唯一标识网络设备,而端口号用于区分具体将数据发送到哪个应用上。通过socket可以建立一条不同主机不同应用之间的虚拟数据通道,并且其是点对点的
参考文章
IP地址转换
client_sock = accept(sock, (struct sockaddr *)&client_addr, &clientaddr_size);//会一直阻塞在这里,直至与远程主机建立 TCP 连接
printf("new client connected from (%s, %d)\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
char *inet_ntoa(struct in_addr in);
- 将网络传输的二进制数值转化为成点分十进制的ip地址(也就是变成一个字符串格式)。
- inet_ntoa 函数转换网络字节排序的地址为标准的ASCII以点分开的地址,该函数返回指向点分开的字符串地址(如192.168.1.10)的指针,该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被重写(复盖),所以如果需要保存该串最后复制出来自己管理!
inet_ntoa(变成字符串)和inet_addr(字符串变成传输的值)这两个只能转换ipv4
讲的非常好的一篇文章
inet_addr: 将字符串形式的IP地址转换为按网络字节顺序的整型值
#include <stdio.h>
#include <winsock2.h>
#include <inaddr.h>
int main()
{
struct in_addr ipAddr;
// 将字符串形式的IP地址转换为按网络字节顺序的整型值
ipAddr.S_un.S_addr = inet_addr("192.168.19.36");
unsigned int dip;
printf("%u", ipAddr.S_un.S_addr);
return 0;
}
结构体
套接字结构体——lwip_sock
- 为了更好对 netconn 进行封装,LwIP 还定义了一个套接字结构体——lwip_sock(我称之为 Socket连接结构)
- 每个 lwip_sock 内部都有一个 netconn 的指针,实现了对 netconn 的再次封装
- 那怎么找到 lwip_sock 这个结构体呢?LwIP 定义了一个 lwip_sock 类型的 sockets 数组,通过套接字就可以直接索引并且访问这个结构体了,这也是为什么套接字是一个整数的原因
- lwip_sock 结构体是比较简单的,因为基本上全是依赖 netconn 实现
/** 包含用于套接字的所有内部指针和状态 */
struct lwip_sock
{
/** 套接字当前是在 netconn 上构建的,每个套接字都有一个 netconn */
struct netconn *conn;
/** 从上一次读取中留下的数据 */
union lwip_sock_lastdata lastdata;
/** 收到数据的次数由 event_callback() 记录,下面的字段在 select 机制上使用 */
s16_t rcvevent;
/** 发送数据的次数,也是由回调函数记录的 */
u16_t sendevent;
/** Socket 上的发生的错误次数 */
u16_t errevent;
/** 使用 select 等待此套接字的线程数 */
SELWAIT_T select_waiting;
};
#define NUM_SOCKETS MEMP_NUM_NETCONN
/** 全局可用套接字数组(默认是 4) */
static struct lwip_sock sockets[NUM_SOCKETS];
union lwip_sock_lastdata
{
struct netbuf *netbuf;
struct pbuf *pbuf;
};
sockaddr 结构体
咋一看这个结构体,好像没啥信息要我们填写的,确实也是这样子,我们需要填写的 IP 地址与端口号等信息,都在 sa_data 连续的 14 字节信息里面,但是这个数据对我们不友好,因此 LwIP还定义了另一个对开发者更加友好的结构体——sockaddr_in,我们一般也是用这个结构体
struct sockaddr
{
u8_t sa_len; /* 长度 */
sa_family_t sa_family; /* 协议簇 */
char sa_data[14]; /* 连续的 14 字节信息 */
};
sockaddr_in 结构体
- 这个结构体的前两个字段是与 sockaddr 结构体的前两个字段一致,而剩下的字段就是 sa_data 连续的 14 字节信息里面的内容,只不过从新定义了成员变量而已
- sin_port 字段是我们需要填写的端口号信息
- sin_addr 字段是我们需要填写的 IP 地址信息 INADDR_ANY就是所有IP地址的意思
- sin_zero 区域的 8 字节保留未用。
#if LWIP_IPV4
/* members are in network byte order */
struct sockaddr_in {
u8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
#define SIN_ZERO_LEN 8
char sin_zero[SIN_ZERO_LEN];
};
#endif /* LWIP_IPV4 */
typedef u32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
ERR_OK = 0
/** Definitions for error constants. */
typedef enum {
/** No error, everything OK. */
ERR_OK = 0,
/** Out of memory error. */
ERR_MEM = -1,
/** Buffer error. */
ERR_BUF = -2,
/** Timeout. */
ERR_TIMEOUT = -3,
/** Routing problem. */
ERR_RTE = -4,
/** Operation in progress */
ERR_INPROGRESS = -5,
/** Illegal value. */
ERR_VAL = -6,
/** Operation would block. */
ERR_WOULDBLOCK = -7,
/** Address in use. */
ERR_USE = -8,
/** Already connecting. */
ERR_ALREADY = -9,
/** Conn already established.*/
ERR_ISCONN = -10,
/** Not connected. */
ERR_CONN = -11,
/** Low-level netif error */
ERR_IF = -12,
/** Connection aborted. */
ERR_ABRT = -13,
/** Connection reset. */
ERR_RST = -14,
/** Connection closed. */
ERR_CLSD = -15,
/** Illegal argument. */
ERR_ARG = -16
} err_enum_t;
API
socket(domain,type,protocol)
1、简介
- 作用: 向内核申请一个套接字,因此其返回值就是一个int类型的值,也就是Socket描述符,用户通过这个值可以索引到一个Socket 连接结构——lwip_sock。
- 本质上该函数其实就是对 netconn_new() 函数进行了封装,虽然说不是直接调用它,但是主体完成的工作就做了 netconn_new() 函数的事情。
- 参数domain :表示该套接字使用的协议簇,对于 TCP/IP 协议来说,该值始终为 AF_INET。
- 参数 type 指定了套接字使用的服务类型,可能的类型有 3 种:
(1)SOCK_STREAM:提供可靠的(即能保证数据正确传送到对方)面向连接的 Socket 服务,多用于资料(如文件)传输,如 TCP 协议。
(2)SOCK_DGRAM:是提供无保障的面向消息的 Socket 服务,主要用于在网络上发广播信息,如 UDP 协议,提供无连接不可靠的数据报交付服务。
(3)SOCK_RAW:表示原始套接字,它允许应用程序访问网络层的原始数据包,这个套接字用得比较少,暂时不用理会它。 - 参数 protocol 指定了套接字使用的协议,在 IPv4 中,只有 TCP 协议提供 SOCK_STREAM 这种可靠的服务,只有 UDP 协议提供 SOCK_DGRAM 服务,对于这两种协议,protocol 的值均为 0。
2、使用
//TCP客户端
int sock = -1;
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
printf("Socket error\n");
osDelay(1);
continue; //这个continue是不执行下面的代码,从这个循环的头开始重新走一遍
}
3、源码
#define socket(domain,type,protocol) lwip_socket(domain,type,protocol)
int
lwip_socket(int domain, int type, int protocol)
{
struct netconn *conn;
int i;
LWIP_UNUSED_ARG(domain); /* @todo: check this */
/* create a netconn */
switch (type) {
case SOCK_RAW:
conn = netconn_new_with_proto_and_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_RAW),
(u8_t)protocol, DEFAULT_SOCKET_EVENTCB);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ",
domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
break;
case SOCK_DGRAM:
conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain,
((protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP)),
DEFAULT_SOCKET_EVENTCB);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ",
domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
#if LWIP_NETBUF_RECVINFO
if (conn) {
/* netconn layer enables pktinfo by default, sockets default to off */
conn->flags &= ~NETCONN_FLAG_PKTINFO;
}
#endif /* LWIP_NETBUF_RECVINFO */
break;
case SOCK_STREAM:
conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_TCP), DEFAULT_SOCKET_EVENTCB);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ",
domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
break;
default:
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n",
domain, type, protocol));
set_errno(EINVAL);
return -1;
}
if (!conn) {
LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n"));
set_errno(ENOBUFS);
return -1;
}
i = alloc_socket(conn, 0);
if (i == -1) {
netconn_delete(conn);
set_errno(ENFILE);
return -1;
}
conn->socket = i;
done_socket(&sockets[i - LWIP_SOCKET_OFFSET]);
LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i));
set_errno(0);
return i;
}
connect(s,name,namelen)
1、简介
- 作用与 netconn_connect() 函数的作用基本一致,用于客户端中,就是建立连接,因为就是封装了 netconn_connect() 函数。
- TCP 客户端连接中,调用这个函数将发生握手过程(会发送一个 TCP 连接请求),并最终建立新的 TCP 连接。
- 而对于 UDP协议来说,调用这个函数只是在 UDP 控制块中记录远端 IP 地址与端口号,而不发送任何数据,参数信息与 bind() 函数是一样的。
- 参数 s 是表示要绑定的 Socket 套接字,注意了,这个套机字必须是从 socket() 函数中返回的索引,否则将无法完成绑定操作。
- 参数 name 是一个指向 sockaddr 结构体的指针,其中包含了网卡的 IP 地址、端口号等重要的信息,LwIP 为了更好描述这些信息,使用了 sockaddr 结构体来定义了必要的信息的字段,它常被用于Socket API 的很多函数中。
- 参数 namelen 指定了 name 结构体的长度。
- 返回值: 当调用成功时,函数返回0;否则返回-1
2、使用
struct sockaddr_in client_addr;
//这一段用来设置IP地址
uint8_t Remote_IP_Addr[4]; //远端ip地址
//定义目标IP地址
//定义一个u8的数组,每一项存一个。最后使用IP4_ADDR函数将其整合在一起
Remote_IP_Addr[0]=192;
Remote_IP_Addr[1]=168;
Remote_IP_Addr[2]=0;
Remote_IP_Addr[3]=4;//8000
IP4_ADDR(&server_ipaddr,Remote_IP_Addr[0],Remote_IP_Addr[1],Remote_IP_Addr[2],Remote_IP_Addr[3]);
client_addr.sin_family = AF_INET; //固定就是这个
client_addr.sin_port = htons(8000); //远端端口号
client_addr.sin_addr.s_addr = server_ipaddr.addr; //IP地址 u32_t
memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero)); //8字节保留未用
err = connect(sock,(struct sockaddr *)&client_addr,sizeof(client_addr));
if(err != ERR_OK)
{
closesocket(sock);
osDelay(10);
continue;//重新开启一遍这个while
}
printf("Connect to server successful!\r\n");
3、源码
#define connect(s,name,namelen) lwip_connect(s,name,namelen)
int
lwip_connect(int s, const struct sockaddr *name, socklen_t namelen)
{
struct lwip_sock *sock;
err_t err;
sock = get_socket(s);
if (!sock) {
return -1;
}
if (!SOCK_ADDR_TYPE_MATCH_OR_UNSPEC(name, sock)) {
/* sockaddr does not match socket type (IPv4/IPv6) */
sock_set_errno(sock, err_to_errno(ERR_VAL));
done_socket(sock);
return -1;
}
LWIP_UNUSED_ARG(namelen);
if (name->sa_family == AF_UNSPEC) {
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, AF_UNSPEC)\n", s));
err = netconn_disconnect(sock->conn);
} else {
ip_addr_t remote_addr;
u16_t remote_port;
/* check size, family and alignment of 'name' */
LWIP_ERROR("lwip_connect: invalid address", IS_SOCK_ADDR_LEN_VALID(namelen) &&
IS_SOCK_ADDR_TYPE_VALID_OR_UNSPEC(name) && IS_SOCK_ADDR_ALIGNED(name),
sock_set_errno(sock, err_to_errno(ERR_ARG)); done_socket(sock); return -1;);
SOCKADDR_TO_IPADDR_PORT(name, &remote_addr