网络编程

网络编程中的惊群现象

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

  • 套接字是应用程序和底层网络协议之间的一个通信端口,这意味着想要读取来至网络的数据就需要读取套接字,想要写数据到网络就要写套接字,想要控制网络协议选项就要设置套接字。从程序员的角度来讲,套接字等价于网络,就像文件描述符是磁盘操作的一个端口一样。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*@param
 * doamin--指明通信域,决定通信使用的网络协议族,协议族不同,地址结构也不同
 *       -AF_UNIX:UNIX通信域,即同一台计算机内两个进程通过文件系统进行通信
 *    要求文件系统的路径名作为套接字的地址
 *       -AF_INET:网络通信,使用IPv4;要求32位IPv4地址
 *       -AF_INET6:网络通信,使用IPv6;
 *
 * type--指明套接字的类型,套接字的类型指明通信的语义。
 *     -SOCK_STREAM:字节流套接字。(TCP)
 *     -SOCK_DGRAM:数据报套接字。(UDP)
 *     -SOCK_RAW:raw套接字,主要用于越过高层协议而直接访问低层协议,使程序员
 *   可以直接使用IP协议或者是网络的物理层。
 *
 * protocol--domain参数确定套接字采用什么协议族,而此参数在给定的协议族内选
 *    选择一种具体的协议。对于给定的通信域内的每一种套接字类型,一般只存在
 *    一种协议,在这种情况之下,只要指明了通信域和通信类型,协议就是唯一的,
 *    一般都将此参数置为0,即让系统选择默认的协议。
 */

该函数在通信域domain中创建一个类型为type、使用协议protocol的套接字,并返回一个套接字描述字。

int socketpair(int domain,int type,int protocol,int filedes[2]);

socket()创建的只是一个套接字,若通信的两个进程是由fork()派生的具有共同祖先的进程,则可以使用socketpair();

#include <sys/socket.h> 
int socketpair(int domain,int type,int protocol,int filedes[2]);
/*若type指明是有链接的类型,得到的两个套接字是已连接的,若指明了一个
 *   无连接的类型,得到的两个套接字是无连接的,但是由于他们都知道
 *   对方的存在,因此可以互相发送数据包。该函数通常用于父子进程之间
 *   通信,与无名管道非常的相似。
 */

该函数创建一对未命名的套接字,返回这对套接字描述符于filedes[0]和filedes[1]当中,套接字偶对是一个全双工的通信信道,其在两端均可执行读和写操作。fork之后须在一端关闭另一端使用的描述字。

close()和shutdown()

#include <unistd.h>
int close(int socket);

#include <sys/socket.h>
int shutdown(int socket,int how);
/*
 *how--指明执行哪一种关闭动作可以为下列值:
 *    -SHUT_RD:停止从此套接字接收数据,若还有数据到达,则拒收。 
 *    -SHUT_WR:停止从此套接字发送数据,忽略任何等待发送的数据,
 *   停止查询已经发送的数据的有关消息,若已发送数据丢失则不在重新发送它。
 *    -SHUT_DOWN:停止从该套接字发送和接收数据。
*/

close()导致该套接字被销毁,当此套接字是面向连接的且还有数据等待传送时,close()会尝试完成传送。有时我们并不需要关闭这个套接字,而是断开其连接,则可以使用shutdown()来做。

套接字地址结构

在Internet通信域当中,套接字地址由主机的IP地址加上端口号组成。
程序中分别使用两种不同的数据类型来表示32位和128位的IP地址

#include <netinet/in.h>
typedef unit32_t in_addr_t;
struct in_addr {in_addr_t s_addr;};
struct in6_addr {uint8 s6_addr[16]; };

UNIX提供了如下的一组函数将点-数表示的IPv4地址或者是冒号表示的IPv6地址转换成按照网络字节序表示的二进制整数形式或者反之。
#include <apra/inet.h>
int inet_aton(const char *name,struct in_addr *addr);
/*将标准数-点表示的IPv4地址转换为二进制形式,并将结果存储在addr中*/

char *inet_ntoa(struct in_addr inaddr);
/*转换32位IPv4地址为标准的数-点表示字符串,存储该字符串在一个静态分配的缓存区当中,并返回指向该缓冲区的指针。后继调用将会覆盖同一个缓冲区,因此当需要保存其内容时应该将其复制到别处*/

