第十六章 网络IPC 套接字

套接字描述

套接字是通信端点的抽象

套接字描述符:正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字

套接字描述符在 UNIX 系统中被当作是一种文件描述符

许多处理文件描述符的 函数(如 read 和 write)可以用于处理套接字描述符

为创建一个套接字,调用 socket 函数

#include <sys/socket.h>
int socket(int domain,int type,int protocol);

参数 domain(域)确定通信的特性

AF_INET IPv4

AF_INET6 IPv6

AF_UNIX 别名 AF_LOCAL unix域

AF_UPSPEC

参数 type 确定套接字的类型,进一步确定通信特征

SOCK_DGRAM 默认UDP 无连接 报文

SOCK_RAW 直接访问下面的网络层 应用程序负责构造自己的协议头部,这是因为传输协议(如 TCP 和 UDP) 被绕过了

SOCK_SEQPACKET 面向连接 报文

SOCK_STREAM 默认tcp 面向连接 字节流

参数 protocol 通常是 0,表示为给定的域和套接字类型选择默认协议

当对同一域和套接字 类型支持多个协议时,可以使用 protocol 选择一个特定协议

因特网域套接字定义的协议:

IPPROTO_IP IPv4

IPPROTO_IPV6 IPv6

IPPROTO_ICMP

IPPROTO_RAW

IPPROTO_TCP tcp

IPPROTO_UDP udp

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

#include <sys/socket.h>
int shutdown(int sockfd,int flow);

如果 how 是 SHUT_RD(关闭读端),那么无法从套接字读取数据。
如果 how 是 SHUT_WR(关闭写 端),那么无法使用套接字发送数据。
如果 how 是 SHUT_RDWR,则既无法读取数据,又无法发送数据

寻址

进程标识:计算机的网络地址 + 计算机上用端口号表示的服务

字节序

big-endian:最大字节地址出现在最低有效字节

little-endian:最低有效字节包含最小字节地址

不管字节如何排序,最高有效字节总是在左边msb,最低有效字节总是在右边lsb

0x04030201:msb包含4,lsb包含1

大端 cp来存,cp[0]=4

小端 cp来存,cp[0]=1

网络协议指定了字节序,TCP/IP 协议栈使用大端字节序

对于 TCP/IP 应用程序,有 4 个用来在处理器字节序和网络字节序之间实施转换的函数

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);//返回值:以网络字节序表示的 32 位整数
uint16_t htons(uint16_t hostint16);//返回值:以网络字节序表示的 16 位整数
uint32_t ntohl(uint32_t netint32);//返回值:以主机字节序表示的 32 位整数
uint16_t ntohs(uint16_t netint16);//返回值:以主机字节序表示的 16 位整数

h 表示“主机”字节序,n 表示“网络”字节序。l 表示“长”(即 4 字节)整数,s 表示“短” (即 4 字节)整数

地址格式

为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构 sockaddr
struct sockaddr{
    sa_family_t sa_family;
    char sa_data[];//linux sa_data[14];
    ...
}
在 IPv4 因特网域(AF_INET)中,套接字 地址用结构 sockaddr_in 表示
struct socketaddr_in{
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;
}
struct in_addr{
    in_addr_t s_addr;
}
与 AF_INET 域相比较,IPv6 因特网域(AF_INET6)套接字地址用结构 sockaddr_in6 表示
struct socketaddr_in6{
    sa_family_t sin6_family;
    in_port_t sin6_port;
    uint32_t sin6_flowinfo;
    uint32_t sin6_scope_id;
    struct in6_addr sin6_addr;
}
struct in6_addr{
    uint8_t s6_addr[16];
}

尽管 sockaddr_insockaddr_in6 结构相差比较大,但它们均被强制转换成 sockaddr 结构输入到套接字例程中

二进制地址格式与点分十进制字符表示(a.b.c.d)之间的相互转换的两个函数

#include <arpa/inet.h>
const char *inet_ntop(int domain,const void *restrict addr,char *restrict str,socketlen_t size);
int inet_pton(int domain,const char *restrict str,void *restrict addr);

参数 domain 仅支持两个值:AF_INET 和 AF_INET6

地址查询

通过调用 gethostent,可以找到给定计算机系统的主机信息

#include <netdb.h>
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);
计算机系统的主机信息
struct hostent{
    
}

能够采用一套相似的接口来获得网络名字和网络编号

#include <netdb.h>
struct netent *getnetbyaddr(uint32_t net,int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);
网络名字和网络编号
struct netent{
    
}

可以用以下函数在协议名字和协议编号之间进行映射

#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);

void setprotoent(int stayopen);
void endprotoent(void);
协议
struct protoent{
    
}

