、什么是socket

socket译为“插座”,在计算机通信领域,socket被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过这种方式,一台计算机可以接受其他计算机的数据,也可以向其他计算机发送数据。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现。socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).

说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

注意:socket没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。

二、socket编程流程

socket编程的总体流程如下图所示。 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端客户端读取数据,最后关闭连接,一次交互结束。

三、socket相关函数介绍

Socket()

函数原型:

int  socket(int protofamily, int type, int protocol)

该函数用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数通过它来进行一些读写操作

参数:

Protofamily:

协议域,又称为协议族(family),即IP地址类型。常用的协议族有AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

注:AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。经常也用PF前缀,PF 是“Protocol Family”的简写,它和 AF 是一样的。例如,PF_INET 等价于 AF_INETPF_INET6 等价于 AF_INET6

type;

        数据传输方式/套接字类型。常用的socket类型有,SOCK_STREAM(流格式套接字/面向连接的套接字 TCPSOCK_DGRAM(数据报套接字/无连接的套接字 UDP、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

protocol:

        传输协议,常用的协议有,IPPROTO_TCPIPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议UDP传输协议、STCP传输协议、TIPC传输协议。type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。

注:如果将该参数设置为0,系统会自动推演出应该使用什么协议,例如

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

返回值:

NULL 创建失败

其他值 创建成功返回的socket描述符

实例:

#define AF_INET         2   //IPV4
#define SOCK_STREAM     1 //提供有序的,可靠的、双向的和基于连接的字节流,使用带外数据传输机制,TCP
int s = lwip_socket(AF_INET, SOCK_STREAM, 0);   //创建SOCKET,IPV4,TCP,自动选择type类型对应的默认协议
If(s ==NULL)){
//失败
}

Bind()

该函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

函数原型:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

参数:

Sockfd:

即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字

Addr:

一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:

typedef u32_t in_addr_t;struct in_addr {in_addr_t s_addr; //IP地址};struct sockaddr_in {u8_t            sin_len; //长度sa_family_t     sin_family; //地址族(address family),也就是地址类型in_port_t       sin_port; //16位端口号struct in_addr  sin_addr; //32位IP地址#define SIN_ZERO_LEN 8char            sin_zero[SIN_ZERO_LEN]; //不使用,一般用0填充};

Addrlen:

typedef u32_t socklen_t;

对应的地址的长度。

返回值:

0 成功

非0 失败

代码实例:

//创建套接字int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建sockaddr_in结构体变量struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充serv_addr.sin_family = AF_INET;  //使用IPv4地址serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址serv_addr.sin_port = htons(1234);  //端口//将套接字和IP、端口绑定bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        sin_prot 为端口号。uint16_t 的长度为两个字节,理论上端口号的取值范围为 0~65536,但 0~1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80FTP 服务的端口号为 21,所以我们的程序要尽量在 1024~65536 之间分配端口号。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,因为客户端在connect()时由系统随机生成一个。

这里有个问题,为什么要将sockaddr_in类型强制转换为sockaddr.

sockaddr结构体的定义如下:

struct sockaddr {u8_t        sa_len; //长度sa_family_t sa_family; //地址族(address family),也就是地址类型char        sa_data[14]; //IP地址和端口号};

将sockaddr与sockaddr_in对比(括号中的数字表示所占用的字节数)

sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。

可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体

实例:

#define AF_INET         2   //IPV4
#define CNET_AP_PORT 8686
/** 0.0.0.0 */
#define IPADDR_ANY          ((u32_t)0x00000000UL)struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;  //IPV4
addr.sin_port = lwip_htons(CNET_AP_PORT);   //端口
addr.sin_addr.s_addr = lwip_htonl(IPADDR_ANY);  //地址
Int ret = lwip_bind(s, (struct sockaddr *)&addr, sizeof(addr)); //绑定
If(ret != 0){
//失败
}

Listen()

该函数可以让套接字进入被动监听状态,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求

函数原型:

int listen(int sock, int backlog); 

参数:

Sock:需要进入监听状态的套接字描述符

Backlog:请求队列的最大长度

当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

返回值:

0 成功

非0 失败

注:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数

实例:

Int ret = lwip_listen(s, 5);
If(ret != 0){
//失败
}

lwip_setsockopt()

套接字描述符选项

函数原型:

int lwip_setsockopt (int s, int level, int optname, const void *optval, socklen_t optlen)

参数:

s:

套接字描述符

level:

选项定义的层次,支持一下层次:

  • SOL_SOCKET,套接字层
  • IPPROTO_TCP,TCP层
  • IPPROTO_IP,IP层
  • IPPROTO_IPV6,IPV6层

otpname:

需要设置的选项

在套接字级别上(SOL_SOCKET),option_name可以有以下取值:

#define SO_DEBUG        0x0001 /* turn on debugging info recording */
#define SO_DONTROUTE    0x0010 /* just use interface addresses */#define SO_USELOOPBACK  0x0040 /* bypass hardware when possible */#define SO_LINGER       0x0080 /* linger on close if data present */
#define SO_DONTLINGER   ((int)(~SO_LINGER))
#define SO_OOBINLINE    0x0100 /* leave received OOB data in line */#define SO_REUSEPORT    0x0200 /* allow local address & port reuse */#define SO_SNDBUF       0x1001 /* send buffer size */
#define SO_RCVBUF       0x1002 /* receive buffer size */#define SO_CONTIMEO     0x1009 /* connect timeout */#define SO_NO_CHECK     0x100a /* don't create UDP checksum */
#define SO_BINDTODEVICE 0x100b /* bind to device */#define SO_REUSEADDR   0x0004 /* Allow local address reuse */
#define SO_KEEPALIVE   0x0008 /* keep connections alive */
#define SO_BROADCAST   0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */#define SO_ACCEPTCONN   0x0002 /* socket has had listen() */
#define SO_ERROR        0x1007 /* get error status and clear */
#define SO_SNDLOWAT     0x1003 /* send low-water mark */
#define SO_SNDTIMEO     0x1005 /* send timeout */
#define SO_RCVLOWAT     0x1004 /* receive low-water mark */
#define SO_RCVTIMEO     0x1006 /* receive timeout */
#define SO_TYPE         0x1008 /* get socket type */

       SO_DEBUG,打开或关闭调试信息。BOOL

当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(第10)位,或清SOCK_DBG位。

 SO_DONTROUTE,打开或关闭路由查找功能。BOOL

当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。

        SO_LINGER,延缓关闭。struct linger

如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。

该选项的参数(option_value)是一个linger结构:

struct linger {
            int   l_onoff;   //开关
            int   l_linger;  //延迟时间
        };

如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。

   SO_DONTLINER ,不延缓关闭。BOOL

不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。

        SO_OOBINLINE,紧急数据放入普通数据流。BOOL

该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。

        SO_SNDBUF,设置发送缓冲区的大小。INT

发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。

    SO_RCVBUF,设置接收缓冲区的大小。INT

接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。

SO_NO_CHECK,打开或关闭校验和。BOOL

该操作根据option_value的值,设置sock->sk->sk_no_check。

      SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。BOOL

该选项最终将设备赋给sock->sk->sk_bound_dev_if。

     SO_REUSEADDR,打开或关闭地址复用功能。BOOL

当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为1或0。

   SO_KEEPALIVE,套接字保活。BOOL

如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。

        SO_BROADCAST,允许或禁止发送广播数据。BOOL

当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。

    SO_RCVTIMEO,设置接收超时时间。INT

该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。

         SO_SNDTIMEO,设置发送超时时间。INT

该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。

struct timeval timeout;
timeout.tv_sec  = 30;   //秒
timeout.tv_usec = 0;    //微秒
if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {LOG_I(lwip_socket_example, "Setsockopt failed - set rcvtimeo\n");
}

在套接字级别上(IPPROTO_TCP),option_name可以有以下取值:

#define TCP_NODELAY    0x01    /* don't delay send to coalesce packets */
#define TCP_KEEPALIVE  0x02    /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
#define TCP_KEEPIDLE   0x03    /* set pcb->keep_idle  - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
#define TCP_KEEPINTVL  0x04    /* set pcb->keep_intvl - Use seconds for get/setsockopt */
#define TCP_KEEPCNT    0x05    /* set pcb->keep_cnt   - Use number of probes sent for get/setsockopt */
#define TCP_MAXSEG     0x06    /* set maximum segment size */

TCP_NODELAY,不延迟发送。BOOL

TCP_KEEPALIVE,发送keepalive探测包,当在空闲时

TCP_KEEPIDLE,设置连接上如果没有数据发送时,多久发送keepalive探测分组,单位为秒。

TCP_KEEPINTVL,前后两次探测之间的时间间隔,单位是秒

TCP_KEEPCNT,最大重试次数。

int keepAlive = 1;    // 非0值,开启keepalive属性
int keepIdle = 60;    // 如该连接在60秒内没有任何数据往来,则进行此TCP层的探测
int keepInterval = 5; // 探测发包间隔为5秒
int keepCount = 3;        // 尝试探测的最多次数// 开启探活
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount)

optval:

指针,指向存放选项待设置的新值的缓冲区

optlen:

optval缓冲区长度。

Accept()

当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求

函数原型:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen)

参数:

Sock:接收到的客户端套接字

Addr:客户端的IP地址和端口号

Addrlen:长度

返回值:

-1 失败

>0 新监听的到客户端套接字描述符

注:后续和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。此外,accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来

实例:

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;  //IPV4
addr.sin_port = lwip_htons(CNET_AP_PORT);   //端口
addr.sin_addr.s_addr = lwip_htonl(IPADDR_ANY);  //地址
socklen_t sockaddr_len = sizeof(addr);
Int c = lwip_accept(s, (struct sockaddr *)&addr, &sockaddr_len);    //等待新的连接
If(c < 0){
//异常
}

Connect()

客户端通过该函数与服务端建立链接

函数原型:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

参数:

Sock:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。

serv_addr:要连接的IP和端口以及类型

Addrlen:长度

返回值:

0 成功

-1 失败

实例:

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET; //IPV4
addr.sin_port = lwip_htons(SOCK_TCP_SRV_PORT); //端口
inet_addr_from_ip4addr(&addr.sin_addr, netif_ip4_addr(sta_if)); //IP地址转换ret = lwip_connect(s, (struct sockaddr *)&addr, sizeof(addr));
If(ret < 0){
//失败
lwip_close(s);
}

Read()

从套接字中读取数据

函数原型:

ssize_t read(int fd, void *buf, size_t nbytes)

参数:

Fd:要读取的套接字描述符

Buf:要接收数据的缓冲区地址

Nbytes:要读取的数据的字节数

返回值:

-1 失败

其他值:成功则返回读取到的字节数,遇到文件结尾则返回0

实例:

uint8_t iobuf[512];
rlen = lwip_read(c, iobuf, sizeof(iobuf));  //读SOCKET数据
if (rlen < 0) {
//失败
}

Recv()

读取socket数据。
函数原型:
ssize_t read(int fd, void *buf, size_t nbytes, int flags)
参数:
Fd:
        要读取的套接字描述符
Buf:
        要接收数据的缓冲区地址
Nbytes:
        要读取的数据的字节数
Flags:
0:
        默认值,设置后该函数与read()相同。其他几个选项用到的很少。
MSG_PEEK:
        查看可读的信息。数据被复制到缓冲区中,但不会从输入队列中删除。函数返回当前准备接受的字节数。
MSG_OOB:(out-of-band data)
        指明是带外信息。带外数据是指连接双方的一方发生重要事情,想要迅速通知对方。这种通知在已经排队等待发送的任务“普通”数据之前发送。带外数据设计比普通数据有更高的优先级。
在网络上有两种类型的数据包,正常包和带外包。带外包可以通过检验一个TCP/IP包头的一个特定标志来决定。
MSG_WAITALL:
        仅当发生以下事件之一时,接收请求才会完成:

  • 调用方提供的缓冲区已完全满
  • 连接已关闭
  • 该请求已被取消或发生错误

注:这里MSG_WAITALL也只是尽量读全,在有中断的情况下,recv还是可能会被打断,造成没有读完指定的长度。所以即是是采用recv+MSG_WAITALL参数还是要考虑是否需要循环读取的问题。在实验中对于多数情况下recv+MSG_WAITALL还是可以读完数据的。所以相应的性能会比直接read进行循环读要好一些。
MSG_DONTWAIT:

  • 如果发现没有数据就直接返回
  • 如果发现有数据,采用有多少读多少的方式处理。

注:read完一次需要判断读到的数据长度再决定是否还需要再次读取。
返回值:
>0 接收到的数据大小
=0 对方调用了close API来关闭连接
<0 出错
EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
EBADF:sock不是有效的描述词
ECONNREFUSE:远程主机阻绝网络连接
EFAULT:内存空间访问出错
EINTR:操作被信号中断
EINVAL:参数无效
ENOMEM:内存不足
ENOTCONN:与面向连接关联的套接字尚未被连接上
ENOTSOCK:sock索引的不是套接字
实例:

rlen = lwip_read(c, iobuf, sizeof(iobuf),0);  //读SOCKET数据
if (rlen < 0) {
//失败
}

Write()

向套接字写数据

函数原型:

ssize_t write(int fd, const void *buf, size_t nbytes);

参数:

Fd:要写入的套接字描述符

Buf:要写入的数据的缓冲区地址

Nbytes:要写入的数据的字节数

返回值:

-1 失败

其他值:成功则返回写入的字节数

实例:

char send_data[] = "Hello Server!";
ret = lwip_write(s, send_data, sizeof(send_data));
if (rlen < 0) {
//失败
}

Send()

向套接字发送数据
函数原型:
ssize_t read(int fd, void *buf, size_t nbytes, int flags)
参数:
Fd:
要发送的套接字描述符
Buf:
要发送的数据的缓冲区地址
Nbytes:
要发送的数据的字节数
Flags:
0:
默认值。设置后,与write()函数功能相同
MSG_DONTROUTE:
绕过路由表查找
MSG_DONTWAIT:
非阻塞操作
MSG_OOB:
发送带外数据
返回值:
>0 发送的字节数(实际上是拷贝到发送缓冲中的字节数)
=0 对方调用了close API来关闭连接
<0 发送失败
EBADF 参数s 非合法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket
EINTR 被信号所中断
EAGAIN 此操作会令进程阻断,但参数s的socket为不可阻断
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确
实例:

char send_data[] = "Hello Server!";
ret = lwip_send(s, send_data, sizeof(send_data),0);
if (rlen < 0) {
//失败
}

Close()

关闭之前打开的套接字

函数原型:

int close(int fd);

参数:

Fd:要关闭的套接字描述符

返回值:

0 成功

其他值:失败

close一个TCP socket的缺省行为时把该socket标记为已关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求

实例:

lwip_close(s);

Select()

在一段指定的时间内,监听文件描述符上可读、可写和异常等事件。用select可以完成非阻塞,进程或线程执行到此函数时,不必非要等待事件的发生,会根据select返回结果来反映执行情况。
函数原型:

int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数:
Maxfdp:
被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的。
Readset:
可读文件描述符集合
Writeset:
可写文件描述符集合
Exceptset:
异常事件文件描述集合
Timeout:
设置超时时间。NULL表示无线等待,类似于阻塞。
timeval结构体:

struct timeval
{__time_t tv_sec;        /* Seconds.  */__suseconds_t tv_usec;    /* Microseconds.  */
};

返回值:
0 超时
-1 失败
>0 表示就绪描述符的数目

查看该函数底层源码,发现如下函数:

查看源码:

u32_t sys_arch_sem_wait(sys_sem_t *pxSemaphore, u32_t ulTimeout)
{TickType_t xStartTime, xEndTime, xElapsed;unsigned long ulReturn;xStartTime = xTaskGetTickCount();if (ulTimeout != 0UL) {if (xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_PERIOD_MS ) == pdTRUE) {xEndTime = xTaskGetTickCount();xElapsed = (xEndTime - xStartTime) * portTICK_PERIOD_MS;ulReturn = xElapsed;} else {ulReturn = SYS_ARCH_TIMEOUT;}} else {while (xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE);xEndTime = xTaskGetTickCount();xElapsed = (xEndTime - xStartTime) * portTICK_PERIOD_MS;if (xElapsed == 0UL) {xElapsed = 1UL;}ulReturn = xElapsed;}return ulReturn;
}

