嵌入式技术论坛
直播中

卓侨汉

8年用户 1082经验值
擅长:MEMS/传感技术
私信 关注
[问答]

BSD socket是什么看了就知道

基础知识之BSD socket

回帖(1)

王琰

2021-3-31 17:08:42
Socket 通常也称作"套接字",是支持 TCP/IP 协议的网络通信应用的基本操作单元,可以用来实现网间不同虚拟机或不同计算机之间的通信。使用TCP/IP协议的应用程序通过在客户端和服务器各自创建一个 Socket ,然后通过操作各自的 Socket 就可以完成客户端和服务器的连接以及数据传输的任务了。
Socket 的本质是编程接口( API ),是对 TCP/IP 的封装。使开发者不需要面对复杂的 TCP/IP 协议族,只需要调用几个较简单的 Socket API 就可以完成网络通信了。
RT-Thread 中的 SAL 抽象层 提供完整的 BSD Socket 相关 API。
BSD Socket 相关 API

[tr] 名称 作用 [/tr]   
socket创建一个 socket 套接字
bind将端口号和 IP 地址绑定带指定套接字上
listen开始监听
accept接受连接请求
connect建立连接
send面向连接的发送数据(tcp)
recv面向连接的接收数据(tcp)
sendto无连接的发送数据(udp)
recvfrom无连接的接收数据(udp)
closesocket关闭 socket
shutdown按设置关闭套接字
gethostbyname通过域名获取主机的 IP 地址等信息
getsockname获取本地主机的信息
getpeername获取连接的远程主机的信息
ioctlsocket设置套接字控制模式
TCP/UDP

要学用套接字编程,一定要了解 TCP/UDP 协议。TCP/UDP 协议工作在 TPC/IP 协议栈的传输层,如下图所示:

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的协议,使用该协议时,可以保证客户端和服务端的连接是可靠和安全的。使用 TCP 协议进行通信之前,通信双方必须先建立连接,然后再进行数据传输,通信结束后终止连接。
优点:能保证可靠性、稳定性。
适用场景:TCP适合用于端到端的通信,适用于对可靠性要求较高的服务。
基于 TCP 的 socket 编程流程如下图所示:

UDP(User Datagram Protocol 用户数据报协议)是一种非面向连接的协议,它不能保证网络连接的可靠性。 客户端发送数据之前并不会去服务器建立连接,而是直接将数据打包发送出去。当服务器接收数据时它也不向发送方提供确认信息,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。
优点:控制选项少,无须建立连接,从而使得数据传输过程中的延迟小、数据传输效率高。
适用场景:UDP适合对可靠性不高,或网络质量有保障,或对实时性要求较高的应用程序。
基于 UDP 的 socket 编程流程如下图所示:

API 详解

socket

使用 socket 通信之前,通信双方都需要各自建立一个 socket。我们通过调用 socket 函数来创建一个 socket 套接字:
int socket(int domain, int type, int protocol)   函数参数
[tr] 参数 描述 [/tr]   
domain协议域
type类型
protocol传输协议
返回——
> = 0成功,返回一个代表套接字描述符的整数
< 0失败
domain 参数支持下列参数:
AF_INET    Ipv4  AF_INET6   Ipv6  AF_UNIX    UNIX 域 AF_UNSPEC  未指定   type 参数支持下列参数:
SOCK_DGRAM   长度固定的、无连接的不可靠的报文传递(UDP) SOCK_RAM     IP 协议的数据报接口 SOCK_STREAM  有序、可靠、双向的面向连接字节流(TCP)   protocol 参数:
通常是 0 ,表示按给定的 domain 和 type 选择默认传输协议。在 AF_INET 通信域中套接字类型 SOCK_STREAM 的默认传输协议是 TCP。在 AF_INET 通信域中套接字类型 SOCK_DGRAM 的默认传输协议是 UDP。
当对同一 domian 和 type 支持多个协议时,可以使用 protocol 参数选择一个特定协议。
函数返回
返回一个 socket 描述符,它唯一标识一个 socket。这个 socket 描述符 跟文件描述符 一样,后续的操作都有用到它,比如,把它作为参数,通过它来进行一些读写操作等。
bind

