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的更多相关文章
-
Linux Socket 网络编程
Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...
-
Python Socket 网络编程
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
-
Python全栈【Socket网络编程】
Python全栈[socket网络编程] 本章内容: Socket 基于TCP的套接字 基于UDP的套接字 TCP粘包 SocketServer 模块(ThreadingTCPServer源码剖析) ...
-
python之Socket网络编程
什么是网络? 网络是由节点和连线构成,表示诸多对象及其相互联系.在数学上,网络是一种图,一般认为专指加权图.网络除了数学定义外,还有具体的物理含义,即网络是从某种相同类型的实际问题中抽象出来的模型.在 ...
-
Python之路【第七篇】python基础 之socket网络编程
本篇文章大部分借鉴 http://www.cnblogs.com/nulige/p/6235531.html python socket 网络编程 一.服务端和客户端 BS架构 (腾讯通软件:ser ...
-
Socket网络编程-基础篇
Socket网络编程 网络通讯三要素: IP地址[主机名] 网络中设备的标识 本地回环地址:127.0.0.1 主机名:localhost 端口号 用于标识进程的逻辑地址 有效端口:0~65535 其 ...
-
Socket网络编程--FTP客户端
Socket网络编程--FTP客户端(1)(Windows) 已经好久没有写过博客进行分享了.具体原因,在以后说. 这几天在了解FTP协议,准备任务是写一个FTP客户端程序.直接上干货了. 0.了解F ...
-
windows下的socket网络编程
windows下的socket网络编程 windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了, ...
-
windows下的socket网络编程(入门级)
windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先 ...
-
Java Socket 网络编程心跳设计概念
Java Socket 网络编程心跳设计概念 1.一般是用来判断对方(设备,进程或其它网元)是否正常动行,一 般采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经当掉.用于 ...
随机推荐
-
eclipse 创建maven web项目
参考:http://www.cnblogs.com/hongwz/p/5456616.html eclipse 创建maven web项目
-
WCF入门教程(vs2010)
这几天挺别人提起WCF,鄙人之前只知道WPF,对WCF这个东东不甚了解,经过查阅网上的资料略有所得,和大家交流一下. 首先WCF是什么? Windows Communication Foundatio ...
-
HDU 5319 Painter (模拟)
题意: 一个画家画出一张,有3种颜色的笔,R.G.B.R看成'\',B看成'/',G看成这两种的重叠(即叉形).给的是一个矩阵,矩阵中只有4种符号,除了3种颜色还有'.',代表没有涂色.问最小耗费多少 ...
-
JavaScript 函数基础
1. JavaScript 函数基础 1. 定义方法 2. 函数的调用方法 3. 函数方法 apply : 将函数作为数组的方法来调用 将参数以数组形式传递给该方法 call : 将函数作为对象的 ...
-
(译)ABP之Abp Session
原文地址:https://aspnetboilerplate.com/Pages/Documents/Abp-Session 介绍 ABP提供IAbpSession接口获取当前用户和租户信息,而不是使 ...
-
github windows pycharm 设置
Window 上pycharm数据上传到github 在window上操作 1),安装git(百度) 进入git , bin目录执行 git-bash.exe 1) gengyantao@DESKT ...
-
Linux 桌面玩家指南:18. 使用 Docker 隔离自己的开发环境和部署环境
特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...
-
myeclipse在过时的环境下部署项目出现的问题
开发环境 操作系统:xp sp3 2002 (ps:客户公司环境只有这个老古董) 开发工具:myeclipse2014 32位 jdk: 1.7_49 32位 DB:DB2 9.1 32位 ...
-
springCloud笔记
分布式和集群的理解:比如在一个厨房有两个厨师,一个炒菜,一个洗菜,各自做不同的事情,但是却在合作,这种叫做分布式,两个都在炒菜或者都在做菜,就叫做集群. eureka的是springCloud的注册中 ...
-
python学习快人一步,从19个语法开始!
Python简单易学,但又博大精深.许多人号称精通Python,却不会写Pythonic的代码,对很多常用包的使用也并不熟悉.学海无涯,我们先来了解一些Python中最基本的内容. Python的特点 ...