项目总结三:解决TCP/UDP通讯函数阻塞

需要解决问题:

  1. 主备线程身份判断UDP:recvfrom函数设置阻塞,影响程序执行下面的代码
  2. TCP:connect连接到服务器时间过长,影响界面操作

原因:

socket在默认情况下是阻塞状态的,这就使得发送以及接收操作处于阻塞的状态,即调用不会立即返回,而是进入睡眠等待    操作完成。

解决方法:

设置socket套接字非阻塞,下面进行详解(在查找资料中也学到了许多)。

一、设置UDP套接字非阻塞

设置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通讯时,tcp客户端要与服务端通信,必须先建立连接,即调用connect函数完成三次握手,而默认情况下connect是阻塞方式的,也就是说调用connect函数会发生阻塞,超时时间可能在10s至几分钟之间。这就会导致很长时间的等待,而我的tcp函数在界面程序中调用,导致界面进入假死的状态,无法响应。
为避免等待长时间的connect,使用非阻塞connect方式来处理,集体步骤大致为:

  1.  创建socket,返回套接口描述符
  2.  调用ioctlsocket把套接口描述符设置成非阻塞
  3. 调用connect开始建立连接
  4.  判断连接是否成功建立    

    A: 如果connect返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况)                

    B: 调用select来等待连接建立成功完成    

  5.  继续判断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;
}

 

你可能感兴趣的:(C++,TCP,UDP,c++,后端)