经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传。

回顾一下我们之间实现在TCP回射客户/服务器程序,首先回顾一下第一个版本:

TCP客户端从stdin获取(fgets)一行数据,然后将这行数据发送(write)到TCP服务器端,这时TCP服务器调用read方法来接收然后再将数据回射(write)回来,客户端收到(read)这一行,然后再将其输出fputs标准输出stdout,但是这个程序并没有处理粘包问题,因为TCP是流协议,消息与消息之间是没有边界的,关于粘包问题,可以参考博文:http://www.cnblogs.com/webor2006/p/3946541.html,为了解决这个问题,于是第二个改进版程序诞生了:

一行一行的发送数据,每一行都有一个\n字符,所以我们在服务器端实现了按行读取的readline方法,另外发送也并不能保证一次调用write方法就将tcp应用层的所有缓冲区拷贝到了套接口缓冲区,所以我们封装了一个writen方法进行一个更可靠消息的发送,所以就很好的解决了粘包问题。

回顾一下程序:

echosrv.c:

#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)ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;ssize_t nread;char *bufp = (char*)buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return -1;}else if (nread == 0)return count - nleft;bufp += nread;nleft -= nread;}return count;
}ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;ssize_t nwritten;char *bufp = (char*)buf;while (nleft > 0){if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return -1;}else if (nwritten == 0)continue;bufp += nwritten;nleft -= nwritten;}return count;
}ssize_t recv_peek(int sockfd, void *buf, size_t len)
{while (1){int ret = recv(sockfd, buf, len, MSG_PEEK);if (ret == -1 && errno == EINTR)continue;return ret;}
}ssize_t readline(int sockfd, void *buf, size_t maxline)
{int ret;int nread;char *bufp = buf;int nleft = maxline;while (1){ret = recv_peek(sockfd, bufp, nleft);if (ret < 0)return ret;else if (ret == 0)return ret;nread = ret;int i;for (i=0; i<nread; i++){if (bufp[i] == '\n'){ret = readn(sockfd, bufp, i+1);if (ret != i+1)exit(EXIT_FAILURE);return ret;}}if (nread > nleft)exit(EXIT_FAILURE);nleft -= nread;ret = readn(sockfd, bufp, nread);if (ret != nread)exit(EXIT_FAILURE);bufp += nread;}return -1;
}void echo_srv(int conn)//由于这个函数的意义就是回显消息给客户端,所以改一个函数名
{char recvbuf[1024];while (1){memset(recvbuf, 0, sizeof(recvbuf));int ret = readline(conn, recvbuf, 1024);if (ret == -1)ERR_EXIT("readline");if (ret == 0){printf("client close\n");break;}fputs(recvbuf, stdout);writen(conn, recvbuf, strlen(recvbuf));}
}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);echo_srv(conn);exit(EXIT_SUCCESS);}elseclose(conn);}return 0;
}

echocli.c:

#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)ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;ssize_t nread;char *bufp = (char*)buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return -1;}else if (nread == 0)return count - nleft;bufp += nread;nleft -= nread;}return count;
}ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;ssize_t nwritten;char *bufp = (char*)buf;while (nleft > 0){if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return -1;}else if (nwritten == 0)continue;bufp += nwritten;nleft -= nwritten;}return count;
}ssize_t recv_peek(int sockfd, void *buf, size_t len)
{while (1){int ret = recv(sockfd, buf, len, MSG_PEEK);if (ret == -1 && errno == EINTR)continue;return ret;}
}ssize_t readline(int sockfd, void *buf, size_t maxline)
{int ret;int nread;char *bufp = buf;int nleft = maxline;while (1){ret = recv_peek(sockfd, bufp, nleft);if (ret < 0)return ret;else if (ret == 0)return ret;nread = ret;int i;for (i=0; i<nread; i++){if (bufp[i] == '\n'){ret = readn(sockfd, bufp, i+1);if (ret != i+1)exit(EXIT_FAILURE);return ret;}}if (nread > nleft)exit(EXIT_FAILURE);nleft -= nread;ret = readn(sockfd, bufp, nread);if (ret != nread)exit(EXIT_FAILURE);bufp += nread;}return -1;
}void echo_cli(int sock)//将之前这一段代码封装成一个函数,回显客户端,让其代码更加整洁。
{char sendbuf[1024] = {0};char recvbuf[1024] = {0};while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){writen(sock, sendbuf, strlen(sendbuf));int ret = readline(sock, recvbuf, sizeof(recvbuf));if (ret == -1)ERR_EXIT("readline");else if (ret == 0){printf("client close\n");break;}fputs(recvbuf, stdout);memset(sendbuf, 0, sizeof(sendbuf));memset(recvbuf, 0, sizeof(recvbuf));}close(sock);
}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");struct sockaddr_in localaddr;socklen_t addrlen = sizeof(localaddr);if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)ERR_EXIT("getsockname");printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));echo_cli(sock);return 0;
}

