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

6.Connect的使用方式

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

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

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

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

 server_select.c
#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 = 0;
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 = 0; if (sigaction(signo, &act, &oact) < 0)
{
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 + 1] = {0};
int num = 0;
pid_t pid_child;
int ret;
fd_set set;
struct timeval mytm; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
} 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)) == -1)
{
perror("Bind()error.");
exit(1);
}
if (listen(listenfd, BACKLOG) == -1)
{
perror("listen()error\n");
exit(1);
}
signal_ex(SIGCHLD, sig_chld);
while (1)
{
addrlen = sizeof(client);
sleep(10);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
#if 1
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif perror("accept()error\n");
exit(1);
}
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 (0 == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep(5);
exit(0);
}
mytm.tv_sec = 15;
mytm.tv_usec = 0;
reselect:
FD_ZERO(&set);
FD_SET(connectfd, &set);
if ((ret = select(connectfd + 1, &set, NULL, NULL, &mytm)) > 0)
{
if(FD_ISSET(connectfd, &set))
{
printf("connectfd can be readn!\n");
}
}
else if (0 == ret)
{
printf("timeout!\n");
}
else if (ret < 0)
{
//perror("error! ");
if (EINTR == errno)
{
printf("error! errno = %s:%d\n", strerror(errno), errno);
goto reselect;
}
}
close(connectfd);
connectfd = -1;
} close(listenfd); return 0;
}
server_select.c

客户端代码:

 client_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 = -1;
struct sockaddr_in server;
struct linger ling; if (argc != 2)
{
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
}
signal(SIGPIPE, SIG_IGN); if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket()error\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
printf("connect()error\n");
exit(1);
}
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); close(sockfd); return 0;
}
client_select.c

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

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

8.最终的代码

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

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

先是服务器代码:

 server_last.c
#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 = -1;
struct timeval select_timeout;
fd_set rset;
ptr = (char*)vptr;
nleft = n;
while (nleft > 0)
{
FD_ZERO(&rset);
FD_SET(connfd, &rset);
select_timeout.tv_sec = 5;
select_timeout.tv_usec = 0;
if ((ret = select(connfd+1, &rset, NULL, NULL, &select_timeout)) < 0)
{
if (errno == EINTR)
{
continue;
}
else
{
return -1;
}
}
else if (0 == ret)
{
return -1;
}
if ((nread = recv(connfd, ptr, nleft, 0)) < 0)
{
if(errno == EINTR)
{
nread = 0;
}
else
{
return -1;
}
}
else if (nread == 0)
{
break;
}
nleft -= nread;
ptr += nread;
} return(n - nleft);
}
void sig_chld(int signo)
{
pid_t pid;
int stat = 0;
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 = 0; if (sigaction(signo, &act, &oact) < 0)
{
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 + 1] = {0};
int num = 0;
pid_t pid_child;
signal(SIGPIPE, SIG_IGN); if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
} 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)) == -1)
{
perror("Bind()error.");
exit(1);
}
if (listen(listenfd, BACKLOG) == -1)
{
perror("listen()error\n");
exit(1);
}
signal_ex(SIGCHLD, sig_chld);
while (1)
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
} perror("accept()error\n");
exit(1);
}
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 (0 == (pid_child = fork()))
{
while (1)
{
num = readn(connectfd, szbuf, TEST_STRING_LEN);
if (num < 0)
{
printf("read error!\n");
break;
}
else if (0 == num)
{
printf("read over!\n");
break;
}
else
{
printf("recv: %s\n", szbuf);
}
}
close(connectfd);
close(listenfd);
sleep(5);
exit(0);
} close(connectfd);
connectfd = -1;
} close(listenfd); return 0;
}
server_last.c

客户端:

 client_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 > 0)
{
if ((nwritten = send(connfd, ptr, nleft, MSG_NOSIGNAL)) == -1)
{
if (errno == EINTR)
{
nwritten = 0;
}
else
{
return -1;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
int connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm)
{
int err = 0;
int len = sizeof(int);
int flag;
int ret = -1;
int retselect = -1;
fd_set set;
struct timeval mytm; if (tm != NULL){
memcpy(&mytm, tm, sizeof(struct timeval));
}
flag = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flag | O_NONBLOCK); //linux用这个
// flag = 1;
// ioctl(sock,FIONBIO,&flag);
ret = connect(sock, addrs, adrsLen);
if (-1 == ret)
{
if (EINPROGRESS == errno)
{
reselect:
printf("start check!\n");
FD_ZERO(&set);
FD_SET(sock,&set);
if ((retselect = select(sock+1, NULL, &set, NULL, tm)) > 0)
{
if (FD_ISSET(sock, &set))
{
getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
if (0 == err)
ret = 0;
else
ret = -1;
} }
else if (retselect < 0)
{
if (EINTR == errno)
{
printf("error! errno = %s:%d\n", strerror(errno), errno);
goto reselect;
}
}
}
}
else if (0 == ret)
{
printf("OK at right!\n");
ret = 0; //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] = {0};
struct sockaddr_in server;
struct timeval timeOut;
int ret = -1;
int iSendTime = 0; if (argc != 2)
{
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
}
signal(SIGPIPE, SIG_IGN); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket()error\n");
exit(1);
} bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[1]);
timeOut.tv_sec = 5;
timeOut.tv_usec = 0;
ret = connectWithTimeout(sockfd, (struct sockaddr *)&server, sizeof(server), &timeOut);
if (-1 == ret)
{
printf("connect()error\n");
exit(1);
}
memset(szbuf, 0, sizeof(szbuf));
strcpy(szbuf, TEST_STRING); while (iSendTime < 5)
{
ret = writen(sockfd, szbuf, TEST_STRING_LEN);
if (TEST_STRING_LEN != ret)
{
break;
}
else
{
printf("%dth send success!\n", iSendTime);
iSendTime++;
}
} close(sockfd); return 0;
}
client_last.c

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

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

