Linux网络编程笔记

概述

Linux网络编程是C/C++开发必须掌握的技能。

常用网络模型

select模型

epoll模型

epoll支持水平触发和边缘触发,理论上来说边缘触发性能更高,但是使用更加复杂,因为任何意外的丢失事件都会造成请求处理错误。Nginx就使用了epoll的边缘触发模型。

这里提一下水平触发和边缘触发就绪通知的区别,这两个词来源于计算机硬件设计。它们的区别是只要句柄满足某种状态,水平触发就会发出通知;而只有当句柄状态改变时,边缘触发才会发出通知。例如一个socket经过长时间等待后接收到一段100k的数据,两种触发方式都会向程序发出就绪通知。假设程序从这个socket中读取了50k数据,并再次调用监听函数,水平触发依然会发出就绪通知,而边缘触发会因为socket“有数据可读”这个状态没有发生变化而不发出通知且陷入长时间的等待。

因此在使用边缘触发的 api 时,要注意每次都要读到 socket返回 EWOULDBLOCK为止。 否则netstat 的recv-q会持续增加。

通常来说,et方式是比较危险的方式,如果要使用et方式,那么,应用程序应该 1、将socket设置为non-blocking方式 2、epoll_wait收到event后,read或write需要读到没有数据为止,write需要写到没有数据为止(对于non-blocking socket来说,EAGAIN通常是无数据可读,无数据可写的返回状态).

主要函数

socket(创建socket文件描述符)

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

domain:指定协议族(AF_UNIX和AF_INET等),AF_UNIX只能够用于单一的Unix系统进程间通信,也称为本地socket,性能很好。AF_INET是针对Internet的,可以进行机器之间的通信,如常用的TCP和UDP等。

type: 指定通讯协议,常用的有SOCK_STREAM,SOCK_DGRAM等。SOCK_STREAM就是TCP协议,提供按顺序的、可靠的、双向的、面向连接的比特流。SOCK_DGRAM就是UDP协议,提供定长的、不可靠的、无连接的通信。

protocol: 因为指定了type,一般设为0即可。

返回值:成功返回文件描述符。失败时返回-1,错误码见errno。

bind(绑定本地IP和端口)

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

sockfd: 是由socket函数创建的文件描述符。addrlen: 是sockaddr结构的长度。

addr: 指定需要绑定的本地IP和端口,结构定义如下。其中sin_family一般为AF_INET, sin_addr设置为INADDR_ANY或IP地址,sin_port为端口号。sin_zero[8]用0来填充,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。

返回值:成功返回0。失败时返回-1,错误码见errno。

listen(监听端口)

int listen(int sockfd,int backlog);

sockfd: 为bind后的文件描述符。

backlog: 设置请求队列的最大长度。当此队列满了之后,客户端会收到ECONNREFUSED错误。

返回值:成功返回0。失败时返回-1,错误码见errno。

accept(响应客户端的连接请求)

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

accept是阻塞的,与客户端建立连接后返回,客户端地址保存在addr里。在TCP三次握手的第三次握手时返回。

sockfd: 为listen后的文件描述符。

addr: 是建立连接的客户端地址指针,为传出参数。

返回值:成功返回文件描述符。失败时返回-1,错误码见errno。

connect(请求与服务端建立连接)

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

connect是客户端请求连接服务端的函数。调用时发起TCP三次握手的第一次握手,在TCP三次握手的第二次握手时返回。

sockfd: socket返回的文件描述符。

addr: 用于指定服务器端的连接信息,其中sin_add是服务端的地址。

addrlen: addr的长度。

返回值:成功返回0。失败时返回-1,错误码见errno。

read(读取数据)

ssize_t read(int fd, void *buf, size_t count);

任何类型的文件描述符都可能通过这个函数读取数据,当然包括socket。

fd: 文件描述符。

buf: 读取的数据。

count: 读取的数据长度。

返回值:成功返回读取的字节数,返回0表示已经读完,小于0表示有错误,错误码见errno。

write(写入数据)

ssize_t write(int fd, const void *buf, size_t count);

任何类型的文件描述符都可能通过这个函数写入数据,当然包括socket。

fd: 文件描述符。

buf: 写入的数据。

count: 写入的数据长度。

返回值:成功返回写入的字节数,小于0表示有错误,错误码见errno。

recv

send

常见错误码

  1. EINTR: 表示在写的时候出现了中断错误。
  2. EPIPE: 表示网络连接出现了问题(对方已经关闭了连接)。
  3. ECONNREST:

常用工具

netstat, ping, telnet, ss, nslookup, dig