Linux网络编程基础API--socket文件描述符API

《Linux高性能服务器编程》阅读笔记:

1. 创建socket

  Linux系统上”一切皆是文件“,socket也不例外,它是可读/可写/可控制/可关闭的文件描述符。要实现socket通信,双方都需要建立各自的socket对象

#include           /* See NOTES */
#include 
int socket(int domain, int type, int protocol);

  (1) domain参数告诉系统使用哪一个底层协议族。对TCP/IP协议族而言,使用IPv4协议设置AF_INET(或PF_INET),使用IPv6协议设置为AF_INET6(或PF_INET6),使用UNIX本地域协议族则设置为AF_UNIX(PF_UNIX)。
  (2) type参数指定服务类型。主要有SOCK_STREAM(流服务)和SOCK_UGRAM(数据报)服务。 通过TCP协议–TCP头部一文可知,若是使用传输层的TCP协议则取值SOCK_STREAM、使用传输层的UDP则取值为SOCK_DGRAM。
  另外自2.6.17版本的内核之后,type参数可以接受上述服务类型与SOCK_NONBLOCKSOCK_CLOEXEC位或操作,它们分别表示将新创建的socket设为非阻塞、用exec()函数族调启动新进程时在新进程关闭该sock文件描述符。
  (3) protocol参数是在前面两个参数组成的协议集合下再指定一个具体的协议。因前两个参数已完全决定了具体协议,所以几乎所有情况下都将该参数设置为0,表示使用默认协议。
  socket()系统调用成功返回一个socket文件描述符,失败返回-1并设置errno。

2. 绑定socket

  创建socket文件描述时,程序员只是指定了地址族(使用什么协议),但是未指定使用具体哪一个地址(IP地址和端口号)。在服务端程序中程序员通常需要为socket描述符绑定具体的地址,也叫为sockt命名,因为只有命名后客户端才能知道连接的目的地址。客户端通常不需要命名,而是采用匿名的方式,即使用操作系统自动分配的socket地址。操作系统自动分配的socket地址。当然,客户端也可以命名地址。为socket文件描述符绑定地址的函数是bind():

#include 
#include 

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

  bind()函数实现将addr所描述的地址类型的原型见 Linux网络编程基础API–socket地址API分配给socket文件描述符,addrlen参数指定socket地址的长度。函数成功返回0,失败返回-1并设置errno。其中常见的errno是EACCES和EADDRINUSE:

1) EACCES: 被绑定的地址是受保护的,仅超级用户能够访问,比如普通用户将socket绑定到知名服务端口号(0~1023)。

2) EADDRINUSE: 被绑定的地址正在使用,如socket绑定到一个处于TIME_WAIT的socket地址(进入CLOSED状态才是没被使用的地址的状态)。

3. 监听socket

  为socket描述符绑定地址后,还需要为socket创建一个监听队列,用来存放待处理的客户连接:

#include 
#include 

int listen(int sockfd, int backlog);

  (1) sockfd参数指定被监听的socket的文件描述符。
  (2) backlog参数指定内核监听队列的最大长度,典型值为5。监听队列的长度超过backlog的,服务端将不接受新的客户连接,客户端也将收到ECONNREFUSED错误信息。一般操作系统的监听队列会在backlog的基础上递增一些
  在2.2内核之前,backlog指的是是处于半连接状态(SYN_RECV)和完全连接状态(ESTABLISHED)的socket的上限。在2.2版本内核之后,处于半连接状态的socket的上限则在/proc/sys/net/ipv4/tcp_max_syn_backlog内核参数指定。

4. 等待连接

  accept()系统调用实现从listen监听队列中接受一个连接:

#include  
#include 

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

  (1) sockfd参数指执行过listen()系统调用的socket描述符。
  (2) addr参数用来获取被接受连接的远端socket地址。
  (3) addrlen指定socket地址的长度,注意这里是指针变量。
  函数成功返回一个新的连接socket描述符,服务端通过这个socket描述符来与被接受连接对应的客户端通信,失败返回-1,并设置errno。需要注意的是,accept()只是从监听队列中取出连接,而不在乎连接处于何种情况,也不关心网络状况发生的变化

5. 发起连接

  服务端通过listen()调用被动来接收连接,客户端则通过connect()系统调用主动与服务器建立连接:

#include 
#include 
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  sockfd参数是系统调用socket()创建socket描述符,addr参数是服务端监听的socket地址,addrlen参数则指定这个地址的长度。connect()成功返回0,一旦建立成功,sockfd就唯一的标识此连接,客户端通过读写该sockfd来和服务端通信。connect()失败返回-1并设置errno,常见的errno是ECONNREFUSEDETIMEDOUT:

1) ECONNREFUSED: 目标端口不存在,连接被拒绝。此时本端会收到RST数据报文
2) ETIMEDOUT: 连接超时

6. 关闭连接

  关闭连接实际上就是关闭该连接对应的socket,可以和关闭普通文件描述符的系统调用一样,使用close():

#include 
int close(int fd);

  fd为待关闭的socket描述符。close()系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用计数为0时连接才会真正被关闭
  shutdown()系统调用是在无论如何都要立即终止连接而不是将socket的引用计数减1的情况下使用的,相对于close()来说,它是专门为网络编程设计的

#include 
int shutdown(int sockfd, int how);

  (1) sockfd参数是带关闭的连接的socket描述符。
  (2) how参数决定函数的行为:

1) SHUT_RD: 关闭sockfd上读端,执行后APP将不能针对sockfd文件描述符执行读操作,且在sockfd接收缓冲区的数据被丢弃

2) SHUT_WR: 关闭sockfd的写端,执行后发送缓冲区中的数据会在真正关闭连接之前全部发出,APP不能在对该sockfd文件描述符执行写操作。(不能写只能读,这是半关闭状态)

3) SHUT_RDWR: 同时关闭sockfd的读写端

你可能感兴趣的:(Linux系统/网络编程,Linux编程)