socket基本的API使用

1.socket初探

2.socket分析

3.socket内核源码分析

//1.生成内核socket;2。与文件描述符绑定
socket(AF_UNIX, SOCK_STREAM, 0);
//建立连接,包含三次握手
connect(sockfd, (struct sockaddr *)&address, len);
//绑定一个IP地址和端口到socket套接字上
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
//半连接队列、全连接队列 
int listen(int sockfd, int backlog)
//返回一个new的socket文件描述符(不占用端口号)
accept(server_sockfd,(struct sockaddr *)&client_address, client_len);
//断开连接,包含四次挥手(TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。TIME_WAIT原因)
int close(int sockfd); 
//可选择性的断开连接

系统调用的过程:

1.int socket(int domain,int type,int protocol)

作用:根据用户定义的网络类型、协议类型、和具体的协议标号,生成一个套接字文件描述符供用户使用,实现各种初始化工作(文件系统初始化、socket初始化等)

  • //[sock_create]
  • [ ] 分配socket结构:1.在socket文件系统中创建i节点;2.创建socket专用inode;
  • [ ] 根据inode取得socket对象:
  • [ ] 使用协议族来初始化socket:1) 注册AF_INET协议域 2)套接字类型(如AF_INET域下存在流套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),在这三种类型的套接字上建立的协议分别是TCP, UDP,ICMP/IGMP);3) 使用协议域来初始化socket
  • [ ] 分配sock结构:
  • [ ] 建立socket结构与sock结构的关系:
  • [ ] 使用tcp协议初始化sock:
  • //[sock_map_fd]
  • [ ] socket与文件系统关联;

2.int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen)

  • [ ] bind()的Socket层实现
  • [ ] bind()的tcp层实现、端口的冲突处理
  • Q: 什么情况下会出现冲突呢?

同时符合以下条件才会冲突:

  1. 绑定的设备相同(不允许自动选择设备)

  2. 绑定的IP地址相同(不允许自动选择IP)

3 以下条件有一个成立:

3.1 要绑定的socket不允许重用

3.2 已绑定的socket不允许重用

3.3 已绑定的socket处于监听状态   

3.4 relax参数为false

端口区间(0--65535)

我们可以指定系统自动分配端口号时,端口的区间:

/proc/sys/net/ipv4/ip_local_port_range,默认为:32768 61000

也可以指定要保留的端口区间:

/proc/sys/net/ipv4/ip_local_reserved_ports,默认为空

系统自动选择端口时:不优先选择没被使用过的端口。只要没有冲突,直接重用端口。

  • 一个网络应用程序只能绑定一个端口( 一个套接字只能 绑定一个端口 )
  • 一般情况下服务器需要绑定端口号,而客户端可以不绑定端口号,在send的时候,系统随机分配一个端口号。
  • 端口复用技术//设置socket的SO_REUSEADDR选项,即可实现端口复用
  • SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)

1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。

2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。

3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。

4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP

  • 端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口
  • 当在一个应用或是进程中多个socket同时绑定到相同的端口时,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。
    浅析套接字中SO_REUSEPORT和SO_REUSEADDR的区别

3.int listen(int sockfd,int backlog)

backlog的定义

Now it specifies the queue length for completely established sockets waiting to be accepted,instead of the number of incomplete connection requests. The maximum length of the queuefor incomplete sockets can be set using the tcp_max_syn_backlog sysctl. When syncookiesare enabled there is no logical maximum length and this sysctl setting is ignored.If the socket is of type AF_INET, and the backlog argument is greater than the constant SOMAXCONN(128 default), it is silently truncated to SOMAXCONN.

全连接队列的最大长度:

  • backlog保存的是完成三次握手、等待accept的全连接队列
  • 负载不高时,backlog不用太大。(For complete connections)
  • 系统最大的、未处理的全连接数量为:min(backlog,somaxconn),net.core.somaxconn默认为128。这个值最终存储于sk->sk_max_ack_backlog

半连接队列的最大长度:

  • tcp_max_syn_backlog默认值为256。(For incomplete connections)
  • 当使用SYN Cookie时,这个参数变为无效。
  • 半连接队列的最大长度为backlog、somaxconn、tcp_max_syn_backlog的最小值。
  1. 检查套接口的状态、当前连接的状态是否合法,然后调用inet_csk_listen_start()启动监听。
  2. 启动监听时,做的工作主要包括:
  1. 创建半连接队列的实例,初始化全连接队列。

  2. 初始化sock的一些变量,把它的状态设为TCP_LISTEN。

  3. 检查端口是否可用,防止bind()后其它进程修改了端口信息。

  4. 把sock链接进入监听哈希表listening_hash中。

  • listen_sock结构用于保存SYN_RECV状态的连接请求块,所以也叫半连接队列
  1. 销毁连接请求块中的listen_sock实例,释放半连接队列
  2. inet_hash()用于把sock链入监听哈希表listening_hash,或者已建立连接的哈希表ehash。

4.int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)

