socket网络编程快速上手(二)——细节问题(5)(完结篇)

2023-05-30,,

6.Connect的使用方式

前面提到,connect发生EINTR错误时,是不能重新启动的。那怎么办呢,是关闭套接字还是直接退出进程呢?如果EINTR前,三次握手已经发起,我们当然希望链路就此已经建立完成,不要再重新走流程了。这个时候我们就需要为connect量身定做一个使用方案。代码如下:

 STATUS connectWithTimeout(int sock, struct sockaddr*addrs, int adrsLen,struct timeval* tm)
{
int err = ;
int len = sizeof(int);
int flag;
int ret;
fd_set set;
struct timeval mytm; if(tm!=NULL){
memcpy(&mytm, tm, sizeof(struct timeval));
} flag = ;
ioctl(sock,FIONBIO,&flag); ret = connect(sock, addrs, adrsLen);
if ( ret == - )
{
if ( EINPROGRESS == errno )
{
FD_ZERO(&set);
FD_SET(sock,&set);
if ( select(sock+,NULL,&set,NULL,tm) > )
{
getsockopt(sock,SOL_SOCKET,SO_ERROR,&err,(socklen_t*)&len);
if ( == err )
ret = OK;
else
ret = ERROR;
}
else
{
ret = ERROR;
}
}
} flag = ;
ioctl(sock,FIONBIO,&flag); if(tm!=NULL){
memcpy(tm, &mytm, sizeof(struct timeval));
} return ret;
}

connectWithTimeout

这部分代码是从原有工程里抠出来的,在学习完前面知识后,我觉得有些地方不是很完善,按照下面的代码做了修改。此处将老代码贴出只是为了防止自己的理解有误,做了画蛇添足的事情。新代码如下:

  STATUS connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm)
{
int err = ;
int len = sizeof(int);
int flag;
int ret = -;
int retselect = -;
fd_set set;
struct timeval mytm; if (tm != NULL){
memcpy(&mytm, tm, sizeof(struct timeval));
} flag = ;
ioctl(sock,FIONBIO,&flag); ret = connect(sock, addrs, adrsLen);
if (- == ret)
{
if (EINPROGRESS == errno)
{
reselect:
FD_ZERO(&set);
FD_SET(sock,&set);
if ((retselect = select(sock+, NULL, &set, NULL, tm)) > )
{
if (FD_ISSET(sock, &set))
{
getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
if ( == err)
ret = ;
else
ret = -;
} }
else if (retselect < )
{
if (EINTR == errno)
{
printf("error! errno = %s:%d\n", strerror(errno), errno);
goto reselect;
}
}
}
}
else if ( == ret)
{
ret = ; //OK
} flag = ;
ioctl(sock, FIONBIO, &flag); if (tm != NULL){
memcpy(tm, &mytm, sizeof(struct timeval));
} return ret;
}

connectWithTimeoutNew.c

这是一个非阻塞式connect的模型,更为详细的介绍可以看《UNIX网络编程》(我真不是个做广告的)。但此处模型个人认为逻辑上还是完善的。

15-16行:设置套接口为非阻塞方式,这个步骤需要视使用环境而定,因此这里存在一个移植性问题。

18-21行:非阻塞模式下的connect,调用后立即返回,如果已经errno为EINPROGRESS,说明已经发起了三次握手。否则为异常。

23-45行:使用select去监测套接口状态。实现规定(那些乱七八糟的不同实现,我一直搞不清楚,哎,接触的不多!大家可以自己去查查):(1)当连接建立成功时,描述字变为可写,(2)当连接建立遇到错误时,描述字变为既可读又可写。我是个比较投机的人,归纳一下,描述字变为可写时说明连接有结果了。30行使用getsockopt获取描述字状态(使用SO_ERROR选项)。建立成功err为0,否则为-1。对于一个商业软件的“贡献者”,我们不能放过任何出错处理,但这里不对getsockopt的返回值进行出错处理是有原因的。在异常情况下,有些实现该调用返回0,有些则返回-1,因此我们不能根据它的返回值来判断连接是否异常。38-45行,前面已经提到,select这个阻塞的家伙很有可能发生EINTR错误,我们也必须兼容这种错误。注意reselect的位置,自己使用时位置放错了效果可是截然不同的。

48-51行:connect有时反应很快啊,一经调用就成功建立连接了,这种情况是存在的。所以我们也要兼容这种情况。

后面行:恢复调用前状态。

又到了热血澎湃的总结时刻:以前,看到有些地方使用非阻塞的connect,真的很费解,为什么简单的connect不直接使用,还要搞那么多花样?现在其实我们应该可以想明白了,作为一个完美程序的追求者,着眼于代码长期地维护(自己太高尚了),阻塞的connect确实会存在很多的问题。那是否存在一个稳健的阻塞的connect版本,说实话,我没仔细想过,粗略地想想那应该是个很复杂的东西,比如:connect返回时是否已经开始三次握手等等?因此,阻塞的或者非阻塞的connect目前并非再是二者选其一、根据喜好选择使用的问题了,而是必须使用非阻塞的connect。这个结论或许很武断,但本人通过目前了解的知识得出了这样的结论,还是希望有大神突然出现,指点一二。

