需要解决问题:
原因:
socket在默认情况下是阻塞状态的,这就使得发送以及接收操作处于阻塞的状态,即调用不会立即返回,而是进入睡眠等待 操作完成。
解决方法:
设置socket套接字非阻塞,下面进行详解(在查找资料中也学到了许多)。
设置UDP发送以及接收操作非阻塞,主要用到了setsockopt这个函数:
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对setsockopt(),现选项的长度。
代码如下:
#include
#include
#include
#include
using namespace std;
int main()
{
//加载套接字,这里为了方便使用了mfc的函数
if (!AfxSocketInit()) {
cout << "Load socket failed:";
}
SOCKET sock;
int port = 4528;
SOCKADDR_IN servaddr, cliaddr;
//创建套接字
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1) {
cout << "socket failed:";
}
// 设置超时
struct timeval timeout;
timeout.tv_sec = 0;//秒
timeout.tv_usec = 100;//微秒
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) == -1) {
cout << "setsockopt failed:";
}
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "192.167.0.1", &servaddr.sin_addr.S_un.S_addr);//IP地址
servaddr.sin_port = htons(port);
if (bind(sock, (SOCKADDR*)&servaddr, sizeof(SOCKADDR)) == -1) {
cout << "bind failed:";
}
int len = sizeof(SOCKADDR);
for (; ; ) {
char mesg[1024] = {};
int n = recvfrom(sock, mesg, 1024, 0, (SOCKADDR*)&cliaddr, &len);
perror("recvfrom fail: ");
}
return 0;
}
经bug发现:setsockopt函数设置非阻塞只对秒级以上有效,上面代码中100微妙没有作用
当用TCP通讯时,tcp客户端要与服务端通信,必须先建立连接,即调用connect函数完成三次握手,而默认情况下connect是阻塞方式的,也就是说调用connect函数会发生阻塞,超时时间可能在10s至几分钟之间。这就会导致很长时间的等待,而我的tcp函数在界面程序中调用,导致界面进入假死的状态,无法响应。
为避免等待长时间的connect,使用非阻塞connect方式来处理,集体步骤大致为:
- 创建socket,返回套接口描述符
- 调用ioctlsocket把套接口描述符设置成非阻塞
- 调用connect开始建立连接
- 判断连接是否成功建立
A: 如果connect返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况)
B: 调用select来等待连接建立成功完成
- 继续判断select返回值
A:如果select返回0,则表示建立连接超时;
B: 如果select返回大于0的值,则需要检查套接口描述符是否可读或可写;如果套接口描述符可读或可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
ioctlsocket函数说明
函数原型:
int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp );
参数:
s:一个标识套接口的描述字。
cmd:对套接口s的操作命令。
argp:指向cmd命令所带参数的指针
使用:
u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
//控制为阻塞方式。u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
//控制为非阻塞方式。
代码如下 :
#include
#include
#include
#include
using namespace std;
bool SendOperRecordToHisDB();
int main()
{
SendOperRecordToHisDB();
return 0;
}
bool SendOperRecordToHisDB()
{
//加载套接字
if(!AfxSocketInit())
return false;
int PortNum = 2668;
char pSendBuf[10]="1247";
//创建套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, NULL);
//绑定Ip和端口
SOCKADDR_IN InetAddr;
inet_pton(AF_INET, "192.167.0.1", &InetAddr.sin_addr.S_un.S_addr);//IP地址
InetAddr.sin_family = AF_INET;
InetAddr.sin_port = htons(PortNum);
int timeout = 2;//秒
//socket设置为非阻塞
unsigned long on = 1;
if (ioctlsocket(sock, FIONBIO, &on) < 0) {
int err = WSAGetLastError();
return false;
}
int ret = connect(sock, (struct sockaddr*)&InetAddr, sizeof(InetAddr));
//因为是非阻塞的,这个时候错误码应该是WSAEWOULDBLOCK,Linux下是EINPROGRESS
if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) {
closesocket(sock);
cout << ("无法连接服务器!");
return false;
}
//返回值大于等于0
fd_set writeset;
FD_ZERO(&writeset);
FD_SET(sock, &writeset);
timeval tv;
tv.tv_sec = timeout;
tv.tv_usec = 0;
ret = select(sock + 1, NULL, &writeset, NULL, &tv);
if (ret > 0) {
send(sock, pSendBuf,10, 0);
}
if (ret == 0) {
cout << ("连接超时!");
}
closesocket(sock);
return true;
}