It extracts the first connection request on the queue of pending connections (backlog), creates a newconnected socket, and returns a new file descriptor referring to that socket.If no pending connections are present on the queue, and the socket is not marked as non-blocking,accept() blocks the caller until a connection is present. If the socket is marked non-blocking and no pending connections are present on the queue, accept() fails with the error EAGAIN.

  • 在sys_socketcall()中会调用sys_accept4():
  1. 创建了一个新的socket和inode,以及它所对应的fd、file。
  2. 调用Socket层操作函数inet_accept()。
  3. 保存对端地址到指定的用户空间地址
  • SOCK_STREAM套接口的Socket层操作函数集实例为inet_stream_ops,连接接收函数为inet_accept():
  1. 调用TCP层的操作函数,获取已建立的连接sock。
  2. 把新socket和sock关联起来。
  3. 把新socket的状态设为SS_CONNECTED。
  • SOCK_STREAM套接口的TCP层操作函数集实例为tcp_prot,其中连接接收函数为inet_csk_accept().inet_csk_accept()用于从backlog队列(全连接队列)中取出一个ESTABLISHED状态的连接请求块,返回它所对应的连接sock,同时更新backlog队列的全连接数,释放取出的连接控制块.
  1. 非阻塞的,且当前没有已建立的连接,则直接退出,返回-EAGAIN。
  2. 阻塞的,且当前没有已建立的连接:
    2.1 用户没有设置超时时间,则无限期阻塞。
    2.2 用户设置了超时时间,超时后会退出。

accept()是如何避免惊群现象(当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程或线程)的:

初始化等待任务时,flags|=WQ_FLAG_EXCLUSIVE。传入的nr_exclusive为1,表示只允许唤醒一个等待任务。
所以这里只会唤醒一个等待的进程,不会导致惊群现象。

Nginx中使用mutex互斥锁解决这个问题,具体措施有使用全局互斥锁,每个子进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,并设置了一个负载均衡的算法(当某一个子进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量。使用mutex锁住多个线程是不会惊群的,在某个线程解锁后,只会有一个线程会获得锁,其它的继续等待.

参考:accept与epoll惊群

5.int connect(int sockfd,struct sockaddr *,int addrlen)

  • SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中主动建立连接的函数为inet_stream_connect()。
  1. 检查socket地址长度和使用的协议族。

  2. 检查socket的状态,必须是SS_UNCONNECTE或SS_CONNECTING。

  3. 调用tcp_v4_connect()来发送SYN包。

  4. 等待后续握手的完成:

    如果socket是非阻塞的,那么就直接返回错误码-EINPROGRESS。

    如果socket为阻塞的,就调用inet_wait_for_connect(),通过睡眠来等待。在以下三种情况下会被唤醒:

    (1) 使用SO_SNDTIMEO选项时,睡眠时间超过设定值,返回0。connect()返回错误码-EINPROGRESS。

    (2) 收到信号,返回剩余的等待时间。connect()返回错误码-ERESTARTSYS或-EINTR。

    (3) 三次握手成功,sock的状态从TCP_SYN_SENT或TCP_SYN_RECV变为TCP_ESTABLISHED,sock I/O事件的状态变化处理函数sock_def_wakeup()就会唤醒进程。connect()返回0。

  5. 进程的睡眠: connect()的超时时间为sk->sk_sndtimeo,在sock_init_data()中初始化为MAX_SCHEDULE_TIMEOUT,表示无限等待,可以通过SO_SNDTIMEO选项来修改。

  6. 进程的唤醒:三次握手中,当客户端收到SYNACK、发出ACK后,连接就成功建立了。此时连接的状态从TCP_SYN_SENT变为TCP_ESTABLISHED,sock的状态发生变化,会调用sock_def_wakeup()来处理连接状态变化事件,唤醒进程,connect()就能成功返回了。

close()与shutdown()

int close(int sockfd); //返回成功为0,出错为-1.
int shutdown(int sockfd,int howto); //返回成功为0,出错为-1.

1.SHUT_RD:值为0,关闭连接的读这一半。

2.SHUT_WR:值为1,关闭连接的写这一半。

3.SHUT_RDWR:值为2,连接的读和写都关闭。

  • close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的,特别是对于多进程并发服务器来说。在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断连过程。

  • shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。利用shutdown()可以避免用close()过程出现死锁现象

//close()

/* First  Sample client fragment, 
 * 多余的代码及变量的声明已略       */  
   s=connect(...);  
   if( fork() ){   /*      The child, it copies its stdin to the socket              */  
       while( gets(buffer) >0)  
           write(s,buf,strlen(buffer));  
           close(s);  
           exit(0);  
   }  
   else {          /* The parent, it receives answers  */  
        while( (n=read(s,buffer,sizeof(buffer)){  
            do_something(n,buffer);  
            /* Connection break from the server is assumed  */  
            /* ATTENTION: deadlock here                     */  
         wait(0); /* Wait for the child to exit          */  
         exit(0);  
    }  

//shutdown()

if( fork() ) {  /* The child                    */  
      while( gets(buffer)  
         write(s,buffer,strlen(buffer));  
      shutdown(s,1); /* Break the connection 
                        *for writing, The server will detect EOF now. Note: reading from 
                  *the socket is still allowed. The server may send some more data 
                  *after receiving EOF, why not? */  
      exit(0);  
 }  
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容