P13 select 模型上

电脑承受进程数量 1核2GB 可以大约 100 多进程或线程 普通服务器大概是 10倍性能,即 1000 多进程或线程。 IO 多路复用能力 1个进程或线程可以使用多个 TCP。 单进程 select 可以 1024 单进程 poll 可以数千 单进程 epoll 可以百万 fdset 本质上是 int[32] 组成的 bitmap, 刚好对应 1024 个文件描述符。 参考 IO 多路复用 select 模型上

2023-09-24 · 1 min · 36 words · RamLife

P6 万恶的结构体

客户端和服务端字符串转字节为啥不一样? 客户端 struct hostent* h; // 用于存放服务端IP地址(大端序)的结构体的指针。 if ( (h = gethostbyname(argv[1])) == nullptr ) // 把域名/主机名/字符串格式的IP转换成结构体。 { cout << "gethostbyname failed.\n" << endl; close(sockfd); return -1; } memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); // ③指定服务端的IP(大端序)。 服务端 servaddr.sin_addr.s_addr=inet_addr(argv[1]); // ③指定服务端的IP,只能用IP,不能用域名和主机名。 原因 inet_addr 这个只能转换 ip 地址 gethostbyname 可以支持 IP,域名等等。 对于服务端来说,给他 ip 地址就够了。但是对于客户端来说,他需要连接的更多的可能是域名,而不是 IP。 服务端为什么也要设置 IP 如果服务端是运行于多网卡的服务器上面,那么必须要明确当前这个服务端是服务于那一个网段的,通过设置 servaddr.sin_addr.s_addr 就可以确定服务的网段了。 参考 万恶的结构体

2023-09-24 · 1 min · 54 words · RamLife

P12 TCP 缓存

getsockopt 作用 可以获取 socket 缓冲区大小。 int bufsize = 0; socklen_t optlen = sizeof(bufsize); getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen); // 获取发送缓冲区的大小。 cout << "send bufsize=" << bufsize << endl; getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen); // 获取接收缓冲区的大小。 cout << "recv bufsize=" << bufsize << endl; send 会阻塞吗? 如果发送太快,接受太慢,send 也会被阻塞。 send 之后,立即关闭,能接收到吗? 可以的,因为 send 是把数据写入到内核中的发送缓冲区,就算立即关闭,也能接收到。 nagle 算法 在发送一个小于 MSS 长度的包之后,必须收到回复,才能发下一个小包。保证了网络中小包数量得到控制。一般 ACK 大概 40ms. 接收延迟:在接受到一个包之后,会等待 40ms,才会发送 ACK, 这样可以尽量和应用层的回复数据合一个包。 在联机游戏,证券交易等应用场景,时效性要求高,一般会禁用 Nagle 算法。开启 TCP_NODELAY 选项。 #include <netinet/tcp....

2023-09-23 · 1 min · 82 words · RamLife

P11 三握四挥

netstat 使用 netstat 可以用来查看 socket 状态。 安装 如果系统里面没有,那么 yum install net-tools -y 可以安装。 使用 使用 netstat -a | less 或者 netstat -a | more 来查看。 上半部分的信息是 TCP 相关,下半部分是进程间的 socket 通信。 可以观察右边的 listen, established 等状态。 客户端的 port 是客户端系统随机选择的,不要去太过于关注。 port 使用限制 普通权限只能使用 1024 以上端口 root 可以使用 1024 以下的端口。 close 状态是假想状态,不存在的。 listen 的第二个参数作用 第二个参数 + 1 是 established 队列的大小,超过队列的客户端连接状态为 SYN_RCV. 三次握手 客户端申请向服务端的通道 服务端接受了,并且向客户端申请通道 客户端接受了,并给出回应 这样经过三次握手,双方建立了双向通道 四次挥手 A 请求关闭 B 同意关闭单向通道 B 把剩余的数据全部发送。 B 向 A 发送关闭请求 A 发送回应 主动端开的 A 在最后的回应后有个 TIME_WAIT 状态,时间为 2MSL,一个 MSL 在 30秒到1分钟。 服务端主动关闭 会导致 socket 释放,并且 2MSL 后端口才能重用。否则会有 bind 错误提示,并且在 netstat 中可以看到处于 TIME_WAIT 状态。 可以使用 setsockopt 函数,在 bind 之前,可以防止 bind 错误。 客户端主动关闭 没有危害,因为客户端的端口是随机分配端口号的,并且一般用的不多,够用了。...