运行效果如下:

对于上面运行程序,当客户端退出之后,服务端会产生僵进程:

【说明】:关于什么是僵尸进程,可以参考博文:http://www.cnblogs.com/webor2006/p/3512781.html

对于僵进程的避勉,有如下方法,之前都有介绍过:

忽略SIGCHLD信号,所以,可以在服务端加入:

编译运行,看是否解决了僵进程的问题:

第二种方式,则可以捕捉SIGCHLD信号来进行忽略,具体代码如下:

此时再编译运行:

但是,对于上面两种解决方式还是会存在一些问题,如果有很多个子进程同时退出,wait函数并不能等待所有子进程的退出,因为wait仅仅只等待第一个子进程退出就返回了,这时就需要用到waitpid了,在这之前,需要模拟一下由五个客户端并发连接至服务器,并同时退出的情况,用简单示例图来描述如下:

客户端创建五个套接字来连接服务器,一旦连接了服务器就会创建一个子进程出来为客户端进行处理,从图中可以看,服务端创建了5个子进程出来。

对于服务端程序是不需要进行修改的,只需修改客户端创建五个套接字既可,修改客户端程序如下:

这时,编译运行:

关于这个比较容易理解,是由于wait函数只等待一个子进程退出就返回了,所以有四个进程处于僵尸的状态。

再运行一次:

那如果再运行一次呢?

从以上结果来看,僵尸进程的数目不一定。

由于wait函数只能返回一个子进程,这时候我们应该用什么方法解决呢?可以用waitpid函数来解决,具体程序修改如下:

再来运行看是否还存在僵尸进程呢?

这种情况有一点不太好解释,但是还会遇到这种情况:

对于这种情况,就可以用理论来解释了,原因是由于有五个子进程都要退出,这就意味着有五个信号要发送给父进程,

在关闭连接时,会向服务器父进程发送SIGCHLD信号,具体如下图:

此时父进程处于一个handle_sigchld()的过程,而在这个处理过程中,如果其它信号到了,其它信号会丢失,之前也提到过,这些信号是不可靠信号不可靠信号只会排队一个,如果只排队一个的话,那么最终能够处理两个子进程,所以,最后存在三个僵尸进程。

那为什么有时会有2个僵进程,有时又会有四个呢,这里来解释一下:

原因可能跟FIN(客户端在终止时,会向服务器发送FIN)到达的时机有关,服务器收到FIN的时候,返回等于0就退出了子进程,这时就会向服务器发送SIGCHLD信号,如果这些信号都是同时到达的话,那么就有可能只处理一个,这时就会出现了4个僵尸进程;如果不是同时到达,handle_sigchld()函数就会执行多次,如果被执行了两次,捕捉到了两个信号的话,那就此时就会出现3个僵进程,以此类推,但不管结果怎样,都是属于需要解决的情况。

那怎么解决此问题呢,可以用一个循环来做:

这时再编译运行:

好了,今天就学到这,下节见~~

转载于:https://www.cnblogs.com/webor2006/p/4014586.html

