server端网络连接建立的流程

一、创建socket

1、int socket(int __domain, int __type, int __protocol)
作用:创建一个socket,

  • __domain的value值,常用的几个值:(用于设置网络通信的域,根据这个参数选择通信协议族)
PF_UNIX,PF_LOCAL:本地通信
AF_INET,PF_INET:IPv4 internet协议
PF_INET6:IPv6 internet协议
  • __type的value值,常用:(用于设置套接字通信的类型)
SOCK_STREAM:TCP连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
SOCK_DGRAM:支持UDP连接(无连接状态的消息)
SOCK_SEQPACKET:序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
__protocol:用于指定某个协议的特定类型,通常某个协议中只有一种特定类型,这样protocol参数仅能设置为0。

2、int setsockopt (int __fd, int __level, int __optname, const void *__optval, socklen_t __optlen)
一般在bind()之前使用
作用:用于给任意类型、任意状态socket接口设置选项值,选项由__optname指定。当level值为SOL_SOCKET时,代表在“套接字”层次上设置选项。选项影响socket接口的操作。

  • __fd:获取选项的套接字
  • __level:选项所在的协议层,目前来说只有两个层次SOL_SOCKET和IPPROTO_TCP
  • __optname:需要访问的选项名
    在socket层的一些选项名:
SO_DEBUG:打开调试信息模块
SO_BROADCAST:允许发送广播消息
SO_REUSEADDR:允许地址复用(有四个作用)
SO_KEEPALIVE:保持连接活跃。
  • __optval:给optname指定的选项置初值,一般是bool或者int类型。
  • __optlen:optval类型的长度

二、地址转换

int inet_pton(int af, const char *src, void *dst);
功能:将文本类型的IPv4和IPv6的地址转换成二进制类型
头文件:<arpa/inet.h>
第一个参数af只能是AF_INET或者是AF_INET6.
当af为AF_INET时,src指向的字符串的格式一般为"ddd.ddd.ddd.ddd",ddd可以取0到255之间的值。src指向的地址将会被转换为struct in_addr类型,然后复制到dst指向的地方;
当af为AF_INET6时,src指向的字符串的格式最好为x:x:x:x:x:x:x:x(8个十六进制的数,每个数都表示一个16bit的值),除此之外还能将IPv4的地址转换为IPv6的地址表示,格式为x:x:x:x:x:x:d.d.d.d(最后两个用IPv4的地址值代替)

这里官方有一个例子

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){
    unsigned char buf[sizeof(struct in6_addr)];
    int domain, s;
    char str[INET6_ADDRSTRLEN];

    if(argc != 3){
        fprintf(stderr, "Usage: %s {i4|i6<num>} string\n",argv[0]);
        exit(EXIT_FAILURE);
    }

        domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
             (strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);


     s = inet_pton(domain, argv[2], buf);
     if (s <= 0) {
           if (s == 0)
              fprintf(stderr, "Not in presentation format");
           else
              perror("inet_pton");
           exit(EXIT_FAILURE);
     }
     if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
           perror("inet_ntop");
           exit(EXIT_FAILURE);
     }
     printf("%s\n", str);
     exit(EXIT_SUCCESS);
}

2、uint32_t htonl(uint32_t hostlong);
功能:将host地址转换成网络字节序;
头文件:<arpa/inet.h> 在一些其他系统中可能是<netinet/in.h>
要注意的是传入参数是无符号整形,即IP为255.255.255.255,要传入的就是0xffffffff。

3、socket通信中使用的地址的结构(sockaddr和sockaddr_in)
在sys/socket.h中

struct sockaddr {
        sa_family_t sin_family;//地址族    
        char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息  
};

在netinet/in.h中

struct sockaddr_in{
    sa_family_t sin_family; //地址族
    uint16_t sin_port; //16位TCP/UDP端口号
    struct in_addr sin_addr; //32位IPv4地址
    char sin_zero[8];
}

typedef uint32_t in_addr_t;

struct in_addr{
    in_addr_t s_addr //32位IPv4地址
}

