——by firo 2011.5.2
Cherish the memory of the Master —— W. Richard Stevens
man
Understanding The Linux Kernel 3e 【ULK 3e】
Professional Linux Kernel Architecture 【PLKA】
Computer Systems: A Programmer‘s Perspective 2e 【CS:APP 2e】
Advanced Programming in the Unix Environment Second Edition 【APUE 2e】
Unix Network Programming The Sockets Networking API VOLUME 1 Third Edition 【UNPv1 3e】
The Linux Programming inTerface A Linux and UNIX System Programming Handbook 【TLPI】
Sockets are a method of IPC that allow data to be exchanged between applications,either on the same host (computer) or on different hosts connected by a network.
——by Michael Kerrisk author of TLPI
what is Socket?
【1】Socket: An interface between an application process and transport layer.
【2】In Unix jargon, a socket is a file descriptor an integer associated with an open file.
【3】A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent.
【4】In computer networking, an Internet socket or network socket is an endpoint of a bidirectional inter-process communication flow across an Internet Protocol-based computer network, such as theInternet.
int socket(int domain, int type, int protocol);
socket()的三个参数【1】domain:地址族,用来指定Socket对应的本机地址格式和Socket使用的领域、范围比如PF_PACKET和AF_UNIX都是用于本机数据传输,而AF_INET和AF_INET6是通过Internet进行数据传输的。【2】type:Socket类型SOCK_STREAM or SOCK_DGRAM等;结合地址族用来表明这个Socke的用途。【3】protocol:数据传输遵守的协议、规则,如IPPROTO_TCP,IPPROTO_UDP等。更多描述man 2 socket。
socket()通过调用sys_socket系统调用为我们创建了socket数据结构,该结构维护着主机或进程间进行数据传递操作需要的参数;对编程重要的成员有【1】网络文件系统sockfs的i-node,由kernel创建;通过返回的file descriptor 文件描述符关联,我们可以对这个Socket进行操作:read、write。【2】本地主机的IP地址,由参数domain指定具体格式,由bind()系统调用初始化为具体的值。【3】Socket类型,由参数type初始化。【4】数据传输协议,由参数protocol指定。【5】地址族,有domain指定。
通用Socket地址结构——struct sockaddr
因为不同的domain域决定了不同Socket的地址格式,而系统调用又要为所有的应用提供一致,统一的Interface,这就产生了矛盾;所以通过使用通用地址结构消除不同的Socket地址结构对系统调用的影响,使系统调用不特定于某一个地址结构。你想用这些系统调用,好啊,先转化成我规定的通用结构struct sockaddr * 吧~~你可能会说,我通过void *指针也能实现呀何必再定义一个结构呢,但是,在1982年那会儿,ANSI C 的 void * 还没出生~~
Byte order字节序
你需要记住:Big endian大端、Network byte order网络字节序、人书写和识别多字节类型的变量,他们三个顺序是一致的:都是最高有效位的字节在最初的地方,即最低地址,自己体会吧~~剩下的就是Linux的Little endian了~POSIX规定我们的TCP或UDP分组 的协议头部中目标主机地址和本机地址,统一使用大端网络字节序的存储方式~~同样地址结构中标识进程的端口port也应转为网络字节序使得TCP协议栈正确识别。浅谈字节序(Byte Order)及其相关操作 那么,我们要通过网络在可能字节序不一致的OS系统传输二进制数据呢?有两种解决办法【1】转化为字符串文本格式【2】显示对传输数据的字节序进行说明。需要注意,因为文本中的字符是不存在这种字节序问题的:ASCII是单字节编排每个字符的,自然不会出现多个字节间的字节序问题;而像UTF-8,UTF-16这样的字符编码集中有一位用来显示指定数据使用的字节序~~~
为什么connect()失败后就不能再重连呢?这真的是个问题,有待解决。
Google的结果是connect()失败后Socket的状态是未知的,再次connect()可能产生错误结果,需close后重建Socket重连~~不成功则成仁,硬,够硬!硬不硬以后再说~~
什么是TCP端口/UDP端口?
Port端口在TCP/IP协议栈上,可以看成一个数据结构和一个缓冲区。它用来标明一种服务类型只要你的应用提供这种服务,你就可以和Kernel申请占用这个Port(调用bind()系统调用);比如大家最熟悉的服务是web网页浏览服务,对应的Port端口号是80,像Tomcat和Apache这样的Web 应用服务器配置软件都可以占用这个端口;请记住【1】一个Port端口在同一时间只能被一个应用(进程或线程)监听。在Unix(BSD)上可以设置SO_REUSEPORT这个Socket设置选项使多个Process进程或线程 ,同时监听同一个Port端口,但是这会引发混乱问题:TCP/IP协议栈不能正确的识别并传送数据给正确的进程;为了消除这个问题Linux上不支持SO_REUSEPORT这个Socket 选型,我在Ubuntu10.10上没有这个常量的定义;【2】父子进程或多个线程之间同时可以使用 同一个Port来标注不同的进程或线程,而不会出现混乱:原因是Socket通信的两个进程或线程是通过Socket地址对(唯一的!)进行标识的。对于第二点UNPv2 2.9和2.10节有精彩的论述,贴一张UNPv2的原图帮助理解,实在不能理解看那就看UNPv2吧:
讲文件系统的路径名Bind 到Unix domain Socket 作为地址时,请注意:
【1】不能将已经存在的文件路径名bind到Unix domain Socket【2】为了不打麻烦,最好bind绝对文件路径名,你们懂的~【3】bind的文件路径名和Socket是Injective function一一映射的 【4】open()不能用于Unix domain Socket【5】在操作结束后,应该显示的调用unlink() or remove()删除创建的文件。
The Linux Abstract Socket Namespace【LASN】——linux虚拟Socket命名空间
LASN提供了一种新的机制来创建UNIX Domain Socket 的地址,先来说说LASN的有点【1】不用担心创建的文件存在的问题~【2】不需要显示的删除创建的文件,当Socket关闭时这个名字自动被删除~【3】不方便读写文件系统时,也可以使用Unix域Socket~~使用起来非常简单,就是在文件的正式名字前加上一个NULL,其他的不变,这是TLPI的代码:
struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear address structure */ addr.sun_family = AF_UNIX; /* UNIX domain address */ /* addr.sun_path[0] has already been set to 0 by memset() */ strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2); /* Abstract name is "xyz" followed by null bytes */ sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) errExit("socket"); if (bind(sockfd, (struct sockaddr *) &addr,sizeof(struct sockaddr_un)) == -1) errExit("bind");