主要API函数介绍
socket
int socket(int domain, int type, int protocol);
函数描述: 创建socket
参数说明:
- domain: 协议版本
函数描述: 创建socket 参数说明: domain: 协议版本 - - AF_
- type:协议类型
- - SOCK_STREAM 流式, 默认使用的协议是TCP协议 - - SOCK_DGRAM 报式, 默认使用的是UDP协议
- protocal:
- - 一般填0, 表示使用对应类型的默认协议.
- 返回值:
- - 成功: 返回一个大于0的文件描述符 - - 失败: 返回-1, 并设置errno
当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列(监听文件描述符才有,listenFd)
bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数描述: 将socket文件描述符和IP,PORT绑定
参数说明:
- socket: 调用socket函数返回的文件描述符
- addr: 本地服务器的IP地址和PORT,
struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); //serv.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY: 表示使用本机任意有效的可用IP inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
- addrlen: addr变量的占用的内存大小
返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno
listen
int listen(int sockfd, int backlog);
函数描述: 将套接字由主动态变为被动态
参数说明:
- sockfd: 调用socket函数返回的文件描述符
- backlog: 在linux系统中,这里代表全连接队列(已连接队列)的数量。在unix系统种,这里代表全连接队列(已连接队列)+ 半连接队列(请求连接队列)的总数
返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno
accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数说明:获得一个连接, 若当前没有连接则会阻塞等待.
函数参数:
- ockfd: 调用socket函数返回的文件描述符
- addr: 传出参数, 保存客户端的地址信息
- addrlen: 传入传出参数, addr变量所占内存空间大小
返回值:
- 成功: 返回一个新的文件描述符,用于和客户端通信
- 失败: 返回-1, 并设置errno值.
accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.
从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数说明: 连接服务器
函数参数:
- sockfd: 调用socket函数返回的文件描述符
- addr: 服务端的地址信息
- addrlen: addr变量的内存大小
返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno值
读取和发送数据
接下来就可以使用write和read函数进行读写操作了。除了使用read/write函数以外, 还可以使用recv和send函数。
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags); //对应recv和send这两个函数flags直接填0就可以了
注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞。
高并发服务器模型-select
select介绍
多路IO技术: select, 同时监听多个文件描述符, 将监控的操作交给内核去处理
int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
数据类型fd_set::文件描述符集合——本质是位图
函数介绍: 委托内核监控该文件描述符对应的读,写或者错误事件的发生
参数说明:
- nfds: 最大的文件描述符+1
- readfds: 读集合, 是一个传入传出参数
传入: 指的是告诉内核哪些文件描述符需要监控 传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
- writefds: 写文件描述符集合(传入传出参数,同上)
- execptfds: 异常文件描述符集合(传入传出参数,同上)
- timeout:
NULL--表示永久阻塞, 直到有事件发生 0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生 >0 --到指定事件或者有事件发生了就返回
- 返回值: 成功返回发生变化的文件描述符的个数。失败返回-1, 并设置errno值。
select-api
将fd从set集合中清除
void FD_CLR(int fd, fd_set *set);
功能描述: 判断fd是否在集合中
返回值: 如果fd在set集合中, 返回1, 否则返回0
int FD_ISSET(int fd, fd_set *set);
将fd设置到set集合中
void FD_SET(int fd, fd_set *set);
初始化set集合
void FD_ZERO(fd_set *set);
用select函数其实就是委托内核帮我们去检测哪些文件描述符有可读数据,可写,错误发生
int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select优缺点
select优点:
- select支持跨平台
select缺点:
1.代码编写困难
2.会涉及到用户区到内核区的来回拷贝
3.当客户端多个连接, 但少数活跃的情况, select效率较低(例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下)
4.最大支持1024个客户端连接(select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的)
FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做
select代码实现
#include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <sys/poll.h> #include <sys/epoll.h> #include <pthread.h> #define MAX_LEN 4096 int main(int argc, char **argv) { int listenfd, connfd, n; struct sockaddr_in svr_addr; char buff[MAX_LEN]; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } memset(&svr_addr, 0, sizeof(svr_addr)); svr_addr.sin_family = AF_INET; svr_addr.sin_addr.s_addr = htonl(INADDR_ANY); svr_addr.sin_port = htons(8081); if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } //select fd_set rfds, rset, wfds, wset; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(listenfd, &rfds); int max_fd = listenfd; while (1) { rset = rfds; wset = wfds; int nready = select(max_fd + 1, &rset, &wset, NULL, NULL); if (FD_ISSET(listenfd, &rset)) { // struct sockaddr_in clt_addr; socklen_t len = sizeof(clt_addr); if ((connfd = accept(listenfd, (struct sockaddr *) &clt_addr, &len)) == -1) { printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } FD_SET(connfd, &rfds); if (connfd > max_fd) max_fd = connfd; if (--nready == 0) continue; } int i = 0; for (i = listenfd + 1; i <= max_fd; i++) { if (FD_ISSET(i, &rset)) { // n = recv(i, buff, MAX_LEN, 0); if (n > 0) { buff[n] = '\0'; printf("recv msg from client: %s\n", buff); FD_SET(i, &wfds); } else if (n == 0) { // FD_CLR(i, &rfds); close(i); } if (--nready == 0) break; } else if (FD_ISSET(i, &wset)) { send(i, buff, n, 0); FD_SET(i, &rfds); FD_CLR(i, &wfds); } } } close(listenfd); return 0; }