bind 函数用来将套接字与计算机上的一个端口号相绑定,进而在该端口监听服务请求,该函数的一般形式如下:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen)   函数参数
[tr] 参数 描述 [/tr]   
sockfd要绑定的 socket描述符
my_addr一个指向含有本机 IP 地址和端口号等信息的 sockaddr 结构的指针
addrlensockaddr 结构的长度
返回——
0成功
< 0失败
sockaddr 结构体定义如下:
struct sockaddr {   u8_t sa_len;   u8_t sa_family;   char sa_data[14]; };   在 IPv4 因特网域(AF_INET)中,我们使用 sockaddr_in 结构体来代替 sockaddr 结构体:
struct sockaddr_in {   u8_t            sin_len;   sa_family_t     sin_family;   in_port_t       sin_port;   struct in_addr  sin_addr;   char            sin_zero[SIN_ZERO_LEN]; };   其中,

  • sin_family 一般固定写 AF_INET;
  • sin_port 为套接字的端口号;
  • sin_addr 为套接字的 IP 地址,
  • sin_zero 通常全为 0,主要功能是为了与 sockaddr 结构在长度上保持一致。这样指向 sockaddr_in 的指针和指向 sockaddr 的指针可以互相转换。
一般情况下,可以将 sin_port 设为 0,这样系统会随机选择一个未被占用的端口号。同样,sin_addr 设为 INADDR_ANY,系统会自动填入本机的 IP 地址。
  注意事项
当调用 bind 函数时,不要将端口号设为小于 1024 的值,因为 1-1024 为系统的保留端口号,我们可以选择大于 1024 的任何一个未被占用的端口号。

listen

listen 函数用来将套接字设为监听模式,并在套接字指定的端口上开始监听,以便对到达的服务请求进行处理。listen 函数的一般形式如下:
int listen(int sockfd, int backlog)   函数参数
[tr] 参数 描述 [/tr]   
sockfd绑定后的 socket描述符
backlog连接请求队列可以容纳的最大数目
返回——
0成功
< 0失败
accept

accept 函数用来从完全建立的连接的队列中接受一个连接,它的一般形式如下:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)   函数参数
[tr] 参数 描述 [/tr]   
sockfd被监听的 socket 描述符
addr一个指向 scokaddr_in 结构的指针,存放提出连接请求的主机 IP 地址和端口号等信息
addrlen一个指向 socklen_t 的指针,用来存放 sockaddr_in 结构的长度
返回——
> = 0成功,返回新创建的套接字描述符
< 0失败
服务端接受连接后,accept 函数会返回一个新的 socket 描述符,线程可以使用这个新的描述符同客户端传输数据。
connect

connect 函数用来与服务器建立一个 TCP 连接,它的一般形式如下:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
addr指向 sockaddr 结构的指针,存放要连接的服务器的 IP 地址和端口号等信息
addrlensockaddr 结构体的长度
返回——
> = 0成功,返回新创建的套接字描述符
< 0失败
send

send 函数用来在面向连接的数据流 socket 模式下发送数据,send 函数的一般形式如下:
int send(int sockfd, const void *msg, size_t len, int flags)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
msg指向所要发送的数据区的指针
len要发送的字节数
flags控制选项,通常为 0
返回——
>0成功,返回发送的数据的长度
<=0失败
如果返回值小于 len 的话,你需要再次发送剩下的数据。
recv

recv 函数用来在面向连接的数据流 socket 模式下接收数据,recv 函数的一般形式如下:
int recv(int sockfd, void *buf, size_t len, int flags)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
msg指向存储数据的内存缓存区的指针
len缓冲区的长度
flags控制选项,通常为 0
返回——
> 0成功,返回接收的数据的长度
= 0目标地址已传输完并关闭连接
< 0失败
sendto

sendto 函数用来在无连接的数据报 socket 模式下发送数据,sendto 函数的一般形式如下:
int sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
msg指向所要发送的数据区的指针
len要发送的字节数
flags控制选项,通常为 0
to指向 sockaddr 结构体的指针,存放目的主机的 IP 和端口号
tolensockaddr 结构体的长度
返回——
> 0成功,返回发送的数据的长度
< = 0失败
recvfrom