int inet_pton(int family,const char *nameptr, void *addrptr);
const char *inet_ntop(int family, void *addrptr, char *nameptr, size_t len);
/*这两个函数是为了IPv6新加的即可转换v6也可转换v4,由family取值AF_INET还是AF_INET6来指定.*/

域名地址

UNIX内部使用一个主机地址数据库来记录主机名和IP地址之间的映射,这一数据库由文件/etc/hosts或者是域名服务系统提供.
gethostbyname()和gethostbyaddr()用来从该数据库中获得一台主机的完整地址信息,包括主机名和IP地址.

#include<netdb.h>
struct hostent *gethostbyname(const char *name)
/*返回主机name的地址信息,失败返回NULL,成功则保存这些信息在一个静态分配的hostent结构当中,并返回该结构的地址,若需保存,则复制出来.*/
struct hostent *gethostbyaddr(const void *addr,size_t length ,int type);

服务与端口号

Internet通信域当中的套接字地址由机器的IP地址加上端口号组成,IP地址是唯一标识网络中的一台计算机的,端口号则区别一台机器中的不同的服务进程.
在UNIX系统中存在一个专门记录标准服务的数据库,由文件/etc/services或者是域名服务器提供.定义在<netdb.h>中的servent结构当中,用于表示服务数据库的登记项信息.

struct servent{
char *s_name;
char *s_aliases;
int s_port;
char *s_proto;
}
#include <netdb.h>
struct servent *getservbyname(const char *name,const char *proto);
//返回使用协议proto且名为name的服务的有关信息.

struct servent *getserbyport(int port,const char *proto);
//返回使用协议proto且位于端口port的服务的有关信息.

套接字地址数据结构

现在我们已经知道了表示IP地址的数据结构in_addr和in6_addr,知道了如何从主机名获得字符串形式的IP地址,已经如何将字符串形式的IP地址转换为32位或者是128位的数值形式,同时也知道了如何获取端口号,由此便可以顺利的形成套接字的地址.
1.UNIX通信域的套接字地址结构

#include <sys/un.h>
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[];//给出unix文件的路径名,不能与文件系统中的其他文件同.
}

每一种通信域套接字地址结构中的第一个成员都相同,他们指出套接字的地址族。对于sockaddr_un结构类型,该成员的值只能是AF_UNIX。由sun_path命名的文件是在调用bind的时候创建的.此时内核为他在文件系统中创建一个inode.所以应该在关闭一个命令套接字之后,还应该调用unlink()或者是remove()将其从文件系统中移除.

  1. IPv4和IPv6通信域套接字地址结构
    IPv4套接字地址的类型是sockaddr_in结构,它至少由如下一些成员.表示IPv6的套接字地址的数据类型是sockaddr_in6结构,他至少有如下的成员:
#include <netinet/in.h>
struct sockaddr_in{
sa_family_t sin_family;        //套接字地址族或格式,值应该为AF_INET
in_port_t sin_port;               //16位端口号,网络字节序
struct in_addr sin_addr;      //32位IP地址,网络字节序
unsigned char sin_zero[8];  //保留未用
}

struct sockaddr_in6{
sa_family_t sin6_family;      //AF_INET6
in_port_t sin6_port;              // 16位端口
uint32_t sin6_flowinfo;         //IPv6流标签和优先级信号,网络字节序
struct in6_addr sin6_addr;   //128位IPv6,网络字节序
}
  1. 通用套接字地址结构
    许多的套接字函数都要求指明套接字的地址,为了既能够支持不同的协议族,又有统一的接口,这些函数使用指向结构类型为sockaddr的指针作为参数:
struct sockaddr{
sa_family_t sa_family;
char sa_data[];
}

例如:
struct sockaddr_un name;
name.sun_family = AF_UNIX;
strcpy(name.sun_path,filename);
bind(sockfd,(struct aockaddr *)&name, sizeof(name));

字节顺序

数据是存放在存储器当中的,存放数据的基本单元是字节通常4个字节或者是八个字节组成一个机器字.一般与计算机的寄存器的大小相同.