7.Accept返回前连接夭折

这是《UNIX网络编程》上的原装问题,新发现的,按照书上的说法,貌似很严重,我要先测试一下。在我给出一个比较稳健的程序之前不允许存在已知的类似问题。又要牵涉到SO_LINGER选项了,真是有点无法自圆自说的感觉。就当初探SO_LINGER选项吧,现在它只是配角,后面有机会详细研究一下它。

服务器代码,这个代码同样可以用来测试select发生的EINTR的问题:

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h> #define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000 void sig_chld(int signo)
{
pid_t pid;
int stat = ; pid = wait(&stat);
printf("child %d terminated(stat:%d)\n", pid, stat); return;
} void signal_ex(int signo, void* func)
{
struct sigaction act, oact; act.sa_handler = func;
sigemptyset(&act.sa_mask); //清空此信号集
act.sa_flags = ; if (sigaction(signo, &act, &oact) < )
{
printf("sig err!\n");
} //sigaction(SIGINT, &oact, NULL); //恢复成原始状态
return;
} int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE + ] = {};
int num = ;
pid_t pid_child;
int ret;
fd_set set;
struct timeval mytm; if ((listenfd = socket(AF_INET, SOCK_STREAM, )) == -)
{
perror("Creating socket failed.");
exit();
} int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -)
{
perror("Bind()error.");
exit();
}
if (listen(listenfd, BACKLOG) == -)
{
perror("listen()error\n");
exit();
} signal_ex(SIGCHLD, sig_chld);
while ()
{
addrlen = sizeof(client);
sleep();
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -)
{
#if 1
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif perror("accept()error\n");
exit();
}
printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port)); if ( == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep();
exit();
} mytm.tv_sec = ;
mytm.tv_usec = ;
reselect:
FD_ZERO(&set);
FD_SET(connectfd, &set);
if ((ret = select(connectfd + , &set, NULL, NULL, &mytm)) > )
{
if(FD_ISSET(connectfd, &set))
{
printf("connectfd can be readn!\n");
}
}
else if ( == ret)
{
printf("timeout!\n");
}
else if (ret < )
{
//perror("error! ");
if (EINTR == errno)
{
printf("error! errno = %s:%d\n", strerror(errno), errno);
goto reselect;
}
} close(connectfd);
connectfd = -;
} close(listenfd); return ;
}

server_select.c

客户端代码:

 #include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h> #define PORT 1234
#define MAXDATASIZE 1000 int main(int argc, char *argv[])
{
int sockfd = -;
struct sockaddr_in server;
struct linger ling; if (argc != )
{
printf("Usage:%s <IP Address>\n", argv[]);
exit();
} signal(SIGPIPE, SIG_IGN); if ((sockfd=socket(AF_INET, SOCK_STREAM, )) == -)
{
printf("socket()error\n");
exit();
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -)
{
printf("connect()error\n");
exit();
} ling.l_onoff = ;
ling.l_linger = ;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); close(sockfd); return ;
}

client_select.c

书上提到,有些实现内核会抛一个错误给应用进程,应用进程在accept时会返回错误。而有些实现内核完全能够兼容这种问题,不对应用进程产生任何影响。强大的linux显然是后者,客户端发送RST后,服务器accept没有返回任何错误,运行正常。

因此,该问题暂且略过,实际使用时还是应当注意这种极端的情况。

8.最终的代码

写到这,赶紧要为这个标题画句号了。一边写一边学,再这么下去就成无底洞了。而UDP的相关知识自己本身用得不多,在这不敢下笔,等哪一天心中有物再来详细整理一下。因此,这边要很不负责任地给出一个最终版本的TCP代码了。

个人认为,网络编程还有很多知识,但是该代码已经可以应付一个新手解决很多实际的问题了。之所以叫“快速上手”,干得也就是这种事,离精通还是很远的。之后,有什么问题再以专题的形式给出吧。

