标签:
keepalive机制的自定义 远端socket关闭造成send操作崩溃 非阻塞式的connect操作 实时检测socket上是否有错误
server.c
-
/*
-
* 监听, 收到内容变大写后回写
-
*/
-
#include <stdio.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <sys/un.h>
-
#include <errno.h>
-
#include <unistd.h>
-
#include <signal.h>
-
#include <sys/wait.h>
-
#include <netdb.h>
-
-
void sig_handler(int signo)
-
{
-
pid_t pid;
-
int stat;
-
-
pid=waitpid(-1,&stat,WNOHANG);
-
while(pid>0){
-
printf("child process terminated (PID: %ld)\n",(long)getpid());
-
pid=waitpid(-1,&stat,WNOHANG);
-
}
-
-
return;
-
}
-
-
-
int main(int argc,char *argv[])
-
{
-
socklen_t clt_addr_len;
-
int listen_fd;
-
int com_fd;
-
int ret;
-
int i;
-
static char recv_buf[1024];
-
int len;
-
int port;
-
int sleep_cnt;
-
pid_t pid;
-
-
char sys_cmd[20];
-
-
struct sockaddr_in clt_addr;
-
struct sockaddr_in srv_addr;
-
-
-
port=5000;
-
-
if(signal(SIGCHLD,sig_handler)<0){
-
perror("cannot set the signal");
-
return 1;
-
}
-
-
listen_fd=socket(PF_INET,SOCK_STREAM,0);
-
if(listen_fd<0){
-
perror("cannot create listening socket");
-
return 1;
-
}
-
-
int flag=1;
-
len=sizeof(int);
-
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &flag, len);
-
-
memset(&srv_addr,0,sizeof(srv_addr));
-
srv_addr.sin_family=AF_INET;
-
srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
-
srv_addr.sin_port=htons(port);
-
-
ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
-
if(ret==-1){
-
perror("cannot bind server socket");
-
close(listen_fd);
-
return 1;
-
}
-
-
ret=listen(listen_fd,5);
-
if(ret==-1){
-
perror("cannot listen the client connect request");
-
close(listen_fd);
-
return 1;
-
}
-
-
-
while(1)
-
{
-
-
len=sizeof(clt_addr);
-
com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
-
if(com_fd<0){
-
if(errno==EINTR){
-
continue;
-
}else{
-
perror("cannot accept client connect request");
-
close(listen_fd);
-
return 1;
-
}
-
}
-
-
pid=fork();
-
if(pid<0){
-
perror("cannot create the child process");
-
close(listen_fd);
-
return 1;
-
}else if(pid==0)
-
{
-
-
printf("the child child process is %ld \n",(long)getpid());
-
while((len=recv(com_fd,recv_buf,1024,0))>0)
-
{
-
printf("Message from client(%d): %s\n",len,recv_buf);
-
if(recv_buf[0]=='@')
-
{
-
close(com_fd);
-
return 0;
-
}
-
for(i=0;i<len;i++)
-
recv_buf[i]=toupper(recv_buf[i]);
-
write(com_fd,recv_buf,len);
-
}
-
-
close(com_fd);
-
printf("return while(1) \n");
-
return 0;
-
-
}else
-
{
-
close(com_fd);
-
printf("the child parent process is %ld \n",(long)getpid());
-
-
}
-
}
-
-
return 0;
-
}
client.c
使用非阻塞式的connect操作, 对网络异常和对端socket断开的情况有简单的处理。
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <errno.h>
-
#include <sys/types.h>
-
#include <sys/ioctl.h>
-
#include <fcntl.h>
-
#include <sys/select.h>
-
#include <arpa/inet.h>
-
#include <sys/socket.h>
-
#include <netinet/tcp.h>
-
#include <signal.h>
-
-
int sockfd = -1;
-
-
static int SockSelect(int sockfd, fd_set *readfs, fd_set *writefs, fd_set *excepfs, struct timeval *vtimeout)
-
{
-
int iret;
-
while ((iret=select(sockfd+1, readfs, writefs, excepfs, vtimeout)) < 0)
-
{
-
if (errno==EINTR) continue;
-
else break;
-
}
-
-
return iret;
-
}
-
-
int check_sock_error(int s)
-
{/*
-
fd_set exceptSet;
-
struct timeval vtimeout;
-
-
FD_ZERO(&exceptSet);
-
FD_SET(s, &exceptSet);
-
-
vtimeout.tv_sec = 0;
-
vtimeout.tv_usec = 100*1000;
-
-
int iRet =SockSelect(s , NULL, NULL, &exceptSet, &vtimeout);
-
if (iRet <= 0) return iRet;
-
-
if (FD_ISSET(s, &exceptSet)) return 1;
-
*/
-
int error;
-
int len = sizeof(int);
-
getsockopt(s, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
-
if(error != 0) {
-
fprintf(stderr, "check_sock_error = [%d] \n", error);
-
}
-
-
return error;
-
}
-
-
int check_sock_send(int s)
-
{
-
fd_set sendSet;
-
struct timeval vtimeout;
-
-
FD_ZERO(&sendSet);
-
FD_SET(s, &sendSet);
-
-
vtimeout.tv_sec = 1;
-
vtimeout.tv_usec = 0;
-
-
int iRet =SockSelect(s , NULL, &sendSet, NULL, &vtimeout);
-
if (iRet <= 0) return iRet;
-
-
if (FD_ISSET(s, &sendSet))
-
return 1;
-
}
-
-
int check_sock_recv(int s)
-
{
-
fd_set sendSet;
-
struct timeval vtimeout;
-
-
FD_ZERO(&sendSet);
-
FD_SET(s, &sendSet);
-
-
vtimeout.tv_sec = 5;
-
vtimeout.tv_usec = 0;
-
-
int iRet =SockSelect(s , &sendSet, NULL, NULL, &vtimeout);
-
if (iRet <= 0) return iRet;
-
-
if (FD_ISSET(s, &sendSet))
-
return 1;
-
}
-
-
int set_keepalive(int s)
-
{
-
int keepAlive = 1;//设定KeepAlive
-
int keepIdle = 1;//开始首次KeepAlive探测前的TCP空闭时间
-
int keepInterval = 1;//两次KeepAlive探测间的时间间隔
-
int keepCount = 1;//判定断开前的KeepAlive探测次数
-
int flag=1;
-
int len=sizeof(int);
-
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &flag, len);
-
struct timeval timeout={3,0};//3s
-
setsockopt(s,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
-
setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
-
-
if(setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1)
-
{
-
fprintf(stderr, " SO_KEEPALIVE failed .\n");
-
}
-
-
if(setsockopt(s,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1)
-
{
-
fprintf(stderr, " TCP_KEEPIDLE failed .\n");
-
}
-
-
if(setsockopt(s,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1)
-
{
-
fprintf(stderr, " TCP_KEEPINTVL failed .\n");
-
}
-
-
if(setsockopt(s,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1)
-
{
-
fprintf(stderr, " TCP_KEEPCNT failed .\n");
-
}
-
}
-
-
void closesocket(int s)
-
{
-
if (s != -1) {
-
shutdown(s, SHUT_RDWR);
-
close(s);
-
}
-
}
-
static void proc_sig_pipe(int signo)
-
{
-
fprintf(stderr, "*** maybe Opposite Side socket closed. ***\n");
-
//closesocket(sockfd);
-
//sockfd = -1;
-
}
-
-
/*初始化信号句柄*/
-
void regSigProcHandle(void)
-
{
-
struct sigaction sigact;
-
sigset_t block_mask;
-
-
//屏蔽本App之外所有的信号
-
sigfillset(&block_mask);
-
sigdelset(&block_mask, SIGPIPE);
-
sigprocmask(SIG_BLOCK, &block_mask, NULL);
-
-
//信号处理
-
sigfillset(&sigact.sa_mask);
-
sigact.sa_handler=proc_sig_pipe;
-
sigaction(SIGPIPE, &sigact, NULL);
-
}
-
-
int main()
-
{
-
struct sockaddr_in servaddr;
-
int ret;
-
-
/*register some signals */
-
regSigProcHandle();
-
-
while (1) {
-
if (sockfd == -1) {
-
//1. 连接服务器
-
//此处不应当使用阻塞式调用,因为当网络持续不通时,此处可能会变得无限长时间( <= 2 * MSL = (1-4分钟))的阻塞。
-
//建议使用 sockfd 设置成非阻塞状态。
-
//然后connect 调用完毕后,使用select检测sockfd的可发送状态,
-
//若如select超时,则说明connect超时,
-
//若成功,则把sockfd从新设置成阻塞状态,进行下一步操作。
-
-
fprintf(stderr, "------- reconnected.-------------- \n");
-
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
-
{
-
fprintf(stderr, "socket create error. \n");
-
sleep(1);
-
continue;
-
}
-
-
//set non-blocking
-
int flags;
-
flags = fcntl(sockfd, F_GETFL); //if (flags<0) { error();}
-
flags |= O_NONBLOCK;
-
fcntl(sockfd, F_SETFL, flags);
-
/*
-
int nonblock = 1;
-
if (ioctl(sockfd, FIONBIO, &nonblock) < 0) {
-
close(sockfd);
-
perror("ioctl FIONBIO");
-
}
-
*/
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_addr.s_addr = inet_addr("192.168.10.147");
-
servaddr.sin_port = htons(5000);
-
-
if ((ret = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) == -1) {
-
if (errno == EINPROGRESS) {
-
int error;
-
int len = sizeof(int);
-
fd_set set;
-
struct timeval tv_timeout;
-
tv_timeout.tv_sec = 10;
-
tv_timeout.tv_usec = 0;
-
FD_ZERO(&set);
-
FD_SET(sockfd, &set);
-
if(select(sockfd + 1, NULL, &set, NULL, &tv_timeout) > 0) {
-
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
-
if(error != 0) {
-
fprintf(stderr, "socket error. [%d] \n", error);
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
} else { //timeout or select error
-
fprintf(stderr, "connect timeout. \n");
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
}
-
else { //exception
-
fprintf(stderr, "connect exception. \n");
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
}
-
else if (ret == 0) { // connect success.
-
fprintf(stderr, "connect success. \n");
-
}
-
-
// reconfig to block mode
-
int nonblock = 0;
-
if (ioctl(sockfd, FIONBIO, &nonblock) < 0) {
-
closesocket(sockfd);
-
perror("ioctl FIONBIO");
-
}
-
-
set_keepalive(sockfd);
-
}
-
-
//2. 发送数据
-
//此处应当先判断当前连接是否有异常。
-
//1. 异常判断 getsockopt(s, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
-
//2. 规避远程对端的sock关闭,此处会跑出 SIGPIPE异常造成崩溃。
-
//3. 尽量改用select方式先进行超时处理。
-
/*
-
//error
-
//fprintf(stderr, "socket check error. \n");
-
//getc(stdin);
-
ret = check_sock_error(sockfd);
-
if (ret > 0) {
-
fprintf(stderr, "socket has error. \n");
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "socket is OK.\n");
-
}
-
//*/
-
//send
-
//fprintf(stderr, "socket send check. \n");
-
//getc(stdin);
-
ret = check_sock_send(sockfd);
-
if (ret <= 0) {
-
fprintf(stderr, "socket has error for send. \n");
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "socket no error for send. \n");
-
}
-
-
const char pSend[]="iibull";
-
char pRecv[256];
-
-
ret = send(sockfd, pSend, strlen(pSend), 0);
-
if (ret <= 0) {
-
fprintf(stderr, "send error. [%d]\n", ret);
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "send success. [%d]\n", ret);
-
}
-
-
//3. 收取数据
-
//1. 首先当检测socket上是否有错误。
-
//2. 尽量改用select方式先进行分段超时处理,以节省系统资源。
-
/*
-
//error
-
fprintf(stderr, "socket error check. \n");
-
getc(stdin);
-
ret = check_sock_error(sockfd);
-
if (ret > 0) {
-
fprintf(stderr, "socket has error. \n");
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "socket no error for recv. \n");
-
}
-
//*/
-
//recv
-
//fprintf(stderr, "socket recv check. \n");
-
//getc(stdin);
-
ret = check_sock_recv(sockfd);
-
if (ret <= 0) {
-
fprintf(stderr, "socket has error for recv. \n");
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "socket is OK for recv. \n");
-
}
-
-
memset(pRecv, sizeof(pRecv), 0);
-
ret = recv(sockfd, pRecv, sizeof(pRecv)-1, 0);
-
if (ret <= 0) {
-
fprintf(stderr, "recv error. [%d]\n", ret);
-
closesocket(sockfd);
-
sockfd = -1;
-
sleep(1);
-
continue;
-
}
-
else {
-
fprintf(stderr, "### recv OK [%s]###\n", pRecv);
-
}
-
-
sleep(1);
-
}
-
}
参考:
http://blog.patpig.com/2012/06/c-socket-%E5%85%B3%E4%BA%8Econnect%E8%B6%85%E6%97%B6%E8%AE%BE%E7%BD%AE/
http://www.cnblogs.com/jason-jiang/archive/2006/11/03/549337.html
linux和windows下用setsockopt设置SO_SNDTIMEO,SO_RCVTIMEO的参数的一点区别
linux和windows下用setsockopt设置SO_SNDTIMEO,SO_RCVTIMEO的参数的一点区别
UDP的socket在某些情况:如对方关闭时,本地可能sendto不出去数据,然后recvfrom就会被阻塞,这时就需要设置 这两个参数的值提高程序质量。
linux:
struct timeval timeout={3,0};//3s
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
如果ret==0 则为成功,-1为失败,这时可以查看errno来判断失败原因
int recvd=recv(sock_fd,buf,1024,0);
if(recvd==-1&&errno==EAGAIN)
{
printf("timeout\n");
}
windows:
int timeout = 3000; //3s
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
而solaris,则不支持。
阅读(1877) | 评论(0) | 转发(0) |