可以 使用函数 getservbyname 将一个服务名映射到一个端口号,使用函数 getservbyport 将一 个端口号映射到一个服务名,使用函数 getservent 顺序扫描服务数据库。

#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getserbyport(int port, const char *proto);
struct servent *getservent(void);

void setservent(int stayopen);
void endservent(void);
服务名
struct servent{
    
}

getaddrinfo 函数允许将一个主机名和一个服务名映射到一个地址

#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host,
                const char *restrict service,
                const struct addrinfo *restrict hint,
                struct addrinfo **restrict res);

void freeaddrinfo(struct addrinfo *ai);
struct addrinfo{
    int ai_flags;
    int ai_family;// domain 域
    int ai_socktype;//类型
    int ai_protocol;//协议
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
    char *ai_canonname;
    struct addrinfo *ai_next;
    ...
}

可以提供一个可选的 hint 来选择符合特定条件的地址。hint 是一个用于过滤地址的模板,包 括 ai_family、ai_flags、ai_protocol 和 ai_socktype 字段

getnameinfo 函数将一个地址转换成一个主机名和一个服务名

#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *restrict addr, 
                socklen_t alen,
                char *restrict host, 
                socklen_t hostlen,
                char *restrict service, 
                socklen_t servlen, 
                int flags);

套接字与地址关联

给一个接收客户端请求的服务器套接字关联上一个众所周知的地址

客户端应 有一种方法来发现连接服务器所需要的地址,最简单的方法就是服务器保留一个地址并且注册在 /etc/services 或者某个名字服务中

使用 bind 函数来关联地址和套接字

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

可以调用 getsockname 函数来发现绑定到套接字上的地址

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict alenp);
int getpeername(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict alenp);

建立连接

#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socket_t len);

服务器调用 listen 函数来宣告它愿意接受连接请求。

#include <sys/socket.h>
int listen(int sockfd, int backlog);

参数 backlog 提供了一个提示,提示系统该进程所要入队的未完成连接请求数量

一旦服务器调用了 listen,所用的套接字就能接收连接请求。使用 accept 函数获得连接 请求并建立连接

#include <sys/socket.h>
accept(int sockefd,struct socketaddr *addr,socklen_t *restrict len);

函数 accept 所返回的文件描述符是套接字描述符,该描述符连接到调用 connect 的客户端

这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族

传给 accept 的原始套接字没有关联到这个连接,而是继续保持可用状态并接收其他连接请求

返回时,accept 会在缓冲区填充客户端的地址,并且更新指向 len 的整数来反映该地址的大小

如果没有连接请求在等待,accept 会阻塞直到一个请求到来。如果 sockfd 处于非阻塞模式, accept 会返回−1,并将 errno 设置为 EAGAIN 或 EWOULDBLOCK

数据传输

尽管可以通过 read 和 write 交换数据,但这就是这两个函数所能做的一切

3 个函数用来发送数据,3 个用于接收数据

最简单的是 send,它和 write 很像,但是可以指定标志来改变处理传输数据的方式

对于字节流协议,send 会阻塞直到整个数据传 输完成。函数 sendto 和 send 很类似。区别在于 sendto 可以在无连接的套接字上指定一个目 标地址

#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t nbytes,int flags);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,const struct sockaddr *destaddr, socklen_t destlen);

通过套接字发送数据时,还有一个选择。可以调用带有 msghdr 结构的 sendmsg 来指定多 重缓冲区传输数据,这和 writev 函数很相似

#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

函数 recv 和 read 相似,但是 recv 可以指定标志来控制如何接收数据。

#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags);
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
//因为可以获得发送者的地址,recvfrom 通常用于无连接的套接字。否则,recvfrom 等同于recv

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

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

套接字选项

套接字机制提供了两个套接字选项接口来控制套接字行为

  1. 通用选项,工作在所有套接字类型上
  2. 在套接字层次管理的选项
  3. 特定于某协议的选项
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);

参数 level 标识了选项应用的协议

带外数据

TCP 支持带外数 据,但是 UDP 不支持

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 网络模型 物理层 物理层表示的是比特流传输,通常包括串口/COM口、并行/LPT口、USB、网线接口、电话线接口;...
    秋风弄影阅读 666评论 0 2
  • 基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调...
    我是强强阅读 380评论 0 0
  • socket的基本概念 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。...
    小叶大孟阅读 620评论 0 0
  • 文章首发于个人blog欢迎指正补充,可联系lionsom_lin@qq.com原文地址:《网络是怎样连接的》阅读整...
    lionsom_lin阅读 13,993评论 6 31
  • 姓名:沈华立 公司:慈溪市创鑫车辆零部件有限公司 六项精进224期利他二组学员 【日精进打卡273天】 【知~学习...
    沈华立阅读 132评论 0 0