可以看到,该函数创建了一个信号量,然后等待信号量的到来。所以select函数的等待是将线程直接挂起。此时会让出CPU使用权给其他的线程使用。

以下介绍与select函数相关的常见的几个宏

#include <sys/select.h>
int FD_ZERO(int fd, fd_set *fdset);   //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset);  //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set);   //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

select使用范例:
        当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:

fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);   

然后调用select函数,拥塞等待文件描述符事件的到来;如果超过设定的时间,则不再等待,继续往下执行。

select(fd+1, &rset, NULL, NULL,NULL);    //一直阻塞等待
select返回后,用FD_ISSET测试给定位是否置位:
if(FD_ISSET(fd, &rset)
{
//检测到数据
}

select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度

如上所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的

实例:

fd_set fd;
struct timeval timeout;
timeout.tv_sec  = 60;   //秒
timeout.tv_usec = 0;    //微秒
FD_ZERO( &fd ); //一个df_set类型变量的所有位都设为0,将套接字集合清空
FD_SET( sock, &fd );    //设置变量的某个位。加入你感兴趣的套接字到集合,这里是一个读数据的套接字
nRet = lwip_select( sock+1, &fd, (fd_set *)NULL, (fd_set *)NULL, &timeout );
if ( nRet > 0 ) {if ( FD_ISSET(sock, &fd) > 0 ){
//读socket数据
}
}

Htonl() htons() ntohl() ntohs()

在编程的时候,往往会遇到自己的网络顺序和主机顺序的问题。这时就需要以上四个函数进行调节了。

htonl()--"Host to Network Long"ntohl()--"Network to Host Long"htons()--"Host to Network Short"ntohs()--"Network to Host Short"

        主机字节序就是我们平常说的大端小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

        网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是大端存储。请谨记务必将主机字转化为网络字节序再赋给socket

WIFI学习一(socket介绍)相关推荐

  1. [Android]Android P(9) WIFI学习笔记 - HAL (1)

    目录 前文回顾 前言 入口 WifiNative 初始化 打开WIFI IWifiChip IWifiCond ISupplicant 前文回顾 WIFI学习笔记 - Framework (1) WI ...

  2. python中socket模块常用吗_python网络学习笔记——socket模块使用记录

    此文章记录了笔者学习python网络中socket模块的笔记. 建议初次学习socket的读者先读一遍socket模块主要函数的介绍. socket模块的介绍可以参考笔者的前一篇关于socket官方文 ...

  3. 第2节Socket介绍

    Socket介绍 进程之间是怎么进行通信的 在学习Socket之前我们先来了解了解进程之间是怎么进行通信的. 本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类: 消息传递(管道.FIFO. ...

  4. camvid数据集介绍_深度学习图像数据集介绍(MSCOCO)

    深度学习图像数据集介绍(MSCOCO) MSCOCO数据集是微软开发维护的大型图像数据集,次数聚集的任务包括识别(recognition),分割(segementation),及检测(detectio ...

  5. 《从0到1学习Flink》—— 介绍Flink中的Stream Windows

    前言 目前有许多数据分析的场景从批处理到流处理的演变, 虽然可以将批处理作为流处理的特殊情况来处理,但是分析无穷集的流数据通常需要思维方式的转变并且具有其自己的术语(例如,"windowin ...

  6. [深度学习] 分布式Horovod介绍(四)

    [深度学习] 分布式模式介绍(一) [深度学习] 分布式Tensorflow介绍(二) [深度学习] 分布式Pytorch 1.0介绍(三) [深度学习] 分布式Horovod介绍(四) 实际应用中, ...

  7. [深度学习] 分布式Pytorch介绍(三)

    [深度学习] 分布式模式介绍(一) [深度学习] 分布式Tensorflow介绍(二) [深度学习] 分布式Pytorch介绍(三) [深度学习] 分布式Horovod介绍(四)  一  Pytorc ...

  8. [深度学习] 分布式模式介绍(一)

    [深度学习] 分布式模式介绍(一) [深度学习] 分布式Tensorflow介绍(二) [深度学习] 分布式Pytorch 1.0介绍(三) [深度学习] 分布式Horovod介绍(四) 一  分布式 ...

  9. 网络编程 socket介绍

    Socket介绍 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对 ...

  10. Hadoop学习之整体介绍及环境搭建

    Hadoop学习之整体介绍及环境搭建 1 大数据概述 1.1 什么是大数据 数据(data)是事实或观察的结果,是对客观事物的逻辑归纳,是用于表示客观事物的未经加 工的的原始素材. 大数据:传统处理方 ...

最新文章

  1. PIC单片机 按键检测识别
  2. Charles之过滤信息
  3. python刷b站教程_python + selenium 刷B站播放量的实例代码
  4. eShopOnContainers 是一个基于微服务的.NET Core示例框架
  5. Android之提示java.lang.RuntimeException: Parcel: unable to marshal value Image问题
  6. 笔记本电脑怎么清理灰尘_笔记本发热怎么办?这里有解决方案
  7. 查看CUDA和cuDNN的版本号
  8. 凸透镜成像实验软件_凸透镜成像6道例题(含详答)
  9. mysql 查询结果导出文件并导入文件到数据库
  10. mysql修改数据库与级别_数据库MySQL查看和修改事务隔离级别的实例讲解
  11. 巧用.bat批处理文件
  12. windows系统下压力测试工具(cpu使用率,内存使用率,磁盘使用率,磁盘空间)
  13. MUTA 人力Vocaloid不是梦
  14. tnl分析笔记之 CORBA 与假装自己是 CORBA
  15. ARIMA模型的拖尾截尾问题
  16. 官方微信支付跟特约商户的区别
  17. 陆源:阿贝尔对椭圆函数论的贡献[附椭圆函数、模形式(g_2,g_3)、模函数的C++程序计算]
  18. 有关联想拯救者Y7000重装window10系统
  19. 动态规划之神奇的口袋
  20. LeetCode题库:并查集问题(Python语言实现)

热门文章

  1. Cause: java.sql.SQLExceptioValue ‘0000-00-00 00:00:00‘ can not be represented as java.sql.Timestamp
  2. 关于富斯遥控器5号电池改锂电的问题探究
  3. Java+Appium+Junit demo
  4. L - Ray in the tube Gym - 101911L (暴力)
  5. 在Jetson Nano上安装RTL8821cu驱动
  6. 字节跳动疯狂招人,为何会有如此操作?
  7. 安卓一键清理内存_软件| 安卓系统最强悍微信清理软件,一键将所有垃圾文件搜索出来,还可预览删除...
  8. MyBatis SQL里的大于号、小于号
  9. python使用xlwt模块操作Excel
  10. makefile(9) : fatal error U1052: 未找到文件Win32.Mak