1、tcp套接字通信
服务器端通过socket函数创建套接字,bind绑定套接字,通过listen函数接受客户端发送过来的请求,通过三次握手建立链接后,通过accept函数接受请求并返回一个能与客户端收发数据的socket描述符,后面客户端和服务器通过recv和send函数收发数据,当调用close函数后关闭链接。
int socket(int domain, int type, int protocol);创建套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);绑定套接字
int listen(int sockfd, int backlog);监听请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);接受请求
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);发送数据
int close(int fd);关闭套接字链接
客户端通过socket函数创建套接字,bind绑定套接字,通过connect函数向服务端发送链接请求,与服务端建立链接后,后面通过recv和send函数收发数据,当调用close函数后关闭链接。
int socket(int domain, int type, int protocol);创建套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);绑定套接字
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);建立链接
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);发送数据
int close(int fd);关闭套接字链接
网络io通信函数功能:
1、connect函数:阻塞的,客户端通过connect函数发送连接请求,直到三次握手成功或超时失败才返回。服务端listen函数是被动连接的。可通过fcntl函数设置为非阻塞的。
2、listen() 函数:非阻塞的,主要作用就是将传入的套接字变成被动的连接监听(被动等待客户端的连接请求),参数 backlog是设置内核中连接队列的长度。它将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。内核为任何一个给定的监听套接口维护两个队列:1)、半连接队列,服务端接受到客户端第一次握手请求。将该请求套接字描述符放入半连接队列中,并将该连接请求置为SYN_RCVD 状态。2)、全连接队列,服务端收到客户端第三次握手请求,创建新的套接字描述符,并将半连接队列中对应的套接字描述符删除,加入全连接队列。并将该连接请求置为 ESTABLISHED 状态。
3、accept()函数:从全连接队列的头部取出一个已完成的连接,若全连接队列中没有已完成的链接,则accpet函数阻塞,直到全连接队列中有已完成连接并将其取出。若半连接队列或全连接队列满了,则connet建立连接超时,返回ETIMEDOUT。可通过fcntl函数设置为非阻塞的。
4、send函数:阻塞的,从发送缓冲区取出数据并将数据发出,若发送缓冲区没有数据,则该函数阻塞。直到发送缓冲区有数据可发。可通过fcntl函数设置为非阻塞的。
5、recv函数:阻塞的,从接收缓冲区读取数据,若接收缓冲区没有数据,则阻塞,直到接收缓冲区有数据可读。可通过fcntl函数设置为非阻塞的。
2、网络io模型
1、网络io阻塞模型:
服务器实现:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include<stdio.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { return -1; } int port = 9999; struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { return -1; } if(listen(sockfd, 10) == -1) { return -1; } struct sockaddr_in client; socklen_t clilen = sizeof(client); int connfd = accept(sockfd, (struct sockaddr*)&client, &clilen); if(connfd == -1) { printf("accept error\n"); return -1; } char rbuffer[100] = {0}; int rlen = recv(connfd, rbuffer, sizeof(rbuffer),0); if(rlen <= 0) { printf("recv error\n"); return -1; } printf("recv from client: %s\n", rbuffer); char wbuffer[100] = "data from server"; int wlen = send(connfd, wbuffer, sizeof(wbuffer), 0); if(wlen <= 0) { return -1; } printf("send to client: %s\n", wbuffer); close(sockfd); close(connfd); return 0; }
客户端实现:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> int main(int argc, char* argv[]) { if(argc != 3) { printf("usage: ip port\n"); return -1; } int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { return -1; } int servport = atoi(argv[2]); struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(argv[1]); servaddr.sin_port = htons(servport); socklen_t servlen = sizeof(servaddr); if(connect(sockfd, (struct sockaddr*)&servaddr, servlen) == -1) { return -1; } char wbuffer[100] = "data from client"; int wlen = send(sockfd, wbuffer, sizeof(wbuffer), 0); if(wlen <= 0) { return -1; } printf("send to server: %s\n", wbuffer); char rbuffer[100] = {0}; int rlen = recv(sockfd, rbuffer, sizeof(rbuffer),0); if(rlen <= 0) { return -1; } printf("recv from server: %s\n", rbuffer); close(sockfd); return 0; }
2、网络io非阻塞模型:
通过fcntl函数设置为非阻塞的。在缓冲区数据没有就绪的情况下,read函数立即返回(accpet、recv等函数原理相同)。
3、IO多路复用:
可通过内核提供的select、epoll实现,这种方式也称为事件驱动IO,IO多路复用顾名思义,就是单个 process 就可以同时处理多个网络连接的 IO,它的基本原理就是 select/epoll 这个 function 会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。
epoll是linux内核实现的一种模型,它给应用程序提供的关键接口如下:
int epoll_create(int size);创建一个epoll实例并返回操作epoll实例的文件描述符,从linux 2.6.8 size参数被忽略,但是必须比0大。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);该系统调用用于添加、修改或移除在epoll实例中被epfd文件描述符提及的fd条目,它请求的op操作为设置目标fd而被执行,event参数描述关联到fd文件描述符的对象。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);该系统调用等待文件描述符epfd涉及的事件,events用于存储准备就绪的事件,上限为maxevents,被epoll_wait返回,maxevents参数必须大于0。
epoll模型总结:
epoll模型实现并发服务器
服务端实现
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include<stdio.h> #include <sys/epoll.h> #define AVAILABLE_EVENTS 1024 int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { return -1; } int port = 9999; struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { return -1; } if(listen(sockfd, 10) == -1) { return -1; } int epofd = epoll_create(1); if(epofd == -1) { return -1; } struct epoll_event event; event.data.fd = sockfd; event.events = EPOLLIN; if(epoll_ctl(epofd, EPOLL_CTL_ADD, sockfd, &event) == -1) { return -1; } struct epoll_event evebuffer[AVAILABLE_EVENTS]; while(1) { int epowaifd = epoll_wait(epofd, evebuffer, AVAILABLE_EVENTS, 0); if(epowaifd == -1) { return -1; } for(int i=0 ; i<epowaifd ; i++) { printf("epowaifd==%d\n", epowaifd); if(sockfd == evebuffer[i].data.fd) { struct sockaddr_in client; socklen_t clilen = sizeof(client); int connfd = accept(sockfd, (struct sockaddr*)&client, &clilen); if(connfd == -1) { printf("accept error\n"); return -1; } event.data.fd = connfd; event.events = EPOLLIN; if(epoll_ctl(epofd, EPOLL_CTL_ADD, connfd, &event) == -1) { return -1; } printf("accept seccess\n"); } else if(evebuffer[i].events == EPOLLIN) { char rbuffer[100] = {0}; int rlen = recv(evebuffer[i].data.fd, rbuffer, sizeof(rbuffer),0); if(rlen <= 0) { printf("recv error\n"); return -1; } printf("recv:%s\n", rbuffer); event.data.fd = evebuffer[i].data.fd; event.events = EPOLLOUT; if(epoll_ctl(epofd, EPOLL_CTL_MOD, evebuffer[i].data.fd, &event) == -1) { return -1; } printf("recv seccess\n"); } else if(evebuffer[i].events == EPOLLOUT) { char wbuffer[100] = "server data"; int wlen = send(evebuffer[i].data.fd, wbuffer, sizeof(wbuffer), 0); if(wlen <= 0) { printf("send error\n"); return -1; } printf("send:%s\n", wbuffer); event.data.fd = evebuffer[i].data.fd; event.events = EPOLLIN; if(epoll_ctl(epofd, EPOLL_CTL_MOD, evebuffer[i].data.fd, &event) == -1) { return -1; } printf("send seccess\n"); } } } close(sockfd); return 0; }
测试结果