【SemiDrive G9X】 【PTG5.0】:Socket 通信接口介绍

一、概述

套接字(socket)是 Linux 下的一种进程间通信机制,使用套接字可以使得不同主机上的应用程序可以相关通信。套接字主要采用的是客户端-服务器之间的模式通信,其中内核向应用层提供了 socket 接口,换句话来说,socket 接口是应用层与 TCP/IP 协议通信的中间接口,对于用户来说,调用 socket 函数,可以对 UDP/TCP 等各种复杂的 TCP/IP 协议不用深入理解,只要了解 socket 规定,写出遵守 socket 通信的编程,即可符合 TCP/UDP 的标准。

接下来,将主要介绍基于 socket 通信过程以及 socket 编程的接口,大家可以根据这些接口去编写测试程序。

二、通信过程

Socket 通信过程主要采用的是客户端和服务器的工作模式,基本通信过程可以用以下流程图说明:

socket 通信过程

三、socket() 函数

Socket () 函数原型如下:

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

该函数包括三个参数,如下说明:

 第一个参数 domain,这个参数主要用于选择通信的协议族,可以选择的协议族如下表,这要列出几种常用的协议族,其他的可自行搜索,对于 TCP/IP 协议,一般选择 AF_INET 即可:

协议族名字

说明

AF_UNIX, AF_LOCAL

Local communication

AF_INET

IPv4 Internet protocols

AF_INET6

IPv6 Internet protocols

AF_NETLINK

Kernel user interface device

 第二个参数 type,主要是指定套接字的类型,目前主要支持的类型有以下:

type

说明

SOCK_STREAM

提供有序的、可靠的、双向的、基于连接的字节流,能保证数据正确传送到对方,用于TCP协议;可以支持带外数据传输机制。

SOCK_DGRAM

固定长度的、无连接的、不可靠的报文传递,用于UDP协议

SOCK_SEQPACKET

固定长度的、有序的、可靠的、面向连接的报文传递

第三个参数 protocol,主要是为给定的通信域和套接字类型选择默认协议,通常默认为0。

Socket() 函数的作用:类型 open() 函数作用,成功则返回 socket 描述符,之后可以直接对这个描述符进行读写操作;失败返回 -1,并且会设置 errno 变量以指示错误类型。

四、bind() 函数

bind() 函数原型如下所示:

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

第一个参数 sockfd,这个参数是前面 socket() 函数的描述符。
第二个参数 addr,这是一个指针,指向 struct sockaddr 类型变量,sockaddr_in 和 sockaddr 是并列的结构,一般直接使用 struct sockaddr_in 结构体代替 struct sockaddr 结构体,struct sockaddr_in 结构体如下:

struct sockaddr_in {

sa_family_t sin_family; /* 协议族 */

in_port_t sin_port; /* 端口号 */

struct in_addr sin_addr; /* IP 地址 */

unsigned char sin_zero[8]; /* 保留使用 */ 

};

第三个参数是第二个参数结构体的长度。

bind() 函数的作用:用于将一个 IP 地址或端口号与一个套接字进行绑定,成功返回 0,失败返回 -1,并设置 errno 以提示错误原因。

五、listen() 函数

listen() 函数原型如下:

int listen(int sockfd, int backlog);

第一个参数 sockfd,这个参数是前面 socket() 函数的描述符。       

第二个参数 backlog,用来描述 sockfd 的等待连接队列能够达到的最大值。在服务器进程正处理客户端连接请求的时候,可能还存在其它的客户端请求建立连接,因为TCP连接是一个过程,由于同时尝试连接的用户过多,使得服务器进程无法快速地完成所有的连接请求,那怎么办呢?直接丢掉其他客户端的连接肯定不是一个很好的解决方法。因此内核会在自己的进程空间里维护一个队列,这些连接请求就会被放入一个队列中,服务器进程会按照先来后到的顺序去处理这些连接请求,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限,这个backlog参数告诉内核使用这个数值作为队列的上限。而当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。

listen() 函数作用:在服务器进程中使用,让服务器进入进程监听状态,等待客户端的连接请求,成功返回 0,失败返回 -1,并设置 errno 以提示错误原因。

注意:无法在一个已经连接的套接字上指向此函数。

六、accept() 函数

accept() 函数原型如下:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept() 的参数同 bind() 函数一样,区别是 accept() 函数的关键在于返回一个新的套接字,这个套接字就是与执行 connect() 的客户端建立连接,这个套接字代表了服务器和客户端的一个连接,服务器通过这个套接字与客户端进行数据交互,发送/接收数据,失败返回 -1,并设置 errno 以提示错误原因。

