您的位置:首页 > 理论基础 > 计算机网络

Linux 进程间通信(二)(网络IPC:套接字)

2015-09-05 16:58 274 查看

socket描述符

套接字是通信端点的抽象,创建一个套接字使用如下函数:

#include <sys/socket.h>

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

返回值:若成功,返回套接字描述符;若出错,返回-1

说明:

domain: 指定通信的特征,包括地址格式,以AF_开头的常数表示地址族(address family):



说明

AF_INET

IPv4因特网域

AF_INET6

IPv6因特网域

AF_UNIX

UNIX域

AF_UPSPEC

未指定(可以代表任何域)

type: 指定套接字的类型。如下为POSIX.1的套接字类型,也可以增加其他类型的支持:

类型

说明

SOCK_DGRAM

固定长度、无连接、不可靠

SOCK_RAM

IP协议的数据报接口

SOCK_SEQPACKET

固定长度、面向连接、有序、可靠

SOCK_STREAM

有序、可靠、双向、面向连接

protocol: 根据指定的domain和type所提供的默认协议,通常设为0即可。在AF_INET通信域中,SOCK_STREAM默认的协议为TCP;SOCK_DGRAM默认的协议为UDP。

使用无连接的数据报通信类似于邮寄信件,你不能保证传递的次序,信件可能会丢失,每封信都包含收信人地址;相反地,使用面向连接的通信协议类似于打电话,首先需要建立连接,连接建立好以后,彼此可以双向地通信,对话中不需要包含地址信息,连接本身指定了通话的源和目的,就像是端与端之间有一条虚拟链路一样。

SOCK_STREAM套接字提供了字节流服务,应用程序不能分辨出报文的界限;SOCK_SEQPACKET套接字提供了基于报文的服务,这意味着接受的数据量和发送的数据量完全一致;SOCK_RAW套接字提供了一个数据报接口,用于直接访问下面的网络层,使用该套接字时,必须有root用户权限,并且需要应用程序自己负责构造协议头。

套接字通信是双向的,可以使用shutdown函数来禁止一个套接字I/O:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

返回值:若成功,返回0;若出错,返回-1。

说明:

how: SHUT_RD(关闭读),SHUT_WR(关闭写),SHUT_RDWR(关闭读写).

套接字是一个文件描述符,那么就可以使用close释放一个套接字。既然如此,为何还使用shutdown呢?首先,只有最后一个活动引用关闭时,close才释放网络端点。这意味着如果复制了一个套接字(dup),要直到关闭了最后一个引用它的文件描述符才会释放这个套接字。而shutdown允许使一个套接字处于不活动状态,和引用它的文件描述符数目无关。其次,有时可以很方便地关闭套接字双向传输中的一个方向。

补充:套接字本质上是一个文件描述符,如下为适用于文件描述符的函数在套接字中的表现行为:

函数

说明

close

释放套接字

dup / dup2

复制套接字

fchdir

失败,并且将errno设置为ENOTDIR

fchomod

未指定

fchown

由实现定义

fcntl

支持某些命令

fdatasync / fsync

由实现定义

fstat

支持一些stat结构成员,如何支持由实现定义

ftruncate

未指定

ioctl

依赖于底层设备驱动

lseek

由实现定义,失败时将errno设置为ESPIPE

mmap

未指定

poll

正常工作

pread / pwrite

失败时,将errno设置为ESPIPE

read / readv

与没有任何标志位的recv等价

select

正常工作

write / writev

与没有任何标志位的send等价

寻址

不同的处理器架构有着不同的字节序,如果需要实现异构通信,必须统一字节序方式。网络协议指定了字节序(网络字节序),TCP/IP协议栈使用大端方式,对于使用TCP/IP的应用程序,有4个函数可以用来在处理器字节序和网络字节序之间进行转换:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32); // 32位主机-->32位网络

uint16_t htons(uint16_t hostint16); // 16位主机-->16位网络

uint32_t ntohl(uint32_t netint32); // 32位网络-->32位主机

uint16_t ntohs(uint16_t netint16); // 16位网络-->16位主机

说明:

h: 主机字节序

n: 网络字节序

l: 4字节的长整型

s: 2字节的短整型

int initserver(int type, const struct sockaddr* addr, socklen_t alen, int qlen)
{
int fd;
int err = 0;

if((fd = socket(addr->sa_family, type, 0)) < 0)
return -1;
if(bind(fd, addr, alen) < 0)
goto errout;
if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
if(listen(fd, qlen) < 0)
goto errout;
return fd;

errout:
err = errno;
close(fd);
errno = err;
return -1;
}


View Code

数据传输

因为一个套接字表示为一个文件描述符,因此只要建立连接,就可以使用read和write来操作套接字了。这意味着可以将套接字传递给处理文件的函数,而该文件处理函数并不需要了解套接字即可工作。

除了read和write,还有6个专为套接字设计的函数:3个用于发送数据,3个用于接收数据。

3个用于发送数据的函数如下:

#include <sys/socket.h>

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

返回值:成功,返回发送的字节数;失败,返回-1

说明:

send类似于write,使用send时套接字必须已经连接。然而,send支持选项flags,如下:

Linux标志

描述

MSG_CONFIRM

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

MSG_DONTROUTE

禁止将数据报路由出本地网络

MSG_DONTWAIT

允许非阻塞通信

MSG_EOR

标记结束

MSG_MORE

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

MSG_NOSIGNAL

在无连接的情况下不产生SIGPIPE信号

MSG_OOB

允许发送带外数据

send的成功返回表示数据被无差错地发送到网络驱动程序中,并不表示对方成功地接受了数据。对于支持报文边界的协议,如果发送的单个报文长度超过了协议所支持的最大长度,那么send发送失败,并将errno设置为EMSGSIZE;对于字节流协议,send会阻塞直到整个数据传输完成。

