4.23 TCP状态转换
四次挥手:
另一种状态图:
红色实线:客户端
绿色虚线:服务端状态转变
4.24 半关闭、端口复用
半关闭
基本就是一边closed,另一边还没有closed
一边一旦closed之后就不能再向另一方传数据(ACK信号不算要传的数据)。
端口复用:
recv是socket通信特有的函数,用来接收信息。
这里作者直接运行了两个文件来观察三次握手和四次挥手(尤其是四次挥手时状态的转变)
假如服务器和客户端成功通信之后,断开了服务器sever,短时间内想再运行./server
是不可能的:
原因是:
服务端当前处于FIN_WAIT_2状态。相当于
过了这段时间后就可以了。
有时候服务器重启后,有2ml的时间服务器无法绑定,为了解决这个问题,要用到端口复用:
则服务器端代码:
#include <stdio.h> #include <ctype.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); return -1; } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(9999); //int optval = 1; //setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); //端口复用,在bind之前 int optval = 1;//1代表可以复用,2代表不可以复用 setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); // 绑定 int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); return -1; } // 监听 ret = listen(lfd, 8); if(ret == -1) { perror("listen"); return -1; } // 接收客户端连接 struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); if(cfd == -1) { perror("accpet"); return -1; } // 获取客户端信息 char cliIp[16]; inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(cliaddr.sin_port); // 输出客户端的信息 printf("client's ip is %s, and port is %d\n", cliIp, cliPort ); // 接收客户端发来的数据 char recvBuf[1024] = {0}; while(1) { int len = recv(cfd, recvBuf, sizeof(recvBuf), 0); if(len == -1) { perror("recv"); return -1; } else if(len == 0) { printf("客户端已经断开连接...\n"); break; } else if(len > 0) { printf("read buf = %s\n", recvBuf); } // 小写转大写 for(int i = 0; i < len; ++i) { recvBuf[i] = toupper(recvBuf[i]); } printf("after buf = %s\n", recvBuf); // 大写字符串发给客户端 ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0); if(ret == -1) { perror("send"); return -1; } } close(cfd); close(lfd); return 0; }
客户端:
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } while(1) { char sendBuf[1024] = {0}; fgets(sendBuf, sizeof(sendBuf), stdin); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } } close(fd); return 0; }
4.25 IO多路复用---------极其重点
这里有个链接动图:登录—专业IT笔试面试备考平台_牛客网
方便理解建议看动图。
所谓读和写,对两个socket之间进行通信时,都各自有各自的读缓冲区和写缓冲区。
把这两个方框看成server和client,每个端都有读缓冲区和写缓冲区。
之前只能遍历一个个文件描述符:
I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的系统调用主要有 select、poll 和 epoll。
几种常见的io模型
一、
在等待的过程中,cpu的使用权交给了其他人。
这两种都是:
二:
非阻塞模型,即使accept不到也不会阻塞,缺点是很可能10000个客户端其中9999个都没有用,遍历是浪费的。
底层使用二进制位,有数据就会置为1.