当将一个数存放在存储器当中时,有两种存放顺序,一种将一个数当中最有意义的字节(MSB)防在存储器编号较小的字节(称为little-endian顺序,因为数的最小意义位在后面.).另外一些计算机将最小意义字节(LSB)放在存储器编号较小的字节(称为big-endian顺序,因为数的最有意义位在后面 )
大端模式:数据的结尾部分位于地址的高位.
小端模式:数据的结尾部分位于地址的地位.

例如:

两台计算机通过套接字通信时,若各自使用不同的字节顺序将导致数据的解释出现问题,Internet协议采用统一的big-endian字节顺序,称之为网络字节序
因此,当建立一个Internet套接字连接时,必须要保证在sockaddr_in中的sin_port和sin_addr域当中给出的数据是网络字节序的。如果封装一个整形数据在一个消息当中并通过套接字发送出去,则也应该将其转换为网络字节序使用getservbyname()和gethostbyname()或者是inet_aton()获得的端口号和机器地址已经是网络字节序的,可以直接使用。否则必须使用专门的函数显式的转换。

#include <arpa/inet.h>
unit16_t htos (unit16_t hostshort)//用于转换sin_port

unit32_t htol(unit32_t hostlong) // 转换sin_addr

unit16_t ntohs (uint16_t netshort) //用于转换sin_port

unit32_t ntohl (uint32_t netlong); // 

命名套接字

用socket()创建的套接字只是系统中的一个没有名字的资源,其他的进程无法访问,也无法从他接收消息,仅仅当程序通过调用bind()给套接字指定了一个名字之后其他的进程才能找到他。只有对外提供服务的套接字才需要bind();

int bind(int socket,const struct sockaddr *address,socklet_t address_len);

套接字通信模式

支持两种通信模式:有连接的(流)和无连接的(数据报)。

  1. 有连接模式套接字通讯示意图

    当服务程序启动的时候,先调用socket()创建一个套接字,之后调用bind()给该套接字命名一个众所周知的名字,以便其他的程序能够与之进行通讯。命名之后,等待客户端的连接,在多个客户端同时连接时,调用listen()为进入的连接创建一个连接队列。通过调用accept()逐一的接收这些连接。
    服务器每一次调用accept()都会创建一个新的套接字,它不同于之前的已经命名的套接字,这个新的套接字完全只用于和特定的客户端通信

无连接模式套接字通讯示意图

和有链接的套接字通信方式不同的是,无连接的套接字通信以对等的方式交换数据。尽管通信的两个进程仍然可能分为客户和服务进程,客户和服务都必须要先创建套接字。

流套接字操作

在这种通信方式中,bind允许服务进程给予一个套接字地址,并建立连接的一方,客户进程通过调用connect(),而服务经常调用accept()来完成连接。我们称一对已经连接的套接字为对等套接字,其中请求服务的一方为主动套接字,等待连接请求的套接字为被动套接字。一个套接字在开始的时候是主动的,并且只有在调用了listen之后才会成为被动。只有主动套接字可以用于connect(),只有被动套接字可以用于accept();

  1. 请求连接
    请求连接是客户端的动作,客户端的套接字是主动套接字,客户进程通过调用connect()建立主动套接字与有名的被动套接字之间的连接。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen address_len);

connect()调用成功时,套接字socket被连接到address给出的地址,后者必须是已经存在的服务程序套接字。connect()等待直到服务程序回答了连接请求,或者是等待时间超过了某个时间限制之后才返回。若超时返回,connect()将会失败并且流产连接请求。如果connect()在阻塞期间由于收到信号而被中断,它将失败并置errno为EINTR,但是连接请求并不会流产,而是被异步的建立。可以对套接字socket设置非阻塞方式,从而使得connect不等待回答就直接返回。对套接字设置非阻塞方式的方法和文件描述字一样使用fcntl();若对套接字socket()设置了非阻塞标志O_NONBLOCK,当连接不能够被立即建立的时候,connect将失败并置errno为EINPROGRESS但是连接请求不是流产,而是被异步的建立。在连接建立之前,后继对同一个套接字调用connect()将会导致失败。
对于异步建立的连接,可以使用select()和poll()来指出套接字就绪

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

推荐阅读更多精彩内容