Unix套接字接口
简介
套接字是操作系统中用于网络通信的重要结构,它是建立在网络体系结构的传输层,用于主机之间数据的发送和接收,像web中使用的http协议便是建立在socket之上的。这一节主要讨论网络套接字。
套接字接口时一组函数,它们和Unix I/O结合起来,用于创建网络应用。许多操作系统都实现了自己的套接字接口。在Unix中,可以将套接字视为一个文件,使用文件I/O函数对套接字进行操作,这也贯彻了Unix中一切皆文件的思想。
既然是网络通信,那么就需要服务端和客户端,一个基本的客户端和服务端的通信模型如下,其中方框里为使用的函数接口:
由于套接字本质上也是一个文件,所以上图中的recv
和send
也可以使用文件I/O函数read
和write
替代。
套接字地址结构
既然要进行网络通信,那么就要有基本的网络的地址。因特网的套接字地址存放在一个叫sockaddr_in
的16字节的结构体中,其中的IP地址和端口号都是以网络字节序(大端序)存放的:
struct sockaddr_in {
uint16_t sin_family; /* 协议类型,总是AF_INET */
uint16_t sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* sizeof(struct in_addr) */
};
struct sockaddr {
uint16_t sin_family; /* 协议类型 */
char sa_data[14]; /* 地址信息 */
}
注意到,上面还有一个sockaddr
结构体类型。由于connect
、bind
和accept
都需要接受一个指向与协议相关的套接字地址结构指针。但是在设计这些接口时,如何定义这些接口,使之能够接受各种类型的套接字地址。为了解决这个问题,设计了sockaddr
这种通用的结构体,在使用这些接口时,都需要将与协议相关的结构体转化为这个通用的结构体。
创建和关闭套接字
使用socket
函数创建一个套接字,函数原型如下:
#include <sys/types.h>
#include <sys/socket.h>
/* 返回:若成功则为非负描述符,失败返回-1 */
int socket(int domain, int type, int protocol);
参数domain
(域)用于确定通讯的特性,具体如下:
域 | 描述 |
---|---|
AF_INET | IPv4因特网域 |
AF_INET6 | IPv6因特网域 |
AF_UNIX | UNIX域 |
AF_UPSPEC | 未指定 |
参数type
确定套接字的类型,进一步确定通讯特性:
类型 | 描述 |
---|---|
SOCK_DGRAM | 固定长度的,无连接的,不可靠的报文传递 |
SOCK_RAW | IP协议的数据报接口 |
SOCK_SEQPACKET | 固定长度的,有序的,可靠的,面向连接的报文协议 |
SOCK_STREAM | 有序的,可靠的,双向的,面向连接的字节流(TCP) |
参数protocol
通常是0,表示使用默认协议,其他选项这里不再赘述。
例如可以像下面这样创建一个套接字:
fd = socket(AF_INET, SOCK_STREAM, 0);
其中,AF_INET
表示我们使用32位IPv4地址,SOCK_STREAM
表示我们使用TCP协议。但是最好的方法使用getaddrinfo
函数来自动生成这些函数,这样我们的代码就与具体的协议无关了。我们会在下方进行讲述。
socket
函数返回的描述符只是部分打开的,还不能直接使用,取决于是作为客户端还是服务端,需要一些初始化工作。
shutdown
可以用来禁止一个套接字的I/O:
#include <sys/socket.h>
/* 返回:若成功返回1,若出错返回-1 */
int shutdown(int sockfd, int how);
参数how
指明了关闭套接字的方式:
标志 | 描述 |
---|---|
SHUT_RD | 关闭读端 |
SHUT_WR | 关闭写端 |
SHUT_RDWR | 关闭读写 |
尽管使用close
函数也够关闭套接字,但是和shutdown
函数却有两点不同:当对一个套接字描述符调用close
,该套接字的引用计数会减一,只有当引用计数为0时才会真正关闭套接字,而shutdown
不管引用计数是否为0,都会关闭套接字。另外,shutdown
可以只关闭套接字读写两个方向中的一个方向,保留另一个方向继续工作。
绑定地址
作为服务器,需要给套接字关联一个众所周知的地址,这样别人才能使用这个地址来对服务器进行访问。
使用bind
函数来关联地址和套接字:
#include <sys/socket.h>
/* 返回:若成功,返回0,出错,返回-1 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
bind
函数将告诉内核将addr
中的服务器套接字地址和套接字sockfd
关联起来。其中len
为sizeof(sockaddr_in)
。
对于使用的服务器地址有如下限制:
- 指定的地址必须有效,不能是其他机器的地址
- 地址必须和创建套接字时的地址族所支持的格式相匹配
- 地址中的端口号必须不小于1024,除非该进程有相应特权
- 一般只能将一个套接字端点绑定到一个地址。
建立监听
作为服务器,为了接受用户的连接,需要监听某个特定的端口。当客户请求连接这个端口时,便创建一个连接。
默认情况下,内核会认为socket
函数创建的套接字为主动套接字,它存在于用于连接的客户端。调用listen
函数来告诉内核描述符是被服务器所使用的,而不是客户端。
#include <sys/socket.h>
/* 返回:成功返回0,失败返回-1 */
int listen(int sockfd, int backlog);
listen
函数将一个主动套接字转化为一个监听套接字,该套接字可以接受来自客户端的连接请求。backlog
参数指明请求队列中未完成连接的数量。当队列已满,进程将拒绝后续的连接请求。
等待连接
一但服务器调用了listen
,所用的套接字就可以用来接受连接请求,使用accept
函数来接受客户端的连接请求。
#include <sys/socket.h>
/* 返回:若成功,返回非负连接描述符,失败返回-1 */
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
accept
返回的描述符是一个新的已连接的描述符,这个套接字描述符用来和客户端进行通信。这个新的套接字和原始套接字(sockfd
)具有相同的套接字类型和地址族,传送给accept
的原始套接字并没有关联到这个连接,继续可用并接受其他连接。
accept
函数会将客户端的地址信息填充到参数addr
指向的缓冲区,参数len
为缓冲区的大小。如果不需要这些信息。可以将addr
和len
设为NULL。
如果没有连接请求,accept
会阻塞到一个连接请求的到来。如果sockfd
处于非阻塞模式,则返回-1,并将errno
设置为EAGAIN
或EWOULDBLOCK
。
建立连接
对于客户端来说,如果要处理一个面向连接的网络服务(SOCK_STREAM
或SOCK_SEQPACKET
)。在交换数据之前,首先要在客户端和服务端之间建立一个连接。
使用connect
函数来建立一个连接:
#include <sys/socket.h>
/* 返回:成功返回0,出错误返回-1 */
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
connect
函数会试图与套接字地址为addr
的服务器建立一个连接,其中len
为sizeof(sockaddr_in)
。connect
函数会阻塞,直到完成连接的建立或出错。随后,描述符sockfd
便可进行读写了。
发送和接收
可以使用send
函数来发送数据:
#include <sys/socket.h>
/* 返回:若成功,返回发送的字节数,若出错,返回-1 */
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
send
函数与用于文件读写的write
函数基本一致,只是多了第四个参数flags
,一般置为0。
使用recv
来接收数据:
#include <sys/socket.h>
/* 返回:返回数据的字节长度,若无可用数据或对方已经按序结束,返回0,若出错返回-1 */
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
recv
函数与read
函数基本一致,只是多了第四个参数flags
,来管理如何接收数据,一般置为0。
主机和服务的转换
在前面对套接字的创建中,我们采用手动指明协议的方式,但是这样编写并不恰当。Linux提供了一些函数用于主机名和服务名与地址之间的相互映射,使用这些函数可以使我们的程序独立于任何特定版本的IP协议。下面看一下这些函数。
getaddrinfo
函数用于将主机名,主机地址,服务名和端口号的字符串表示转化为套接字地址结构。
#include <sys/socket.h>
#include <netdb.h>
/* 返回:若成功返回0,若出错返回非零错误码 */
int getaddrinfo(const char *restrict host,
const char *restrict service,
const struct addrinfo *restrict hint,
strcut addrinfo **restrict res);
/* 返回:无 */
void freeaddrinfo(struct addrinfo *ai);
/* 返回:错误消息字符串 */
const char *gai_strerror(int errcode);
host
参数指定主机地址,可以是域名或者点分十进制的IP地址。service
参数可以是服务名也可以是十进制的端口号。主机名和服务名可以两者都提供,也可以只提供一个,但另一个必须是一个空指针。
getaddrinfo
函数返回一个链表结构addrinfo
,res
指向该结构的第一个节点。使用freeaddrinfo
函数可以释放该结构。addrinfo
结构的定义至少包含以下成员:
struct addrinfo {
int ai_flags; /* customize behavior */
int ai_family; /* address family,first arg to socket function */
int ai_socktype; /* socket type,second arg to socket function */
int ai_protocol; /* protocol,third arg to socket function */
char *ai_canonname; /* canonical name for hostname */
socklen_t ai_addrlen; /* length in byte of address */
struct sockaddr *ai_addr; /* address */
struct addrinfo *ai_next; /* next in list */
...
};
getaddrinfo
可以指定一个可选的hint
来过滤符合条件的地址,包括ai_family
、ai_flags
、ai_protocol
、ai_socktype
字段。剩余的字段必须置为0或空指针。
下表总结了ai_flags
字段的标志,这些标志可以通过or运算指定多个:
标志 | 描述 |
---|---|
AI_ADDRCONFIG | 查询配置的地址类型(IPv4或IPv6)。使用连接时推荐。只有当本地主机被配置为IPv4时,getaddrinfo 返回IPv4地址。对IPv6同样 |
AI_ALL | 查找IPv4和IPv6地址(仅用于AI_V4MAPPED) |
AI_CANONNAME | 需要一个规范的名字(与别名相对) |
AI_NUMERICHOST | 以数字格式指定主机地址 |
AI_NUMERICSERV | 将服务指定为数字端口号 |
AI_PASSIVE | 套接字地址用于监听绑定 |
AI_V4MAPPED | 如果没有找到IPv6地址,返回映射到IPv6格式的IPv4地址 |
如果getaddrinfo
出错,可以将返回的错误码传入gai_strerror
函数获取具体的错误信息。
接下来的getnameinfo
函数与getaddrinfo
函数功能相反,用于将一个地址转化为一个主机名和一个服务名:
#include <sys/socket.h>
#include <netdb.h>
/* 返回:若成功返回0,出错返回非0值 */
int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen,
char *restrict host, socklen_t hostlen,
char *restrict service, socklen_t servlen,
int flags);
getnameinfo
将字节长度为alen
的套接字地址addr
翻译成一个主机名和一个服务名,如果host
非空,则指向一个字节长度为hostlen
的缓冲区用于存放主机名。同样的,如果service
非空,则指向一个长度为servlen
的缓冲区来存放服务名。flags
参数指定了函数工作的方式,如下:
标志 | 描述 |
---|---|
NI_DGRAM | 服务基于数据报而非基于流 |
NI_NAMEREQD | 如果找不到主机名,将其作为一个错误对待 |
NI_NOFQDN | 对于本地主机,仅返回全限定域名的节点名部分 |
NI_NUMERICHOST | 函数默认返回host中的域名,该标志指定返回主机的数字形式,而非主机名 |
NI_NUMERICSCOPE | 对于IPv6,返回范围ID的数字形式,而非名字 |
NI_NUMERICSERV | 函数默认检查/etc/services,如果可能会返回服务名而不是端口号。该标志指定跳过检查,返回服务地址的数字形式(端口号),而非名字 |
下面一个来自书中的例子展示域名到它IP地址的映射:
/* main.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLEN 8192
int main(int argc, char **argv) {
struct addrinfo *p, *listp, hints;
char buf[MAXLEN], buf1[MAXLEN];
int rc, flags;
if(argc != 2) {
fprintf(stderr, "argv error\n");
exit(-1);
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
exit(-1);
}
flags = NI_NUMERICHOST;
for(p = listp; p; p = p->ai_next) {
getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLEN, buf1, MAXLEN, flags);
printf("%s %s\n", buf, buf1);
}
freeaddrinfo(listp);
exit(0);
}
使用如下:
$ gcc main.c -o main
$ ./main baidu.com
39.156.69.79 0
220.181.38.148 0
小结
unix提供了一组函数用于创建套接字并完成数据传输:
- 使用
getaddrinfo
函数和socket
函数来创建套接字,可以使程序更具有通用性。 - 服务端使用
bind
给套接字绑定地址,使用listen
指定套接字用于监听,并使用accept
接受连接 - 客户端使用
connect
来连接到服务器 - 客户端和服务端都可以使用
send
和recv
来传递数据 - 使用
close
或shutdown
函数来关闭套接字
总结自《深入理解计算机系统》《Unix环境高级编程》
Unix套接字接口的更多相关文章
- 网络IPC:套接字接口概述
网络IPC:套接字接口概述 套接字接口实现了通过网络连接的不同计算机之间的进程相互通信的机制. 套接字描述符(创建套接字) 套接字是通信端点的抽象,为创建套接字,调用socket函数 #include ...
- Linux/UNIX套接字连接
套接字连接 套接字是一种通信机子.凭借这样的机制.客户/server系统的开发工作既能够在本地单机上进行.也能够夸网络进行. 套接字的创建和使用与管道是有差别的.由于套接字明白地将客户和server区 ...
- 【T05】套接字接口比XTI_TLI更好用
1.用于网络编程的API接口有两种: Berkeley套接字 XTL 2.套接字是加州大学伯克利分校为其Unix操作系统版本开发的,TLI是AT&T(贝尔实验室)为Unix系统V3.0开发的 ...
- fsockopen — 打开一个网络连接或者一个Unix套接字连接
fsockopen (PHP 4, PHP 5, PHP 7) 说明 resource fsockopen ( string $hostname [, int $port = -1 [, int &a ...
- 一个基于Unix套接字的注册登录系统
2016/5/5 今天,我参考<Unix网络编程-卷1>第5章的TCP回射客户/服务器程序写了一个简单的注册登录系统,其功能如下:(1)注册.客户端向服务器发送个人信息请求注册,服务器查询 ...
- UNIX网络编程——网络IPC:套接字
UNIX网络编程——网络IPC:套接字 Contents 套接字接口 套接字描述符 寻址 字节序 地址格式 地址查询 绑定地址 建立连接 数据传输 套接字选项 带外数据 UNIX域套接字 使用套接字的 ...
- socket , 套接口还是套接字,傻傻分不清楚
socket 做网络通信的朋友大都对socket这个词不会感到陌生,但是它的中文翻译是叫套接口还是套接字呢,未必大多数朋友能够分清,今天我们就来聊聊socket的中文名称. socket一词的起源 在 ...
- UNIX 网络编程笔记-CH3:套接字编程简介
IPv4套接字地址结构 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* length of ...
- unix, PF_UNIX, AF_UNIX, PF_LOCAL, AF_LOCAL - 用于本地内部进程通讯的套接字。
SYNOPSIS(总览) #include <sys/socket.h> #include <sys/un.h> unix_socket = socket(PF_UNIX, t ...
随机推荐
- Element-ui组件库Table表格导出Excel表格
安装npm install --save xlsx file-saver 两个插件的详细地址在下面 https://github.com/SheetJS/js-xlsxhttps://github.c ...
- laravel环境安装
参考 https://blog.csdn.net/xiaomayi721025/article/details/84727405 环境准备
- 2.Map中hashMap和hashTable两个的对比
我们来对比一下hashMap和hashTable吧: 1.hashMap允许键.值可以为空,hashTable键和值都不可以为空,为什么这样呢,我们来看一下他们的put方法的源码. 先看hashMap ...
- CI框架Email类发送邮件提示Unable to send data: . The following SMTP error was encountered: Unable to .......
最近服务器迁移,然后CI框架做的项目发邮件全挂掉了,刚开始是25端口没开,然后开了正好还是有问题, 1.打印请求信息和返回信息 echo $this->email->print_debug ...
- HTML5-语义化
什么是语义化?就是用合理.正确的标签来展示内容,比如h1~h6定义标题. 语义化优点: 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构. 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权 ...
- Cut Ribbon
Polycarpus has a ribbon, its length is n. He wants to cut the ribbon in a way that fulfils the follo ...
- java程序启动参数
例如 启动进程如下 /home/work/noah/ccs/jc-controller/jdk1.7.0_55/bin/java -Xmx4096m -Xms4096m -Xmn1024m -XX:+ ...
- EAC3 Spectral Extension Process
1.overview 当使用Spectral extension时,channel中的高频部分的transform coefficients由低频部分合成. transform coefficient ...
- [AGC027E]ABBreviate
Description AGC027E 给定一个仅由\(AB\)构成的字符串\(S\),给定两个操作,把\(AA\)换成\(B\),和把\(BB\)换成\(A\),问由这个字符串和任意次操作可以得到几 ...
- Perl unless
在perl的if控制结构中,只有当条件表达式为真时才执行某块代码.如果想让程序块在条件为假时才执行,此时可以把if改成unless 例如: unless ($fred =~ /^([A-Z_]\w*$ ...