在此要说OVER了!

 
 

SOCKET网络编程5的更多相关文章

  1. Linux Socket 网络编程

    Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...

  2. Python Socket 网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

  3. Python全栈【Socket网络编程】

    Python全栈[socket网络编程] 本章内容: Socket 基于TCP的套接字 基于UDP的套接字 TCP粘包 SocketServer 模块(ThreadingTCPServer源码剖析) ...

  4. python之Socket网络编程

    什么是网络? 网络是由节点和连线构成,表示诸多对象及其相互联系.在数学上,网络是一种图,一般认为专指加权图.网络除了数学定义外,还有具体的物理含义,即网络是从某种相同类型的实际问题中抽象出来的模型.在 ...

  5. Python之路【第七篇】python基础 之socket网络编程

    本篇文章大部分借鉴 http://www.cnblogs.com/nulige/p/6235531.html python socket  网络编程 一.服务端和客户端 BS架构 (腾讯通软件:ser ...

  6. Socket网络编程-基础篇

    Socket网络编程 网络通讯三要素: IP地址[主机名] 网络中设备的标识 本地回环地址:127.0.0.1 主机名:localhost 端口号 用于标识进程的逻辑地址 有效端口:0~65535 其 ...

  7. Socket网络编程--FTP客户端

    Socket网络编程--FTP客户端(1)(Windows) 已经好久没有写过博客进行分享了.具体原因,在以后说. 这几天在了解FTP协议,准备任务是写一个FTP客户端程序.直接上干货了. 0.了解F ...

  8. windows下的socket网络编程

    windows下的socket网络编程 windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了, ...

  9. windows下的socket网络编程(入门级)

    windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先 ...

  10. Java Socket 网络编程心跳设计概念

    Java Socket 网络编程心跳设计概念   1.一般是用来判断对方(设备,进程或其它网元)是否正常动行,一 般采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经当掉.用于 ...

随机推荐

  1. Office——检索 COM 类工厂中 CLSID 为 {000209FF-0000-0000-C000-000000000046} 的组件时失败

    检索 COM 类工厂中 CLSID 为 {000209FF-0000-0000-C000-000000000046} 的组件时失败,原因是出现以下错误: 8000401a 1.运行dcomcnfg.e ...

  2. python_基础学习_01_按行读取文件的最优方法

    python 按行读取文件 ,网上搜集有N种方法,效率有区别,先mark最优答案,下次补充测试数据 with open('filename') as file: for line in file: d ...

  3. 关于”机器学习方法“,&quot;深度学习方法&quot;系列

    "机器学习/深度学习方法"系列,我本着开放与共享(open and share)的精神撰写,目的是让很多其它的人了解机器学习的概念,理解其原理,学会应用.如今网上各种技术类文章非常 ...

  4. hdu3689(kmp+dp)

    题意:问随机生成一个长度为m(m<=1000)长度的字符串,出现某个子串s的概率是多少. 解法:dp+kmp优化.ans[i][j]表示i长度,走到了s的j位置的概率,当然这是在i之前没有出现s ...

  5. 我们的空间是它圆——基于Poicare对宇宙的模型

    一般 状态 在人类文明的开始,并探讨了空间和时间的混乱从来没有停止过.马跑得更快.鱼下潜深.鸟振翅高飞,但是,人类并没有很深的不满潜飞不高.为什么?其原因是,马跑得更快,但它不会不知道他们为什么会跑得 ...

  6. Java设计模式偷跑系列(21)建模和实现享受metapatterns

    转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/40021651 享元模式(Flyweight):运用共享的技术有效地支持大量细粒度的对象. ...

  7. MySQL 升级方法指南大全

    原文:MySQL 升级方法指南大全 通常,从一个发布版本升级到另一个版本时,我们建议按照顺序来升级版本.例如,想要升级 MySQL 3.23 时,先升级到 MySQL 4.0,而不是直接升级到 MyS ...

  8. XML的序列化与反序列化

    开发时会把数据持久化成xml格式,当然可以用xmlwriter来实现,不过感觉不方便,而且很繁琐.推荐使用直接序列化.反序列化对象的方法来处理. 直接上代码: public static class ...

  9. php中des加密解密 匹配C#des加密解密 对称加密

    原文:php中des加密解密 匹配C#des加密解密 对称加密 网上找来的 php des加密解密 完全匹配上一篇C# 字符串加密解密函数  可以用于C#和php通信 对数据进行加密,其中$key 是 ...

  10. sql点滴40—mysql乱码问题总结

    原文:sql点滴40-mysql乱码问题总结 本文将为大家讲解如何处理Java连接过程中的MySQL中文乱码问题.一般MySQL中文乱码问题都是与字符集有关,这里作者的经历也大致差不多. MySQL默 ...