linux网络编程之socket编程(六)
经过一个国庆长假,又有一段时间没有写博文了,今天继续对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编程(六)相关推荐
- linux网络编程之Socket编程
(1)socket套接字 1)在linux环境下,socket用于表示进程间网络通信的特殊文件类型,其本质是内核借助缓冲区形成的伪文件(不占磁盘空间,除此之外还有二进制文件,管道,字符文件). 2)伪 ...
- 网络编程之 socket编程
socket编程(基于linux下的网络编程) 提起网络编程那么我们就不得不说一下socket编程了(本博客主要是围绕下面这本书展开的). 感谢bingo大佬提供的书籍 链接: https://pan ...
- 网络编程之 Socket 编程 一文看懂
但使龙城飞将在,只缘身在此山中 之前在 BIO.NIO 入门(Netty 先导) 一文中聊了 socket ,本文想把视野拉大,从计算机网络的纬度来聊聊 socket 温故而知新,聊聊网络模型 上图对 ...
- java网络编程之Socket编程
概念 网络编程分为BIO(传统IO).NIO.AIO.Socket编程属于BIO这种传统IO. InetAddress java.net.InetAddress是JAVA中管理IP地址的类,常用 pu ...
- linux多网卡网络编程,Linux网络编程之Socket初探
Socket由来 Socket 的英文原意就是"孔"或"插座",现在,作为 BSD UNIX 的进程通讯机制,取其后一种意义.一起看下网络编程里说的socket ...
- 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 ...
- Python网络编程之socket编程
什么是Socket? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面 ...
- Linux网络编程之Socket套接字
一.Socket到底是什么 socket 这个英文单词的原意是"插口""插槽", 在网络编程中,它的意思是可以通过插口接入的方式,快速完成网络连接和数据收发.你 ...
- Linux网络编程之socket文件传输示例
本文所述示例程序是基于Linux平台的socket网络编程,实现文件传输功能.该示例是基于TCP流协议实现的socket网络文件传输程序.采用C语言编写.最终能够实现传输任何格式文件的文件传输程序. ...
最新文章
- 如何创建计算机视觉场景训练数据
- fast软件_自媒体者遇见喜欢好听的视频背景音乐,用一款软件就可以把它提取...
- Windows注册表修改实例完全手册(上)
- 分析Linux磁盘管理与文件系统专题三
- 好插件·用户造【CSND超好用插件】·【机械键盘大放送】
- NoSQL, Clojure
- [Ext JS] Sencha Cmd命令参考之二
- bootloader功能介绍/时钟初始化设置/串口工作原理/内存工作原理/NandFlash工作原理...
- 摆脱剧荒!教你用 Python 一步步爬取豆瓣电影新榜单
- staruml 为类的属性指定数据类型_关于python的数据类型
- sql 2005 中分页
- Android使用keytool-importkeypair生成系统签名
- Android官方开发文档下载
- mysql数据库之mmm
- 使用 TX2 和 realsense D435i 相机运行 ORBSLAM3
- 2012百度校园招聘笔试杭州站
- SQL语句增删改查公司-员工3表典型案例
- 哈佛区块链最新研究:NFT 2.0投资指南
- anacond清华源 mac_Anaconda更换清华源、中科大源
- 来兄弟连的点滴—兄弟连IT教育
热门文章
- 生成jacoco报告_jacoco生成的覆盖率文件
- 通过nginx反向代理解决跨域
- 激活函数active function
- 两个三维向量叉积_线性代数的本质08 叉积
- 两个not exists_分享两个冷门但又超实用的 Vim 使用技巧!
- android怎么让图片显示在button上面_网上的图片不知道怎么批量下载?python教你怎么把网站上面的图片都爬下来...
- pytorch梯度下降函数_Pytorch学习笔记6:激活函数/单层感知机/梯度下降求最小值实例...
- 关于ionic打包出错:ionic Unable to start the daemon process
- SQLServer分页查询
- Java poi设置打开模式_java操作Excel的poi 格式设置