大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述linux网络编程之socket编程相关的知识,希望能为你提供帮助。
【linux网络编程之socket编程】经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传。
文章图片
回顾一下我们之间实现在TCP回射客户/服务器程序,首先回顾一下第一个版本:
文章图片
TCP客户端从stdin获取(fgets)一行数据,然后将这行数据发送(write)到TCP服务器端,这时TCP服务器调用read方法来接收然后再将数据回射(write)回来,客户端收到(read)这一行,然后再将其输出fputs标准输出stdout,但是这个程序并没有处理粘包问题,因为TCP是流协议,消息与消息之间是没有边界的,关于粘包问题,为了解决这个问题,于是第二个改进版程序诞生了:
文章图片
一行一行的发送数据,每一行都有一个\\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);
else
close(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;
运行效果如下:
文章图片
文章图片
文章图片
文章图片
对于上面运行程序,当客户端退出之后,服务端会产生僵进程:
文章图片
【说明】:关于什么是僵尸进程
对于僵进程的避勉,有如下方法,之前都有介绍过:
文章图片
忽略SIGCHLD信号,所以,可以在服务端加入:
文章图片
文章图片
编译运行,看是否解决了僵进程的问题:
文章图片
文章图片
第二种方式,则可以捕捉SIGCHLD信号来进行忽略,具体代码如下:
文章图片
文章图片
此时再编译运行:
文章图片
但是,对于上面两种解决方式还是会存在一些问题,如果有很多个子进程同时退出,wait函数并不能等待所有子进程的退出,因为wait仅仅只等待第一个子进程退出就返回了,这时就需要用到waitpid了,在这之前,需要模拟一下由五个客户端并发连接至服务器,并同时退出的情况,用简单示例图来描述如下:
文章图片
客户端创建五个套接字来连接服务器,一旦连接了服务器就会创建一个子进程出来为客户端进行处理,从图中可以看,服务端创建了5个子进程出来。
对于服务端程序是不需要进行修改的,只需修改客户端创建五个套接字既可,修改客户端程序如下:
文章图片
这时,编译运行:
文章图片
关于这个比较容易理解,是由于wait函数只等待一个子进程退出就返回了,所以有四个进程处于僵尸的状态。
再运行一次:
文章图片
那如果再运行一次呢?
文章图片
从以上结果来看,僵尸进程的数目不一定。
由于wait函数只能返回一个子进程,这时候我们应该用什么方法解决呢?可以用waitpid函数来解决,具体程序修改如下:
文章图片
再来运行看是否还存在僵尸进程呢?
文章图片
这种情况有一点不太好解释,但是还会遇到这种情况:
文章图片
对于这种情况,就可以用理论来解释了,原因是由于有五个子进程都要退出,这就意味着有五个信号要发送给父进程,
在关闭连接时,会向服务器父进程发送SIGCHLD信号,具体如下图:
文章图片
此时父进程处于一个handle_sigchld()的过程,而在这个处理过程中,如果其它信号到了,其它信号会丢失,之前也提到过,这些信号是不可靠信号,不可靠信号只会排队一个,如果只排队一个的话,那么最终能够处理两个子进程,所以,最后存在三个僵尸进程。
那为什么有时会有2个僵进程,有时又会有四个呢,这里来解释一下:
原因可能跟FIN(客户端在终止时,会向服务器发送FIN)到达的时机有关,服务器收到FIN的时候,返回等于0就退出了子进程,这时就会向服务器发送SIGCHLD信号,如果这些信号都是同时到达的话,那么就有可能只处理一个,这时就会出现了4个僵尸进程;如果不是同时到达,handle_sigchld()函数就会执行多次,如果被执行了两次,捕捉到了两个信号的话,那就此时就会出现3个僵进程,以此类推,但不管结果怎样,都是属于需要解决的情况。
那怎么解决此问题呢,可以用一个循环来做:
文章图片
这时再编译运行:
文章图片
好了,今天就学到这,下节见~~
推荐阅读
- #yyds干货盘点# 织梦根据TAG标签来搜索展示相关文章
- #yyds干货盘点#面试篇(虚拟机栈5连问,一听心里就乐了)
- 学习Java必备的基础知识打卡12.14,要想学好必须扎实基本功(?建议收藏)#yyds干货盘点#
- Flutter 专题34 图解自定义 View 之 Canvas#yyds干货盘点#
- Docker的网络隔离和通信
- #私藏项目实操分享#SpringCloud技术专题「Gateway网关系列」微服务网关服务的Gateway组件的原理介绍分析
- 第11篇-认识Stub与StubQueue
- 老王读Spring IoC-5Spring IoC 小结——控制反转依赖注入
- Locust如何测试物联网MQTT