先是服务器代码:

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h> #define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
#define TEST_STRING "HELLO, WORLD!"
#define TEST_STRING_LEN strlen(TEST_STRING) int readn(int connfd, void *vptr, int n)
{
int nleft;
int nread;
char *ptr;
int ret = -;
struct timeval select_timeout;
fd_set rset; ptr = (char*)vptr;
nleft = n; while (nleft > )
{
FD_ZERO(&rset);
FD_SET(connfd, &rset);
select_timeout.tv_sec = ;
select_timeout.tv_usec = ;
if ((ret = select(connfd+, &rset, NULL, NULL, &select_timeout)) < )
{
if (errno == EINTR)
{
continue;
}
else
{
return -;
}
}
else if ( == ret)
{
return -;
}
if ((nread = recv(connfd, ptr, nleft, )) < )
{
if(errno == EINTR)
{
nread = ;
}
else
{
return -;
}
}
else if (nread == )
{
break;
}
nleft -= nread;
ptr += nread;
} return(n - nleft);
} void sig_chld(int signo)
{
pid_t pid;
int stat = ; pid = wait(&stat); return;
} void signal_ex(int signo, void* func)
{
struct sigaction act, oact; act.sa_handler = func;
sigemptyset(&act.sa_mask); //清空此信号集
act.sa_flags = ; if (sigaction(signo, &act, &oact) < )
{
printf("sig err!\n");
} //sigaction(SIGINT, &oact, NULL); //恢复成原始状态
return;
} int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE + ] = {};
int num = ;
pid_t pid_child; signal(SIGPIPE, SIG_IGN); if ((listenfd = socket(AF_INET, SOCK_STREAM, )) == -)
{
perror("Creating socket failed.");
exit();
} int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -)
{
perror("Bind()error.");
exit();
}
if (listen(listenfd, BACKLOG) == -)
{
perror("listen()error\n");
exit();
} signal_ex(SIGCHLD, sig_chld);
while ()
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -)
{
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
} perror("accept()error\n");
exit();
}
printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port)); if ( == (pid_child = fork()))
{
while ()
{
num = readn(connectfd, szbuf, TEST_STRING_LEN);
if (num < )
{
printf("read error!\n");
break;
}
else if ( == num)
{
printf("read over!\n");
break;
}
else
{
printf("recv: %s\n", szbuf);
}
}
close(connectfd);
close(listenfd);
sleep();
exit();
} close(connectfd);
connectfd = -;
} close(listenfd); return ;
}

server_last.c

客户端:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h> #define PORT 1234
#define MAXDATASIZE 1000
#define TEST_STRING "HELLO, WORLD!"
#define TEST_STRING_LEN strlen(TEST_STRING) int writen(int connfd, void *vptr, size_t n)
{
int nleft, nwritten;
char *ptr; ptr = (char*)vptr;
nleft = n; while (nleft > )
{
if ((nwritten = send(connfd, ptr, nleft, MSG_NOSIGNAL)) == -)
{
if (errno == EINTR)
{
nwritten = ;
}
else
{
return -;
}
}
nleft -= nwritten;
ptr += nwritten;
} return(n);
} int connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm)
{
int err = ;
int len = sizeof(int);
int flag;
int ret = -;
int retselect = -;
fd_set set;
struct timeval mytm; if (tm != NULL){
memcpy(&mytm, tm, sizeof(struct timeval));
} flag = fcntl(sock, F_GETFL, );
fcntl(sock, F_SETFL, flag | O_NONBLOCK); //linux用这个
// flag = 1;
// ioctl(sock,FIONBIO,&flag); ret = connect(sock, addrs, adrsLen);
if (- == ret)
{
if (EINPROGRESS == errno)
{
reselect:
printf("start check!\n");
FD_ZERO(&set);
FD_SET(sock,&set);
if ((retselect = select(sock+, NULL, &set, NULL, tm)) > )
{
if (FD_ISSET(sock, &set))
{
getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
if ( == err)
ret = ;
else
ret = -;
} }
else if (retselect < )
{
if (EINTR == errno)
{
printf("error! errno = %s:%d\n", strerror(errno), errno);
goto reselect;
}
}
}
}
else if ( == ret)
{
printf("OK at right!\n");
ret = ; //OK
} fcntl(sock, F_SETFL, flag);
// flag = 0;
// ioctl(sock, FIONBIO, &flag); if (tm != NULL){
memcpy(tm, &mytm, sizeof(struct timeval));
} return ret;
} int main(int argc, char *argv[])
{
int sockfd, num;
char szbuf[MAXDATASIZE] = {};
struct sockaddr_in server;
struct timeval timeOut;
int ret = -;
int iSendTime = ; if (argc != )
{
printf("Usage:%s <IP Address>\n", argv[]);
exit();
} signal(SIGPIPE, SIG_IGN); if ((sockfd = socket(AF_INET, SOCK_STREAM, )) == -)
{
printf("socket()error\n");
exit();
} bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[]); timeOut.tv_sec = ;
timeOut.tv_usec = ;
ret = connectWithTimeout(sockfd, (struct sockaddr *)&server, sizeof(server), &timeOut);
if (- == ret)
{
printf("connect()error\n");
exit();
} memset(szbuf, , sizeof(szbuf));
strcpy(szbuf, TEST_STRING); while (iSendTime < )
{
ret = writen(sockfd, szbuf, TEST_STRING_LEN);
if (TEST_STRING_LEN != ret)
{
break;
}
else
{
printf("%dth send success!\n", iSendTime);
iSendTime++;
}
} close(sockfd); return ;
}

client_last.c

总结的时候,有很多知识都得到了更新,最终代码对部分函数进行了完善。当然还有一些东西没有考虑进去,比如说对端掉电、路由器损坏等问题,以上程序都无法很好的适应这些问题。后续再慢慢改进吧。

最终的例子功能很简单,最主要还是在socket编程的各个细节的处理上。

在此要说OVER了!

socket网络编程快速上手(二)——细节问题(5)(完结篇)的相关教程结束。

《socket网络编程快速上手(二)——细节问题(5)(完结篇).doc》

下载本文的Word格式文档,以方便收藏与打印。