Linux下socket网络编程实战思考

news/2024/11/29 13:35:54/

        socket网络编程是每个服务端开发人员必会技能,但是目前市面上各种web服务器容器,屏蔽了很多底层实现,导致很多socket通信细节被屏蔽,本文结合在linux下C语言socket通信说明一下网络通信的一些注意点。

目录

1.多进程模型tcp服务器

2.参数SO_REUSEADDR使用

3.参数SO_REUSEPORT使用

4.nginx中参数SO_REUSEADDR和SO_REUSEPORT的使用


1.多进程模型tcp服务器

        以下代码在主进程中bind,listen监听套接字,然后fork两个子进程,子进程中进行accept等待客户端连接,主进程关闭监听套接字。子进程中将接收到客户端的信息打印并返回给客户端。客户端使用网络测试工具nc。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>int main() {int port = 8090;int lfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == lfd) {perror("scoket");return -1;}int ret = 0;struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));if (-1 == ret) {perror("bind");return -1;}ret = listen(lfd, 128);char buf[8192] = {0};int len = 0;int total = 0;char tmpBuf[1024] = {0};struct sockaddr_in cliaddr;    socklen_t socketlen =sizeof(cliaddr);pid_t pro_cli;signal(SIGCHLD, SIG_IGN);std::cout << "main process: " << getpid() << std::endl;for (int i = 0; i < 2; i++) {pro_cli = fork();if (pro_cli > 0) {std::cout << "child process: " << pro_cli << std::endl;}if (0 == pro_cli) {int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;while (true) {if (-1 == cfd) {perror("accept");return -1;}memset(buf, 0x00, sizeof(buf));memset(tmpBuf, 0x00, sizeof(tmpBuf));total = 0;if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {if (total + len < sizeof(buf)) {memcpy(buf + total, tmpBuf, len);}send(cfd, buf, len, 0);}if ('\0' != buf[0]) {std::cout << buf << std::endl;}if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕//}else if (0 == len) { //客户端断开了连接std::cout << "close client" << std::endl;close(cfd);}else {//perror("recv");}}close(cfd);close(lfd);exit(0);}}close(lfd);sleep(999999999); std::cout << "main end" << std::endl;return 0;
}

运行效果如下:

 

 说明:如上显示创建三个进程,主进程17973,两个子进程17974和17975。开启两个nc客户端去连接服务器,并分别发送信息给服务器,服务器回显nc发送的消息。

关于close_wait状态:
(1)只会出现在被动关闭端
(2)状态一直停留在close_wait状态,而没有发送FIN信号并迁移到LAST_ACK状态,一般都是在收到主动关闭发起方的FIN信号后没有调用进程的close方法导致状态不迁移
(3)close_wait状态是不正常的,一般是由于bug产生,应该避免

2.参数SO_REUSEADDR使用

ctrl + C杀死服务器进程时,再重启服务端发生地址已经使用错误。如下所示

 

 服务端是主动关闭的一方,其处于FIN_WAIT2状态,再关闭nc客户端后,其状态为TIME_WAIT,所以服务端不能在此启动。

如何能让服务端进程退出后立马能够在此监听端口呢?

int opt = -1;

int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

完整代码如下所示:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>int main() {int port = 8090;int lfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == lfd) {perror("scoket");return -1;}int ret = 0;int opt = -1;ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));if (-1 == ret) {perror("bind");return -1;}ret = listen(lfd, 128);char buf[8192] = {0};int len = 0;int total = 0;char tmpBuf[1024] = {0};struct sockaddr_in cliaddr;    socklen_t socketlen =sizeof(cliaddr);pid_t pro_cli;signal(SIGCHLD, SIG_IGN);std::cout << "main process: " << getpid() << std::endl;for (int i = 0; i < 2; i++) {pro_cli = fork();if (pro_cli > 0) {std::cout << "child process: " << pro_cli << std::endl;}if (0 == pro_cli) {int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;while (true) {if (-1 == cfd) {perror("accept");return -1;}memset(buf, 0x00, sizeof(buf));memset(tmpBuf, 0x00, sizeof(tmpBuf));total = 0;if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {if (total + len < sizeof(buf)) {memcpy(buf + total, tmpBuf, len);}send(cfd, buf, len, 0);}if ('\0' != buf[0]) {std::cout << buf << std::endl;}if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕//}else if (0 == len) { //客户端断开了连接std::cout << "close client" << std::endl;close(cfd);}else {//perror("recv");}}close(cfd);close(lfd);exit(0);}}close(lfd);sleep(999999999); std::cout << "main end" << std::endl;return 0;
}

3.参数SO_REUSEPORT使用

 如上所示,端口处于LISTEN状态,再其动一个服务端程序,出现地址已经使用错误,如何在端口已经监听的情况下再次监听端口呢?

setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

完整代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>int main() {int port = 8090;int lfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == lfd) {perror("scoket");return -1;}int ret = 0;int opt = -1;ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));if (-1 == ret) {perror("setsockopt");return -1;}setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));if (-1 == ret) {perror("setsockopt");return -1;}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));if (-1 == ret) {perror("bind");return -1;}ret = listen(lfd, 128);char buf[8192] = {0};int len = 0;int total = 0;char tmpBuf[1024] = {0};struct sockaddr_in cliaddr;    socklen_t socketlen =sizeof(cliaddr);pid_t pro_cli;signal(SIGCHLD, SIG_IGN);std::cout << "main process: " << getpid() << std::endl;for (int i = 0; i < 2; i++) {pro_cli = fork();if (pro_cli > 0) {std::cout << "child process: " << pro_cli << std::endl;}if (0 == pro_cli) {int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;while (true) {if (-1 == cfd) {perror("accept");return -1;}memset(buf, 0x00, sizeof(buf));memset(tmpBuf, 0x00, sizeof(tmpBuf));total = 0;if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {if (total + len < sizeof(buf)) {memcpy(buf + total, tmpBuf, len);}send(cfd, buf, len, 0);}if ('\0' != buf[0]) {std::cout << buf << std::endl;}if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕//}else if (0 == len) { //客户端断开了连接std::cout << "close client" << std::endl;close(cfd);}else {//perror("recv");}}close(cfd);close(lfd);exit(0);}}close(lfd);sleep(999999999); std::cout << "main end" << std::endl;return 0;
}

网络状态和运行状态如下:

 

 通过运行看,不同进程可以监听相同的端口

4.nginx中参数SO_REUSEADDR和SO_REUSEPORT的使用

 

 

 


http://www.ppmy.cn/news/296263.html

相关文章

程序员节种草清单|QUANT必须拥有的10件黑科技好物

2022年的双十一 比以往时候来的更早一些 今年的双11活动于1024正式开启 全网程序员都在过节 局长收集了10件黑科技好物 机智的QUANT已经默默装进了购物车 1.机械键盘 Filco 104键忍者双模 要想代码码的快&#xff0c;机械键盘你的爱 神秘的“大F”&#xff0c;Filco&…

淘宝/天猫:畅销榜 API 返回值说明

item_search_best-天猫畅销榜 前往获取key和secret onebound.taobao.item_search_best 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_search_best 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密…

荣耀V40正式发布:三大黑科技引擎 全方位护航游戏体验

2021年荣耀新品发布会&#xff0c;荣耀V40正式发布。正如此次产品全新Slogan“前所未感”&#xff0c;荣耀V40不仅在ID设计上将理性的矩形空间和感性的色彩变幻相融合&#xff0c;采用1:3:9:27等比矩形序列重新划分平面&#xff0c;展示出一种重新定义秩序的逻辑美学&#xff0…

电商平台如何高效快速获取信息?

随着互联网的发展&#xff0c;电商跨境电商给我们的购物带来了巨大的便利和变化。 比如电商平台中&#xff0c;经常要做的就是如何获取某个平台畅销产品。这里举例&#xff0c;淘宝畅销榜的信息获取。 请求地址 公共参数 请求参数 请求参数&#xff1a;q 参数说明&#xf…

设计模式之十生成器模式

概念 生成器模式的核心是当构建生成一个对象的时候&#xff0c;这个对象一般比较复杂。需要包含多个步骤&#xff0c;虽然每个步骤具体的实现不同&#xff0c;但是都遵循一定的流程与规则 。建造模式是将复杂的内部创建封装在内部&#xff0c;对于外部调用的人来说&#xff0c…

“卖键盘的被键盘侠喷了” | 社交网络的戾气怎么这么重

六一儿童节的时候&#xff0c;cherry中国在微博发起了一个抽奖活动。 很正常的抽奖&#xff0c;对不对&#xff1f;但是因为一条评论&#xff0c;整个画风就全都乱起来了。 火野胖次 的评论略显侵略性&#xff0c;官微回复表示“我问您哪句话总结出这个意思&#xff1f;”下面就…

foobar2000隐藏桌面悬浮窗头像_如何解锁华为手机隐藏的超能力?

华为手机有哪些过人之处&#xff1f; 先别着急回答 本期小编将介绍五个很多人都不知道的 华为手机隐藏超能力 想成为玩机高手&#xff1f; 其实很简单 01 备忘录&#xff0c;还可以这么玩 备忘录是大家平时工作生活中都常会用到的应用&#xff0c;看起来很普通&#xff0c;其实…

华为手机键盘android不能长按,输入法cherry经典机械键盘怎么不能长按出来数字...

[问题反馈] 输入法cherry经典机械键盘怎么不能长按出来数字 116910 电梯直达 huafans01245393877 略有小成 发表于 2020-3-10 22:30:22 来自&#xff1a;HUAWEI Mate 30 Pro 5G 最新回复 2020-3-11 15:54:54 输入法cherry经典机械键盘怎么不能长按出来数字&#xff0c;我网上搜…