2023-09-22 · 1 min · 107 words · RamLife

P10 多进程服务端

signal(i, SIG_IGN) 作用 // 忽略全部的信号,不希望被打扰。顺便解决了僵尸进程的问题。 for (int ii=1;ii<=64;ii++) signal(ii,SIG_IGN); kill(0, SIGTERM) 作用 kill(0,SIGTERM); // 向全部的子进程发送15的信号,通知它们退出。 因为在子进程中 SIGTERM 是退出信号。 父进程关掉 clientsocket, 子进程关掉 listensocket int pid=fork(); if (pid==-1) { perror("fork()"); return -1; } // 系统资源不足。 if (pid> 0) { // 父进程。 tcpserver.closeclient(); // 父进程关闭客户端连接的socket。 continue; // 父进程返回到循环开始的位置,继续受理客户端的连接。 } tcpserver.closelisten(); // 子进程关闭监听的socket。 在 /proc/xxx 下面的是进程相关资源,里面的 fd 是根据当前最小可用来递增的。 如果父进程不关 clientsocket, 那么每次 accept 之后,fd 都需要增加一个,然后在后面的 fork 中,这些递增的 fd 也都会被复制到子进程中。对于子进程来说,它不需要其他子进程相关的 =clientsocket=,所以完全是浪费资源。 子进程只需要自己对应的 clientsocket, 不需要 listensocket. listensocket 是专门给父进程用来监听的。 子进程结束需要 return return 0; // 子进程一定要退出,否则又会回到accept()函数的位置。 如果子进程结束没有 return=,让进程退出,那么会在 =while 中重新回到 accept 这个地方,进行了监听,然后 fork 了。...

2023-09-21 · 1 min · 88 words · RamLife

P9 文件传输

为什么 TCP 是可靠的通信,服务端每一步都要有回复。 因为服务端可能没有 accept 或者可能忙于其他事情,bug 掉线等等。所以虽然 TCP 本身是可靠的,但是服务器可能是不可靠的。所以为了业务是正常的,就必须在服务端正常应答的情况下,才能继续。 读取文件使用 ifstream 操作 字符串拼接报错? 一般情况都是只有 char * 和 char * 拼接,才会报错。只要语句中有一个 string, 拼接就不会报错。 参考 实现文件传输功能

2023-09-20 · 1 min · 24 words · RamLife

P7 封装 socket 客户端

封装时 send 函数为啥使用 const string & buf 作为参数类型 bool send(const string &buffer) // buffer不要用const char * const string & buf 既可以接收 string 也可以接受 char *. 估计是对 char * 进行了隐式转换成了 string. send 函数的参数为啥是 buf.data() 和 buf.size(), 而不是 buf.c_str() 和 buf.length if ((::send(m_clientfd,buffer.data(),buffer.size(),0))<=0) return false; buf.data() 和 buf.c_str() 都指向的是内容的指针,但是语义不一样。 data 表示的就是原始数据,而 c_str 表示的是转化为 char *. buf.size() 的语义是原始内容的大小,而 length 表示的是字符串的长度。 所以都是因为语义不合适。虽然都能用,但是确实不合适。 recv 函数的参数为啥是 & buf[0] 而不是 buf.c_str() 或者 buf.data() int readn=::recv(m_clientfd,&buffer[0],buffer.size(),0); // 直接操作buffer的内存。 因为后面两种返回的都是带有 const, 只有 &buf[0] 才没有 const, 适合对内容进行修改。...

2023-09-19 · 1 min · 125 words · RamLife