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. FloatyFish下载量

    老师之前没有统一好一个平台,为了公平起见,我们选择了知名度比较高的CSDN,上次课上给老师说的下载量已成为过去,我们目前的下载量是: 这里还有我们最真实的用户体验,来自CSDN的用户,而非我们的朋友: ...

  2. 导入三方包,出现ClassNotFoundException

    在项目中须要引用settings模块里面的某个活动.在eclipse中导入settins.jar包之后,使用例如以下方式启动: Intent intent = new Intent(); intent ...

  3. python test0729.py

    #!/usr/env python #-*- coding: utf-8 -*- import urllib import urllib2 import random import requests ...

  4. linux旁边flv注射MetaData工具

    1,yamdi 官方网站:http://yamdi.sourceforge.net/                   下载链接:http://sourceforge.net/projects/ya ...

  5. UIImage分类:返回一个可以拉伸的图片

    // 返回一个可拉伸的图片 // UIImage的分类 + (UIImage *)resizedImage:(NSString *)name { UIImage *image = [self imag ...

  6. asp.net mvc3 的数据验证(一)

    原文:asp.net mvc3 的数据验证(一)      对于web开发人员来说,对用户输入的信息进行验证是一个重要但是繁琐的工作,而且很多开发者都会忽略.asp.net mvc3框架使用的是叫做“ ...

  7. 关于Android开发中导出jar包后的资源使用问题解决

    我们经常遇到一个需求,就是给别人使用我们工程的时候,为了能够屏蔽代码,把代码封装成jar包提供给第三方使用,但是这样我们的资源文件怎么给对方用呢? 其实并不用这么的复杂,下面就介绍一下具体的方法 一, ...

  8. [C#][ASP.net] 透过WebBrowser 取得AJAX 后的网页

    原文[C#][ASP.net] 透过WebBrowser 取得AJAX 后的网页 今天 Shih-Min 问我说,假设网页一开始是AJAX 会载入一些资料,但是透过WebClient 去抓 抓到都是J ...

  9. 关于session_start()这个问题

    关于session_start()这个问题,其实网上很多解决的方法,论坛也好多人回答这类的问题, 现在的状况是依然有警告提示Warning: session_start() [function.ses ...

  10. Java集合之Stack 源码分析

    1.简介 栈是数据结构中一种很重要的数据结构类型,因为栈的后进先出功能是实际的开发中有很多的应用场景.Java API中提供了栈(Stacck)的实现,简单使用如下所示 package com.tes ...