accept() 函数的作用:用于服务器应用程序中,如果调用accept() 函数时,并没有客户端请求连接(等待连接队列中也没有等待连接的请求),此时accept() 会进入阻塞状态,直到有客户端连接请求到达为止。当有客户端连接请求到达时,accept() 函数与远程客户端之间建立连接,accept() 函数返回一个新的套接字。

七、connect() 函数

connect() 函数原型如下:

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

connect() 函数的参数和 bind() 函数一样,参数 addr 指定了待连接的服务器的 IP 地址以及端口号等信息,参数 addrlen 指定了 addr 指向的 struct sockaddr 对象的字节大小。

connect() 函数作用:该函数用于客户端应用程序中,建立与服务器连接,对于TCP连接来说,调用该函数将发生TCP连接的握手过程,并最终建立一个TCP连接,而对于UDP协议来说,调用这个函数只是在sockfd中记录服务器IP地址与端口号,而不发送任何数据,成功则返回0,失败返回-1,并设置errno以指示错误原因。

八、recv() 函数

recv() 函数原型如下:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

不论是客户端还是服务器都可以通过 revc() 函数读取网络数据,它与 read() 函数的功能是相似的。参数sockfd 指定套接字描述符,参数 buf 指向了一个数据接收缓冲区,参数 len 指定了读取数据的字节大小,参数 flags 可以指定一些标志用于控制如何接收数据。



函数recv()与read()很相似,但是recv()可以通过指定flags标志来控制如何接收数据,这些标志如下所示:

标志

描述

MSG_CMSG_CLOEXEC

为UNIX域套接字上接收的文件描述符设置执行时关闭标志

MSG_DONTWAIT

启动非阻塞操作(相当于O_NONBLOCK)

MSG_ERRQUEUE

接收错误信息作为辅助数据

MSG_OOB

如果协议支持,获取带外数据

MSG_PEEK

返回数据包内容而不真正取走数据包

MSG_TRUNC

即使数据包被截断,也返回数据包的长度

MSG_WAITALL

等待知道所有的数据可用(仅SOCK_STREAM)

九、send() 函数

send() 函数原型如下:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

send和write很相似,但是send可以通过参数flags指定一些标志,来改变处理传输数据的方式。这些标志如下所示:

标志

描述

MSG_CONFIRM

提供链路层反馈以保持地址映射有效

MSG_DONTROUTE

勿将数据包路由出本地网络

MSG_DONTWAIT

允许非阻塞操作(等价于使用 O_NONBLOCK)

MSG_EOR

如果协议支持,标志记录结束

MSG_MORE

延迟发送数据包允许写更多数据

MSG_NOSIGNAL

在写无连接的套接字时不产生 SIGPIPE 信号

MSG_OOB

如果协议支持,发送带外数据

注意: 即使send()成功返回,也并不表示连接的另一端的进程就一定接收了数据,我们所能保证的只是当 send 成功返回时,数据已经被无错误的发送到网络驱动程序上。

十、close() 函数

当不再需要套接字描述符时,可调用 close() 函数来关闭套接字,释放相应的资源。

十一、htons()/htonl() 函数

对于人来说,我们更容易阅读的是点分十进制的IP地址,譬如192.168.1.110、192.168.1.50,这其实是一种字符串的形式,但是计算机所需要理解的是二进制形式的IP地址,所以我们就需要在点分十进制字符串和二进制地址之间进行转换。

htons() 函数原型如下:

#include <arpa/inet.h>

uint16_t htons(uint16_t hostlog)

htons() 函数参数 hostlog 是主机字节顺序表达的 16 位数,返回值是 16 位的网络字节顺序,此函数的作用就是从主机字节顺序转换成网络字节顺序,示例如下:

server_addr.sin_port = htons(8888);

htonl() 函数原型如下:

uint16_t htonl(uint32_t hostlog)

htonl() 函数参数 hostlog 是主机字节顺序表达的 32 位数,返回值是 32 位的网络字节顺序,此函数的作用就是从主机字节顺序转换成网络字节顺序,示例如下:

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

其中 INADDR_ANY 转换过来就是 0.0.0.0,表示本机的所有 IP。

十二、参考文献

链接:(46条消息) Socket的基本操作函数socket()、bind()、listen()、connect()、accept()、recv()、send()、select()、close()_朱家小旺的博客-CSDN博客_socketclient



★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论