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. mac os x10.11.2系统eclipse无法读取环境变量的问题

    eclipse调试Android自动化脚本的时候一直无法找到adb,遇到这么坑的问题,折腾死了,记录一下. mac os x10.11.2系统GUI程序(eclipse)无法读取~/.bash_pro ...

  2. WebApi统一输出接口

    public class WebApi { /// <summary> /// 成功后的输出 /// </summary> /// <param name="d ...

  3. Sql数据类型转换

     一.ASCII码值与字符间转换 1.ASCII()与CHAR()       ASCII()返回字符表达式最左端字符的ASCII 码值.在ASCII()函数中,纯数字的字符串可不用''括起来,但含其 ...

  4. Struts2.0+Spring3+Hibernate3(SSH~Demo)

    Struts2.0+Spring3+Hibernate3(SSH~Demo) 前言:整理一些集成框架,发现网上都是一些半成品,都是共享一部分出来(确实让人很纠结),这是整理了一份SSH的测试案例,完全 ...

  5. JS读写Cookie(设置、读取、删除)

    JS读写Cookie(设置.读取.删除) Cookie是客户端存放数据的一种方式,可用来做状态保持. 1.设置Cookie: a.无过期时间:(若不设置过期时间,默认为会话级Cookie,浏览器关闭就 ...

  6. MFC中的HOOK编程

    HOOK,n.钩, 吊钩,通常称钩子. 在计算机中,是Windows消息处理机制的一个平台,应用程序能够在上面设置子程以监视指定窗体的某种消息,并且所监视的窗体能够是其它进程所创建的.当消息到达后,在 ...

  7. BZOJ 2120 色彩数 暴力

    标题效果:给定一个序列,两种操作: 1.询[l,r]间隔多少个不同的号码 2.单点变化 n,m<=1W 树盖树?树董事长?因此不必! 暴力之前,这个问题2s,不想复杂!适当的水太! 离散化一下! ...

  8. [Unity3D]Unity3D连衣裙实现游戏开发系统

    大家好,我是秦培.欢迎关注我的博客,我的博客地址">blog.csdn.net/qinyuanpei. 不知从什么时候開始,国产RPG单机游戏開始出现换装,仙剑系列中第一部实现了换装的 ...

  9. angularJS看MVVM

    从angularJS看MVVM   javascript厚积薄发走势异常迅猛,导致现在各种MV*框架百家争雄,MVVM从MVC演变而来,为javascript注入了全新的活力.我工作的业务不会涉及到a ...

  10. WPF 读写TxT文件

    原文:WPF 读写TxT文件 文/嶽永鹏 WPF 中读取和写入TxT 是经常性的操作,本篇将从详细演示WPF如何读取和写入TxT文件. 首先,TxT文件希望逐行读取,并将每行读取到的数据作为一个数组的 ...