TCP是一个流协议
TCP是基于字节流传输的,只维护发送出去多少,确认了多少,没有维护消息与消息之间的边界,因而导致粘包问题.
粘包的解决方法是在应用层维护消息边界.
僵尸进程与SIGCHLD信号
signal(SIGCHLD,SIG_IGN);/// 忽略SIGCHLD信号
signal(SIGCHLD,handle_sigchld);
5个客户端一起向服务器发送连接请求
这是客户端程序,服务器程序,没有发生变换
/// echolic.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.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)
nread = 0;// 继续读取数据
else
return -1;
}
else if( nread == 0) // 对方关闭或已经读到eof
break;
bufp +=nread;
nleft -= nread;
}
return count-nleft;
}
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;
else
return -1;
}
else if( nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while(1)
{
int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
if( ret == -1 && errno == EINTR)
continue;
return ret;
}
}
// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp =(char *) buf;
int nleft = maxline;
//int count=0;
while(1)
{
// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
ret = recv_peek(sockfd,bufp,nleft);
if(ret < 0)
return ret;// 表示失败
else if(ret == 0)
return ret; // 表示对方关闭连接了
nread = ret;
// 判断接收到字符是否有'\n'
int i;
for(i=0; i<nread; ++i)
{
if(bufp[i] == '\n')
{
// readn读取数据,这部分缓冲会被清空的
ret = readn(sockfd,bufp,i+1);
if(ret != (i+1))
exit(EXIT_FAILURE);
return ret; //+ count;
}
}
if( nread > nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd,bufp,nread);
if(ret != nread)
exit(EXIT_FAILURE);
bufp += nread;// 下一次指针偏移
//count += 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));// 需要的注意的的时sizeof(sendbuf)和strlen(recvbuf)不一样的,容易出现混淆
int ret = readline(sock,recvbuf,1024);///最后一个参数为缓冲区的最大值
if( ret == -1 )
ERR_EXIT("readline");
else if(ret == 0)
{
printf("client close \n");
break;
}
fputs(recvbuf,stdout);
memset(recvbuf,0,sizeof(recvbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
close(sock);
}
int main()
{
int sock[5];
int i;
for( i=0;i<5;++i)
{
if( (sock[i] = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
ERR_EXIT("sock err");
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[i],(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("connect error");
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sock[i],(struct sockaddr*)&localaddr,&addrlen) < 0)
ERR_EXIT("getsockname err");
printf("ip=%s ,port = %d \n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
}
// 为了方便,只使用一个套接字通信
echo_cli(sock[0]);
return 0;
}
没有处理子进程,结果出现了好多5个僵尸进程.
最简单的办法就是父进程直接忽略SIGCHLD信号,即:
signal(SIGCHLD,SIG_IGN); // 忽略SIGCHLD信号
忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
如果我们想要捕获SIGCHLD信号的话,在信号处理函数中不能只调用一次wait/waitpid 函数,因为客户端退出发出FIN段的时机是不一定的,如果都能按一定时间顺序发送给5个服务器子进程,即子进程发生SIGCHLD信号给父进程的时间有前后之分,那handler函数会被调用多次,则是允许的,也不会产生僵尸进程;但当多个SIGCHLD信号同时到达,因为不可靠信号不能排队导致信号只保存一个,即其余信号会丢失,则产生的僵尸进程个数是不确定的,因为按前面所说取决于5个SIGCHLD信号到达的次序。解决的办法很简单,只要在handler函数中while 循环一下就ok 了,即使5个信号同时到达,只要接收到一个SIGCHLD信号,则5个子进程都会被清理掉:
void handler(int sig)
{
/* wait(NULL); //只能等待第一个退出的子进程 */
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
signal(SIGCHLD, handler);
前几个程序,都没有注意到僵尸进程,平时的记得处理僵尸进程.