高性能网络设计

一 网络IO管理教程

网络IO,会涉及到两个系统对象,一个是用户空间调用的IO进程或线程,一个是内核空间的内核系统。他一般会经理两个阶段:
1.等待数据准备就绪。
2.将数据从内核空间拷贝到用户空间的进程或线程中。
目前市场上共有五种网络模型,下面一一介绍。

1.阻塞IO(block IO)

int listenfd, connfd, n;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);

servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDr_ANY);
servaddr.sin_port = htons(9999);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

listen(listenfd, 10);

~~~~~~

struct sockaddr_in client;
connfd = accept(listenfd, (struct sockaddr *)&client, &len);

n = recv(connfd, buff, MAXLNE, 0);

send(connfd);

close(connfd);

 以上函数的原型和数据结构的定义如下:

struct sockaddr {
	__SOCKADDR_COMMON (sa_)
    char sa_data[14];
}

struct sockaddr_in {
	unsigned short int sin_family;
	uint32_t sin_addr.s_addr;
	uint16_t sin_port;
	unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
}

int socket(int domain, int type, int protocol);
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
int listen(int s, int backlog);
int accept(int s, struct sockaddr *addr, int *addrlen);
int recv(int s, void *buf, int len, unsigned int flags);
int send(int s, const void *msg, int len, unsigned int flags);
int close(int fd);

 sockaddr是通用套接字地址,sockaddr_in是Internet环境下的套接字地址,它们两个的长度是相同的,都是16个字节。其中指向sockaddr_in的指针也可以转化成指向sockaddr的指针。

 sockaddr_in.sin_family代表的是协议族,而AF_INET代表的是IP协议族。
sockaddr_in.sin_addr.s_addr代表的是网络字符顺序的IP地址,htonl()函数就是把32位的主机字符顺序转换成网络字符顺序。宏INADDR_ANY的值是0,在这里代表的是任意主机上的IP地址。
 sockaddr_in.sin_port代表的是端口号,htons()将16位主机字符顺序转换成网络字符顺序。
 sockaddr_in结构体的剩余部分是为了和sockaaddr结构体保持相等的大小而填充的字节,没啥用。

 socket函数的第一个参数指定何种类型的地址,其中宏AF_INET代表的是IP地址;第二个参数指建立哪种连接,宏SOCK_STREAM代表的是建立TCP连接;第三个参数是指定所用的传输协议的编号,通常这个参数被赋值为0;socket函数返回套接字描述符。

 bind函数的第一个参数是已经被创建的套接字描述符,第二个参数是struct sockaddr_in结构体值,里面存储了要绑定的协议族,IP地址和端口;第三个参数是套接字的地址的长度。此函数的作用是把协议族里的一个特定地址赋给当前listenfd代表的套接字。

 listen()的第一个参数是已经被创建的套接字,第二个参数是指TCP连接已完成但服务器进程还没来得及处理或者TCP还未连接完成的套接字的最大总数。socket()函数生成套接字为主动连接套接字,此函数将它转化为被动连接套接字。

 accept()的第一个参数是被连接的套接字的描述符,第二个参数是用于返回客户地址的指针,第三个参数是这个协议的长度。如果有客户端连接服务器,此函数返回一个已连接的套接字描述符。

 recv()的第一个参数是已连接的套接字描述符(非监听),第二个参数是接受的数据所存储空间的地址,第三个参数是可接受数据的最大长度,第四个参数一般为0;

 send()的第一个参数是套接字描述符(非监听),第二个参数是要发送的数据存储空间的地址,第三个是要发送的数据的长度,第四个是参数一般设置为0;

 close()的参数是套接字描述符(非监听),此函数的用处是关闭此套接字描述符所代表的网络连接。

三次握手发生在listen和accept之间。

2.多路复用IO(IO multiplexing)

1)select组件

typedef struct {
	long __fds_bits[16];
} fd_set;

FD_SET(fd, fdsetp);
FD_CLR(fd, fdsetp);
FD_ISSET(fd, fdsetp);
FD_ZERO(fdsetp);

int select(max_fd+1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

 fd_set是位图,通常是1024位,且很难改变,1024由宏定义定义的,如果改变,可能需要重新编译内核。

2)

3) epoll组件

struct epoll_event {
	uint32_t events;
	epoll_data_t data;
}

typedef union epoll_data {
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

 epoll_create函数的参数没有意义,是为了兼容旧版本。一般把参数设置为1
 epoll_ctl函数说明:
  op:指定的操作类型;
操作类型:EPOLL_CTL_ADD: 往事件表中注册fd上的事件
     EPOLL_CTL_MOD: 修改fd上的注册事件
     EPOLL_CTL_DEL: 删除fd上的注册事件
  fd: 要操作的套接字描述符;
  event: 指定的事件;
指定的事件:EPOLLIN;触发该事件,表示对应的文件描述符上有可读数据
      EPOLLOUT;触发该事件,表示对应的文件描述符上可以写数据
      EPOLLPRI;
      EPOLLERR;
      EPOLLHUP;
      EPOLLET;

epoll_wait函数说明:
  返回:成功时返回就绪的套接字描述符的个数,失败时返回-1并设置errno
  timout:指定epoll_wait阻塞的最小事件单位,0表示能立即返回,-1表示永久阻塞

3.信号驱动IO (signal driven I/O, SIGIO)

Posix API

常用的网络API如下所示

// 创建一个socket用来通信,通信的方式取决于所选择的协议,接口返回一个文件描述符fd。
// domain参数指定通信域(本地或者网络等),type参数指定通信语义(流式或数据包文等),protocol指定了套接字使用的特定协议,在这种通讯域和通信语意都被确定的情况下,protocol一般只有一种,这时候,这个值可以是0,表示默认;如果不止一种,则需要明确指定。
//(AF_INET, SOCK_STREAM, 0)参数表明创建一个TCPsocket;(AF_INET, SOCK_DGRAM, 0)参数表明创建一个UDPsocket;
//返回一个大于0的数,表明这个数就是socket文件描述符,且函数执行成功;返回-1表明函数执行失败。
int socket(int domain, int type, int protocol);


//将通信地址和socket进行绑定。在网络通信中,则是将IP、端口和socket绑定在一起。
//返回0表示成功,-1表明失败。
//其中sockaddr_in是网络通信的地址格式,INADDR_ANY表明这个socket和主机上的所有IP绑定。
//每个不同的domain都有其对应的地址格式,但是bind的第二个参数的类型是sockaddr ,因此,在使用bind的时候,需要将网络地址的指针,强转成sockaddr。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons((uint16_t)9999);
servaddr.sin_family = AF_INET;
bind(listenfd, ((struct sockaddr *)&servaddr), sizeof(struct sockaddr));

//listen将一个socket转化为一个被动socket,使它能够被用于accpet去接收网络请求。
//第一个参数表示要转换的socket,第二个参数表示
int listen(int sockfd, int backlog);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值