recvfrom函数用来在无连接的数据报 socket 模式下接收数据,recvfrom 函数的一般形式如下:
int recvfrom(int sockfd, void*buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
msg指向存储数据的内存缓存区的指针
len缓冲区的长度
flags控制选项,通常为 0
from指向 sockaddr 结构体的指针,存放源主机的 IP 和端口号
fromlen指向 sockaddr 结构体的长度的指针
返回——
> 0成功,返回接收的数据的长度
= 0目标地址已传输完并关闭连接
< 0失败
closesocket

closesocket 在传输完数据之后关闭 socket  并释放资源的函数,closesocket 函数的一般形式如下:
int closesocket(int sockfd)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
返回——
0成功
< 0失败
shutdown

shutdown 允许进行单向的关闭操作,或是全部禁止掉,shutdown 函数的一般形式如下:
int shutdown(int sockfd, int how)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
how控制选项
返回——
0成功
< 0失败
how 参数支持下列参数:
SHUT_RD     关闭接收信道  SHUT_WR     关闭发送信道  SHUT_RDWR   将发送和接收信道全部关闭   函数返回
返回 0 表示成功
gethostbyname

此函数可以通过域名来获取主机的 IP 地址等信息,它的一般形式如下:
struct hostent* gethostbyname(const char*name)   函数参数
[tr] 参数 描述 [/tr]   
name主机域名
返回——
> 0成功,返回一个 hostent 结构体指针
< 0失败
name 可以是具体域名,如:“www.rt-thread.org”,也可以是 IP 地址,如:“192.168.2.56”
hostent 结构体定义如下:
struct hostent {     char  *h_name;      /* 主机正式域名 */     char **h_aliases;   /* 主机的别名数组 */     int    h_addrtype;  /* 协议类型,对于 TCP/IP 为 AF_INET  */     int    h_length;    /* 协议的字节长度,对于 IPv4 为 4 个字节 */     char **h_addr_list; /* 地址的列表*/ #define h_addr h_addr_list[0] /* 保持向后兼容 */ };   getsockname

此函数可以获取本地主机的信息,它的一般形式如下:
int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
namesockaddr 结构体指针,用来存储得到的主机信息
namelen指向 sockaddr 结构体的长度的指针
返回——
0成功
< 0失败
getpeername

此函数可以得到与本地主机连接的远程主机的信息,它的一般形式如下:
int getpeername(int socket, struct sockaddr *name, socklen_t *namelen)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
namesockaddr 结构体指针,用来存储得到的主机信息
namelen指向 sockaddr 结构体的长度的指针
返回——
0成功
< 0失败
ioctlsocket

设置套接字控制模式,它的一般形式如下:
int ioctlsocket(int sockfd, long cmd, void *arg)   函数参数
[tr] 参数 描述 [/tr]   
sockfdsocket 描述符
cmd套接字操作命令
arg操作命令所带参数
返回——
0成功
< 0失败
cmd 参数支持下列参数:
FIONBIO  开启或关闭套接字的非阻塞模式,arg 参数为 1 开启非阻塞,为 0 关闭非阻塞。    注意事项
在网络中都采用大端字节序,但是不同的嵌入式系统,其字节序不一定都是大端格式,相反小端字节序倒是很常见,比如 STM32。我们在设置 IP 和端口号时,要根据自己的平台特点进行必要的字节序转换。

下面给出套接字字节转换函数的列表:
htons() —— "Host to Network Short"  主机字节顺序转换为网络字节顺序 htonl() —— "Host to Network Long"   主机字节顺序转换为网络字节顺序 ntohs() —— "Network to Host Short"  网络字节顺序转换为主机字节顺序 ntohl() —— "Network to Host Long"   网络字节顺序转换为主机字节顺序   对于一个“192.168.2.1”这种字符串形式的 IP 地址,我们如何将其正确的转换为网络字节序呢?
可以使用 inet_addr(“192.168.2.1”),结果直接就是网络字节序了;
我们也可以使用 inet_ntoa()(“ntoa”代表“Network to ASCII”)函数将一个长整形的 IP 地址转换为一个字符串。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分