linux网络编程之socket编程(六)相关推荐

  1. linux网络编程之Socket编程

    (1)socket套接字 1)在linux环境下,socket用于表示进程间网络通信的特殊文件类型,其本质是内核借助缓冲区形成的伪文件(不占磁盘空间,除此之外还有二进制文件,管道,字符文件). 2)伪 ...

  2. 网络编程之 socket编程

    socket编程(基于linux下的网络编程) 提起网络编程那么我们就不得不说一下socket编程了(本博客主要是围绕下面这本书展开的). 感谢bingo大佬提供的书籍 链接: https://pan ...

  3. 网络编程之 Socket 编程 一文看懂

    但使龙城飞将在,只缘身在此山中 之前在 BIO.NIO 入门(Netty 先导) 一文中聊了 socket ,本文想把视野拉大,从计算机网络的纬度来聊聊 socket 温故而知新,聊聊网络模型 上图对 ...

  4. java网络编程之Socket编程

    概念 网络编程分为BIO(传统IO).NIO.AIO.Socket编程属于BIO这种传统IO. InetAddress java.net.InetAddress是JAVA中管理IP地址的类,常用 pu ...

  5. linux多网卡网络编程,Linux网络编程之Socket初探

    Socket由来 Socket 的英文原意就是"孔"或"插座",现在,作为 BSD UNIX 的进程通讯机制,取其后一种意义.一起看下网络编程里说的socket ...

  6. linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时

    一.使用alarm 函数设置超时 C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13   void handler( int sig) { } signal(SIGALRM ...

  7. Python网络编程之socket编程

    什么是Socket? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面 ...

  8. Linux网络编程之Socket套接字

    一.Socket到底是什么 socket 这个英文单词的原意是"插口""插槽", 在网络编程中,它的意思是可以通过插口接入的方式,快速完成网络连接和数据收发.你 ...

  9. Linux网络编程之socket文件传输示例

    本文所述示例程序是基于Linux平台的socket网络编程,实现文件传输功能.该示例是基于TCP流协议实现的socket网络文件传输程序.采用C语言编写.最终能够实现传输任何格式文件的文件传输程序. ...

最新文章

  1. 如何创建计算机视觉场景训练数据
  2. fast软件_自媒体者遇见喜欢好听的视频背景音乐,用一款软件就可以把它提取...
  3. Windows注册表修改实例完全手册(上)
  4. 分析Linux磁盘管理与文件系统专题三
  5. 好插件·用户造【CSND超好用插件】·【机械键盘大放送】
  6. NoSQL, Clojure
  7. [Ext JS] Sencha Cmd命令参考之二
  8. bootloader功能介绍/时钟初始化设置/串口工作原理/内存工作原理/NandFlash工作原理...
  9. 摆脱剧荒!教你用 Python 一步步爬取豆瓣电影新榜单
  10. staruml 为类的属性指定数据类型_关于python的数据类型
  11. sql 2005 中分页
  12. Android使用keytool-importkeypair生成系统签名
  13. Android官方开发文档下载
  14. mysql数据库之mmm
  15. 使用 TX2 和 realsense D435i 相机运行 ORBSLAM3
  16. 2012百度校园招聘笔试杭州站
  17. SQL语句增删改查公司-员工3表典型案例
  18. 哈佛区块链最新研究:NFT 2.0投资指南
  19. anacond清华源 mac_Anaconda更换清华源、中科大源
  20. 来兄弟连的点滴—兄弟连IT教育

热门文章

  1. 生成jacoco报告_jacoco生成的覆盖率文件
  2. 通过nginx反向代理解决跨域
  3. 激活函数active function
  4. 两个三维向量叉积_线性代数的本质08 叉积
  5. 两个not exists_分享两个冷门但又超实用的 Vim 使用技巧!
  6. android怎么让图片显示在button上面_网上的图片不知道怎么批量下载?python教你怎么把网站上面的图片都爬下来...
  7. pytorch梯度下降函数_Pytorch学习笔记6:激活函数/单层感知机/梯度下降求最小值实例...
  8. 关于ionic打包出错:ionic Unable to start the daemon process
  9. SQLServer分页查询
  10. Java poi设置打开模式_java操作Excel的poi 格式设置