学习muduo库(14)之网络编程相关的系统函数SocketsOps.h

学习muduo库(14)之网络编程相关的系统函数SocketsOps.h_第1张图片

SocketsOps.h

在这个头文件中将网络相关的系统函数进行了进一步的封装,这里的函数也都是全局函数。

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

domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。 
type:指定socket类型。常用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等 
protocol:就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND
  int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
  if (sockfd < 0)
  {
    LOG_SYSFATAL << "sockets::createNonblockingOrDie";
  }

  setNonBlockAndCloseOnExec(sockfd);
#else
  int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
  if (sockfd < 0)
  {
    LOG_SYSFATAL << "sockets::createNonblockingOrDie";
  }
#endif
  return sockfd;
}

可见muduo库中的socket,全部设置为,无阻塞、close-on-exec、TCP连接。

int fcntl(int fd, int cmd, long arg);

muduo库中用次函数来设置了,socket的属性。

fcntl函数有5种功能: 
1. 复制一个现有的描述符(cmd=F_DUPFD). 
2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 
3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 
4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 
5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).

muduo库中是这样使用的;

void setNonBlockAndCloseOnExec(int sockfd)
{
  // non-block
  int flags = ::fcntl(sockfd, F_GETFL, 0);//得到文件状态标记
  flags |= O_NONBLOCK;
  int ret = ::fcntl(sockfd, F_SETFL, flags);//设置文件状态标记
  // FIXME check

  // close-on-exec
  flags = ::fcntl(sockfd, F_GETFD, 0);//得到文件描述符标记
  flags |= FD_CLOEXEC;
  ret = ::fcntl(sockfd, F_SETFD, flags);//设置文件描述符标记
  // FIXME check

  (void)ret;
}

sockaddr and sockaddr_in

struct sockaddr {
    unsigned short    sa_family;    // 2 bytes address family, AF_xxx
    char              sa_data[14];     // 14 bytes of protocol address
};
 
// IPv4 AF_INET sockets:
 
struct sockaddr_in {
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
    char             sin_zero[8];     // 8 bytes zero this if you want to
};

这两个地址结构体的内容是完全相同的,sin_family 和sa_family都是用两个字节来表示IP地址是IPV4还是IPV6,sin_port用两个字节表示端口号,sin_addr用4个字节来表示IP地址,sin_zero用8的字节是为了格式上的对齐,sa_data的14个字节表示的就是sin_port、sin_addr、

sin_zero的内容。所以这两个数据结构之间是可以进行强制转换的。他们在使用上的区别就是,sockeaddr_in是让程序员使用的,sockeaddr是提供给系统函数使用的。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。 
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。

addrlen:对应的是地址的长度。 

通常情况下服务端会在listen()之前,绑定一个特定的IP地址和端口,而客户端会在connect()时随机分配一个端口和自身的IP地址。因为对于服务器来说它提供的服务的IP地址和端口号是不会改变的(要不然客户端怎么连接上呢),而对于客户端就无所谓了,反正是客户端是主动连接服务端的。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

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

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

int close(int sockfd); 

close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数,然而TCP将尝试发送已排队等待发送到对端,发送完毕后发生的是正常的TCP连接终止序列。 
在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断连过程。

int shutdown(int sockfd,int howto);  

该函数的行为依赖于howto的值 
SHUT_RD:值为0,关闭连接的读这一半。 
SHUT_WR:值为1,关闭连接的写这一半。 
SHUT_RDWR:值为2,连接的读和写都关闭。 
终止网络连接的通用方法是调用close函数。但使用shutdown能更好的控制断连过程(使用第二个参数)。 
close与shutdown的区别主要表现在: 
close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说。 
而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。 

 

 

 

 

你可能感兴趣的:(学习muduo库)