#include <sys/socket.h>

ssize_t sento(int sockfd, const void* buf, size_t nbytes, int flags, const struct sockaddr* destaddr, socklen_t destlen);

返回值:成功,返回发送的字节数;失败,返回-1

说明:

与send相比,sendto可以指定目的地址,当然这是对于无连接的情况而言的,对于面向连接的情况,目的地址是被忽略的,因为连接中隐含了地址。在无连接的情况下,可以直接使用sendto,或者先使用connect设置目的地址,然后使用send发送数据。

通过套接字发送数据时,还可以调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据,这与writev类似:

#include <sys/socket.h>

ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags);

返回值:成功,返回发送的字节数;失败,返回-1

struct msghdr

{

void* msg_name; /* 目的地址名字,是一个指向结构体struct sockaddr的指针 */

socklen_t msg_namelen; /* 目的地址的长度 */

struct iovec* msg_iov; /* 消息内容,指向struct iovec的指针 */

int msg_iovlen; /* 长度 */

void* msg_control; /* 控制消息 */

socklen_t msg_controllen;

int msg_falgs; /* 标记如何接受数据 */

}

sendmsg函数可以通过msghdr指定多个缓冲区发送数据,同时可以发送辅助数据。

相应地,3个用于接收数据的函数如下:

#include <sys/socket.h>

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

返回值:成功,返回接收数据的字节长度;若未接收到数据或发送方已结束发送,返回0; 失败,返回-1

说明:

flags如下:

Linux标志

描述

MSG_CMSG_CLOEXEC

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

MSG_DONTWAIT

启用非阻塞接受

MSG_WAITALL

等待直到所有可用数据到达(SOCK_STREAM)

MSG_ERRQUEUE

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

MSG_PEEK

仅查看数据包内容而不真正接收数据包

MSG_TRUNC

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

MSG_OOB

接受带外数据

如果发送者已经调用了shutdown结束传输,或者网络协议支持按默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv返回0。

可以使用recvfrom来得到数据发送者的地址:

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,struct sockaddr* addr, socklen_t* addrlen);

返回值:成功,返回接收的实际字节数;若无可用数据或对方已经结束传输,返回0;失败,返回-1

说明:

recvfrom函数可以用于追踪发送者的地址,将其存储在addr指向的结构体中,addrlen表示其长度。该函数一般用于无连接的套接字通信中,在面向连接的情况下,等同于recv。

为了将接收到的数据送入多个缓冲区,可以使用recvmsg,类似于readv:

#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);

返回值:成功,返回接收的实际字节数;若无可用数据或对方已经结束传输,返回0;失败,返回-1

说明:

recvmsg可以通过msghdr指定多个缓冲区接收数据,同时也可以接收辅助数据,其flags标志如下:

Linux标志

描述

MSG_CTRUNC

控制数据被截断

MSG_EOR

接受记录结束符

MSG_ERRQUEUE

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

MSG_TRUNC

一般数据被截断

MSG_OOB

接受带外数据

套接字选项

套接字选项提供了两个接口来控制套接字行为:一个接口用来设置选项,另一个接口用于查询选项的状态。可以获取或设置以下3种选项:

(1) 通用选项,工作在所有套接字类型上。

(2) 在套接字层次管理的选项,但是依赖于下层协议的支持。

(3) 特定于某协议的选项,每个协议独有。

可以使用setsockopt函数来设置套接字选项:

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int option, const void* val, socklen_t len);

返回值:成功,返回0;失败,返回-1

说明:

level标识了选项应用的协议。如果选项是通用的套接字层次选项,择level设置成SOL_SOCKET。否则,level设置成控制这个选项的协议编号。对于TCP选项,level为IPPROTO_TCP,对于IP,level为IPPROTO_IP。如下为通用套接字层次选项:



val根据选项的不同指向一个数据结构或一个整数,一些选项是on/off开关。如果整数非0,则启动该选项,如果为0,则禁止该选项。

len指定了val指向的对象的大小。

可以使用getsockopt函数查看选项的当前值:

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int option, void* val, socklen_t* restrict lenp);

返回值:成功,返回0;失败,返回-1

说明:

lenp是一个指向整数的指针。在调用getsockopt之前,设置该整数为复制选项缓冲区的长度。如果选项的实际长度大于此值,则选项会被截断;如果实际长度小于此值,那么返回时将此值更新为实际长度。

带外数据

与普通数据相比,带外数据拥有更高的传输优先级,即使传输队列中已经有数据了,带外数据也可先行传输。TCP支持带外数据,UDP不支持。TCP将带外数据称为紧急数据(urgent data,TCP仅支持一个字节的紧急数据。可以在send函数中指定MSG_OOB标志来产生紧急数据(指定MSG_OOB标志的send发送的字节数如果超过一个时,则最后一个字节将被视为紧急数据)。

如果通过套接字安排了信号的产生,那么紧急数据被接收时,会发送SIGURG信号。

TCP还支持紧急标记(urgent mark的概念,即在普通数据流中标记紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接受紧急数据,可以使用函数sockatmark判断是否到达了紧急标记处。

#include <sys/socket.h>

int sockatmark(int sockfd);

返回值:到达标记处,返回1;没到达,返回0;出错,返回-1

说明:

可以在普通数据流中接收紧急数据,也可以在recv函数中采用MSG_OOB标志在其他队列数据之前接收紧急数据。

TCP队列仅用一个字节接收紧急数据,如果在接收当前紧急数据前又有新的紧急数据到来,那么已有的字节会被丢弃。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: