有个SO_REUSEADDR值得注意一下:
服务器端尽可能使用SO_REUSEADDR
在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。
使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器。
也就是如果你不这样子用的话会出现这样的问题:_
就是用上节的服务器和客户端通信的时候,如果不设置这个选项的话,当服务器端先退出的话,在启动服务器端的话会失败。可以用netstat -an | grep TIME_WAIT来查看一下。
具体设置:

看截图一目了然,具体怎么用man一下就可以,养成好习惯。
问题来了:上节的程序服务器端只可以接受一个客户端的连接。原因就在于当服务器端接收到一个客户端时它会进入到while中,那它就不能再accept了。
这时我们可以用父子进程配合的方式来实现,一个服务器端可以接受多个客户端的连接。
服务器端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror (m); \ exit (EXIT_FAILURE); \ } while (0) void do_service( int conn) { char recvbuf[1024]; while (1) { memset (recvbuf, 0, sizeof (recvbuf)); int ret = read(conn, recvbuf, sizeof (recvbuf)); if (ret == 0) //服务器端要能捕捉到客户端的退出 { printf ( "client close\n" ); break ; } else if (ret == -1) ERR_EXIT( "read" ); fputs (recvbuf, stdout); write(conn, recvbuf, ret); } } int main( void ) { int listenfd; if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) /* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/ ERR_EXIT( "socket" ); struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/ /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ //这个就是上边说的那个服务器重启的解决方法 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) ERR_EXIT( "setsockopt" ); if (bind(listenfd, ( struct sockaddr*)&servaddr, sizeof (servaddr)) < 0) ERR_EXIT( "bind" ); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT( "listen" ); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof (peeraddr); int conn; pid_t pid; while (1) { if ((conn = accept(listenfd, ( struct sockaddr*)&peeraddr, &peerlen)) < 0) ERR_EXIT( "accept" ); printf ( "ip=%s port=%d\n" , inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork(); if (pid == -1) ERR_EXIT( "fork" ); if (pid == 0) { close(listenfd); //子进程只负责接收后的工作 do_service(conn); //这里写了一个自定义的函数来解决子进程的操作 exit (EXIT_SUCCESS); } else close(conn); //父进程只负责监听接受连接,不负责具体的操作。 } return 0; } |
其中还有很多的细节需要注意,大家亲自试一下,首先自己动手去写,自己试各种情况有什么问题,然后再回过头来看程序和自己写的差别,才会对细节的处理理解更深刻。
下面实现一个点对点的聊天程序:
服务器端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror (m); \ exit (EXIT_FAILURE); \ } while (0) void handler( int sig) { printf ( "recv a sig=%d\n" , sig); exit (EXIT_SUCCESS); } int main( void ) { int listenfd; if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) /* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/ ERR_EXIT( "socket" ); struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/ /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) ERR_EXIT( "setsockopt" ); if (bind(listenfd, ( struct sockaddr*)&servaddr, sizeof (servaddr)) < 0) ERR_EXIT( "bind" ); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT( "listen" ); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof (peeraddr); int conn; if ((conn = accept(listenfd, ( struct sockaddr*)&peeraddr, &peerlen)) < 0) ERR_EXIT( "accept" ); printf ( "ip=%s port=%d\n" , inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT( "fork" ); if (pid == 0) { signal (SIGUSR1, handler); //这里用到了信号处理,在父进程退出的时候会发一个信号过来给子进程,然后子进程也跟着退出 char sendbuf[1024] = {0}; while ( fgets (sendbuf, sizeof (sendbuf), stdin) != NULL) { write(conn, sendbuf, strlen (sendbuf)); memset (sendbuf, 0, sizeof (sendbuf)); } printf ( "child close\n" ); exit (EXIT_SUCCESS); } else { char recvbuf[1024]; while (1) { memset (recvbuf, 0, sizeof (recvbuf)); int ret = read(conn, recvbuf, sizeof (recvbuf)); if (ret == -1) ERR_EXIT( "read" ); else if (ret == 0) { printf ( "peer close\n" ); break ; } fputs (recvbuf, stdout); } printf ( "parent close\n" ); kill(pid, SIGUSR1); //这是父进程退出前要向子进程发一个自定义的信号 exit (EXIT_SUCCESS); } return 0; } |
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror (m); \ exit (EXIT_FAILURE); \ } while (0) void handler( int sig) { printf ( "recv a sig=%d\n" , sig); exit (EXIT_SUCCESS); } int main( void ) { int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT( "socket" ); struct sockaddr_in servaddr; memset (&servaddr, 0, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); if (connect(sock, ( struct sockaddr*)&servaddr, sizeof (servaddr)) < 0) ERR_EXIT( "connect" ); pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT( "fork" ); if (pid == 0) { char recvbuf[1024]; while (1) { memset (recvbuf, 0, sizeof (recvbuf)); int ret = read(sock, recvbuf, sizeof (recvbuf)); if (ret == -1) ERR_EXIT( "read" ); else if (ret == 0) { printf ( "peer close\n" ); break ; } fputs (recvbuf, stdout); } close(sock); kill(getppid(), SIGUSR1); } else { signal (SIGUSR1, handler); char sendbuf[1024] = {0}; while ( fgets (sendbuf, sizeof (sendbuf), stdin) != NULL) { write(sock, sendbuf, strlen (sendbuf)); memset (sendbuf, 0, sizeof (sendbuf)); } close(sock); } return 0; } |
这一节信息量比较大,得好好消化一下。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· Windows桌面应用自动更新解决方案SharpUpdater5发布
· 我的家庭实验室服务器集群硬件清单
· C# 13 中的新增功能实操
· Supergateway:MCP服务器的远程调试与集成工具
· Vue3封装支持Base64导出的电子签名组件