这个两个结构体占用的内存是一致的,因此可以相互转化。
sockaddr多用于bind、connect、recvfrom、sendto等函数的参数
而sockaddr_in一般是存储地址和端口的,用与信息的显示即存储使用。

三、bind

在绑定地址的时候,有时候将sockaddr_in类型的变量中sin_addr(in_addr类型)设置为INADDR_ANY,然后再绑定到socket上,在多网卡的情况下,绑定INADDR_ANY的话,就是可以用一个socket去监听所有网卡中指定的端口。

函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t);
所需头文件:
<sys/types.h> //支持socklen_t类型
<sys/socket.h>

当一个socket被创建是,这个socket是在一个命名空间中的(类似于IPv4或者IPv6),但是没有指定一个地址给这个socket。
bind函数的作用就是将addr参数指向的地址分配给sockfd指向的socket

这里又有一个例子:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MY_SOCK_PATH "/somepath"
#define LISTEN_BACKLOG 50

#define handle_error(msg)  \
    do {perror(msg);exit(EXIT_FAILURE);} while(0)

int main(int argc char *argv[]){
    int sfd, cfd;
    struct sockaddr_un myaddr, peer_addr;
    socklen_t peer_addr_size;
    
    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(sfd == -1)
        handle_error("socket");
    
    memset(&my_addr,0,sizeof(struct sockaddr_un));
    my_addr.sun_family = AF_UNIX;
    strncpy(my_addr.sun_path, MY_SOCK_PATH,sizeof(struct sockaddr_un) - 1);

    if(bind(sfd,(struct sokaddr *)&my_addr,sizeof(struct sockaddr_un))==-1){
        handle_error("bind");
    }

    if(listen(sfd,LISTEN_BACKLOG) == -1)
        handle_error("listen");

    peer_addr_size = sizeof(struct sockaddr_un);
    cfd = accept(sfd, (struct sockaddr *)&peer_addr, &peer_addr_size);
    if(cfd == -1)
        handle_error("accept");

    /*  Code to deal with incoming connection */
}

四、listen

函数原型:
int listen(int sockfd, int backlog);
头文件:<sys/types.h> <sys/socket.h>
功能:主要是的作用就是将第一个参数的socket变为被动连接的套接字,从而成为一个服务器端的套接字。
backlog参数定义挂起连接队列的最大长度。当队列是满的时,对于发来的连接请求,客户端会受到一个error

在服务器端,tcp连接达到ESTABLISHED状态之前会经历中间状态SYN_RECEIVED,这意味着在调用listen之后,会存在两种状态的连接一种是SYN_RECEIVED,另外一种就是ESTABLISHED;这两种套接字可能再一个队列中(两种状态混在一起),也可能在两个队列中(两种状态分开保存)
1、使用单个队列实现:队列大小就backlog参数指定。当收到syn数据报之后就把socket加入队列,然后当收到相应的ack之后,将对应的连接状态变为established。处于后一状态的连接可以通过accept调用返回给客户端。

2、使用两个队列实现,一个syn队列(半连接队列)和一个accept队列(完整的连接队列)。处于SYN_RECEIVED状态的连接被添加到SYN队列中,并且当它们收到ack,变为established状态之后,将它们移动到accept队列中。accept调用就是从这个全连接队列中取出连接。在这种情况下,listen的backlog参数表示全连接队列的大小。

补:
在第一种实现方式中(一般是BSD),当队列满了之后,系统就不会再响应syn分组(不会再回复syn/ack),通常就是简单的丢弃syn分组,使客户端重试。
在第二种实现方式中(一般是Linux),半连接队列的大小是有内核设置的用 /proc/sys/net/ipv4/tcp_max_syn_backlog设置,默认值的256

五、accept

函数原型:
int accept(int sockfd, struct sockaddr *addr, sockl;en_t *addrlen);
头文件:<sys/types.h> <sys/socket.h>
功能:accept()系统调用是跟基于连接的SOCK_STREAM或者SOCK_SEQACKET类型的套接字一起使用的。
它的作用是在监听套接字的挂起连接队列中提取第一个链接请求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值