linux网络编程之socket编程

大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述linux网络编程之socket编程相关的知识,希望能为你提供帮助。
【linux网络编程之socket编程】经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传。

linux网络编程之socket编程

文章图片

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

文章图片

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

文章图片

一行一行的发送数据,每一行都有一个\\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;



运行效果如下:
linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

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

文章图片

【说明】:关于什么是僵尸进程
对于僵进程的避勉,有如下方法,之前都有介绍过:
linux网络编程之socket编程

文章图片

忽略SIGCHLD信号,所以,可以在服务端加入:
linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

编译运行,看是否解决了僵进程的问题:
linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

第二种方式,则可以捕捉SIGCHLD信号来进行忽略,具体代码如下:
linux网络编程之socket编程

文章图片

linux网络编程之socket编程

文章图片

此时再编译运行:
linux网络编程之socket编程

文章图片

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

文章图片

客户端创建五个套接字来连接服务器,一旦连接了服务器就会创建一个子进程出来为客户端进行处理,从图中可以看,服务端创建了5个子进程出来。
对于服务端程序是不需要进行修改的,只需修改客户端创建五个套接字既可,修改客户端程序如下:
linux网络编程之socket编程

文章图片

这时,编译运行:
linux网络编程之socket编程

文章图片

关于这个比较容易理解,是由于wait函数只等待一个子进程退出就返回了,所以有四个进程处于僵尸的状态。
再运行一次:
linux网络编程之socket编程

文章图片

那如果再运行一次呢?
linux网络编程之socket编程

文章图片

从以上结果来看,僵尸进程的数目不一定。
由于wait函数只能返回一个子进程,这时候我们应该用什么方法解决呢?可以用waitpid函数来解决,具体程序修改如下:
linux网络编程之socket编程

文章图片

再来运行看是否还存在僵尸进程呢?
linux网络编程之socket编程

文章图片

这种情况有一点不太好解释,但是还会遇到这种情况:
linux网络编程之socket编程

文章图片

对于这种情况,就可以用理论来解释了,原因是由于有五个子进程都要退出,这就意味着有五个信号要发送给父进程,
在关闭连接时,会向服务器父进程发送SIGCHLD信号,具体如下图:
linux网络编程之socket编程

文章图片

此时父进程处于一个handle_sigchld()的过程,而在这个处理过程中,如果其它信号到了,其它信号会丢失,之前也提到过,这些信号是不可靠信号,不可靠信号只会排队一个,如果只排队一个的话,那么最终能够处理两个子进程,所以,最后存在三个僵尸进程。
那为什么有时会有2个僵进程,有时又会有四个呢,这里来解释一下:
原因可能跟FIN(客户端在终止时,会向服务器发送FIN)到达的时机有关,服务器收到FIN的时候,返回等于0就退出了子进程,这时就会向服务器发送SIGCHLD信号,如果这些信号都是同时到达的话,那么就有可能只处理一个,这时就会出现了4个僵尸进程;如果不是同时到达,handle_sigchld()函数就会执行多次,如果被执行了两次,捕捉到了两个信号的话,那就此时就会出现3个僵进程,以此类推,但不管结果怎样,都是属于需要解决的情况。
那怎么解决此问题呢,可以用一个循环来做:
linux网络编程之socket编程

文章图片

这时再编译运行:
linux网络编程之socket编程

文章图片

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

    推荐阅读