Windows网络通信(二):socket异步编程
简述
这里使用的API和同步编程的API是差不多的,只多了一个ioctlsocket和select函数。这里面涉及一个很重要的结构体fd_set。这里用到的API大部分都是windows和linux通用的。
1. ioctlsocket控制socket的IO模型
int ioctlsocket(
_In_ SOCKET s,
_In_ long cmd,
_Inout_ u_long *argp
);s:需要设置的socket
cmd:想要对socket执行的命令,异步编程需要FIONBIO命令
argp:执行命令的参数,FIONBIO的参数如果为0表示阻塞模式,非0表示非阻塞模式
2. select获取一个或多个套接字的状态(可读可写或其他状态)
int select(
_In_ int nfds,
_Inout_ fd_set *readfds,
_Inout_ fd_set *writefds,
_Inout_ fd_set *exceptfds,
_In_ const struct timeval *timeout
);nfds:无用
readfds:一个fd_set,表示哪些套接字需要判断是否”可读”状态,其中”可读”状态可以是accept,recv或者套接字已经关闭,重置,中断。
writefds:一个fd_set,表示哪些套接字需要判断是否”可写”状态,其中”可写”状态可以是connect成功,send。一般来说send是会立刻返回的,但是当send缓存区被装满了,数据无法放入时就会导致send函数阻塞,异步模式下可以用select判断缓存区是否有空间。
exceptfds:一个fd_set,表示哪些套接字需要判断是否”异常”状态,其中”异常”状态一般是connect失败。
timeout:等待的超时时间,select会等待timeout毫秒,NULL表示无限等待
成功返回所有fd_set响应的套接字数目,错误发生返回SOCKET_ERROR,超时返回0.
3. fd_set就是一个简单的结构体,内部有一个socket数组和一个数组成员个数。
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;可以使用如下宏操作fd_set
FD_CLR(fd, set):从fd_set中删除指定的socket
FD_SET(fd, set):从fd_set中添加指定的socket
FD_ZERO(set):清空fd_set
FD_ISSET(fd, set):判断指定socket是否在fd_set中
异步通信示例
下面是异步通信服务端的代码,用于回发客户端发过来的消息,单线程中处理多个客户端的通信。
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib,"ws2_32.lib") #define IP "127.0.0.1"
#define DEFAULT_PORT 12345 #define Print_ErrCode(e) fprintf(stderr,"\n[Server]%s 执行失败: %d\n",e,WSAGetLastError())
#define DEFAULT_BACKLOG 5
#define MAX_IO_PEND 10
int curr_size = 0; //当前的句柄数
#define OP_READ 0x10
#define OP_WRITE 0x20 //定义结构体用于储存通信信息
typedef struct _socklist
{
SOCKET sock;
DWORD Op;
char name[100];
char Buffer[128];
int bufLen;
} Socklist; int main(int argc, char **argv)
{
int nStartup = 0;
struct sockaddr_in clientService;
WSADATA wsaData;
SOCKET sockListen = INVALID_SOCKET;
int nRet = 0;
//保存所有的客户端、服务端的SOCKET信息
if (0 != (nStartup = WSAStartup(MAKEWORD(2, 2), &wsaData)))
{
WSASetLastError(nStartup); //WSAStartup不会自动设置错误代码
Print_ErrCode("WSAStartup()");
return 1;
}
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr(IP);
clientService.sin_port = htons(DEFAULT_PORT); if (INVALID_SOCKET ==
(sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
)
{
Print_ErrCode("socket()");
return 1;
}
u_long type = 1;
ioctlsocket(sockListen, FIONBIO, &type); if (SOCKET_ERROR == bind(sockListen,
(SOCKADDR *)&clientService,
sizeof(clientService)
))
{
Print_ErrCode("bind()");
closesocket(sockListen);
return 1;
} if (SOCKET_ERROR == listen(sockListen, DEFAULT_BACKLOG))
{
Print_ErrCode("listen()");
closesocket(sockListen);
}
printf("[Server]监听 %s:%d\n", IP, DEFAULT_PORT); //存放所有的socket,包括用于accept的socket。
Socklist sockList[10];
//将监听socket设为socklist第一个元素
sockList[0].sock = sockListen;
curr_size = 1; // 一个大循环,不断的接收客户端请求
while(true)
{
//循环判断是否有请求需要处理
fd_set fdRead, fdWrite;
while (true)
{
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_SET(sockList[0].sock, &fdRead);
for (int i = 1; i < curr_size; i++)
{
//对需要send的客户端连接select
if (sockList[i].Op == OP_WRITE)
{
FD_SET(sockList[i].sock, &fdWrite);
}
//对所有的客户端连接select
FD_SET(sockList[i].sock, &fdRead);
}
//这个操作会被阻塞
nRet = select(0, &fdRead, &fdWrite, NULL, NULL);
if (FD_ISSET(sockList[0].sock, &fdRead))
{
//第0个socket可用了,这时accept一定会立刻返回成功或失败 这里需要处理最大连接数
SOCKET sockNewClient = accept(sockListen,NULL,NULL);
sockList[curr_size].sock = sockNewClient;
sockList[curr_size++].Op = OP_READ;
break;
}
//其他socket可用了,判断哪些能读,哪些能写
if (fdRead.fd_count > 0)
{
for (int i = 1; i < curr_size; i++)
{
if (FD_ISSET(sockList[i].sock, &fdRead))
{
//开始recv
nRet = recv(sockList[i].sock, sockList[i].Buffer, 127, 0);
if (nRet == SOCKET_ERROR)
{
closesocket(sockList[i].sock);
//移除sockList
for (int j = i; j < curr_size-1; j++)
{
sockList[i].sock = sockList[i + 1].sock;
}
curr_size--;
}
else
{
sockList[i].Buffer[nRet] = '\0';
sockList[i].bufLen = nRet;
sockList[i].Op = OP_WRITE;
printf("[Server]接收到:%s\n", sockList[i].Buffer);
}
}
}
}
if (fdWrite.fd_count > 0)
{
for (int i = 1; i < curr_size; i++)
{
if (FD_ISSET(sockList[i].sock, &fdWrite))
{
if (sockList[i].Op == OP_WRITE)
{
//开始send
nRet = send(sockList[i].sock, sockList[i].Buffer, sockList[i].bufLen, 0);
//事实上,这里可能会有nRet小于bufLen的情况
if (nRet == SOCKET_ERROR)
{
closesocket(sockList[i].sock);
//移除sockList
for (int j = i; j < curr_size - 1; j++)
{
sockList[i].sock = sockList[i + 1].sock;
}
curr_size--;
}
else
{
sockList[i].Op = OP_READ;
printf("[Server]已发送:%d\n", nRet);
}
}
}
}
}
}
}
}
Windows网络通信(二):socket异步编程的更多相关文章
- windows下的socket网络编程
windows下的socket网络编程 windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了, ...
- windows下的socket网络编程(入门级)
windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先 ...
- socket异步编程--libevent的使用
使用 libevent 和 libev 提高网络应用性能 http://www.ibm.com/developerworks/cn/aix/library/au-libev/ libevent实现ht ...
- [转] socket异步编程--libevent的使用
这篇文章介绍下libevent在socket异步编程中的应用.在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socket I/O操作上造成程序性能的下降,需要使用异步编程,即程序准备好读写的 ...
- 基于.net的Socket异步编程总结
最近在为公司的分布式服务框架做支持异步调用的开发,这种新特性的上线需要进行各种严格的测试.在并发性能测试时,性能一直非常差,而且非常的不稳定.经过不断的分析调优,发现Socket通信和多线程异步回调存 ...
- NODE编程(二)--异步编程技术
在Node世界里流行两种响应逻辑管理方式,回调和事件监听. 回调通常用来定义一次性响应的逻辑.比如对于数据的查询,可以指定一个回调函数来确定如何处理查询结果. 事件监听器,本质上也是一个回调,不同的是 ...
- 网络编程之Socket异步编程
看了上篇socket入门,相信你已经对socket有一定的了解了http://www.cnblogs.com/nsky/p/4501782.html 现在来回顾一下.上篇在循环接收客户端连接和循环接收 ...
- Cfree clion windows c语言 socket 网络编程
server.c #include <stdio.h> #include <winsock2.h> #define SERVER_PORT 5208 //侦听端口 int ma ...
- Async 、 Await 的异步编程(.NET 4.5 新异步模型) [转自MSDN]
使用异步编程,可以避免性能瓶颈和增强应用程序的总体响应能力. 但是,编写异步应用程序的以前的技术可能比较复杂,使它们难以编写,调试和维护. Visual Studio 2012 引入了一个简化的方法, ...
随机推荐
- 简单属性margin和padding
关于margin属性的介绍 margin:20px 上 右 下 左 都是20px margin:20px 40 px 上 下 20px 左 右 40px margin:20px 40px 60px ...
- 【转】上传jar包到nexus私服
原文:https://my.oschina.net/lujianing/blog/297128 1通过网页上传 这种方法只是上传了jar包.通过maven引用当前jar,不能取得jar的依赖 from ...
- Web | JavaScript的引用数据类型强制转换类型
我在这里主要的想提下的是JavaScript中的引用类型进行强制转换类型.因为对于基本数据类型的变换大多都是雷同的,很容易熟知,但是引用数据类型有一点小插曲. JavaScript的引用类型主要为对象 ...
- zookeeper报错 JAVA_HOME is not set
很多开发者安装zookeeper的时候,应该会发现到这么一个问题: JAVA_HOME is not set 好的!那么这个是什么意思呢? 就是说你的 JAVA_HOME 变量没有设定 为什么会提示 ...
- 双硬盘双系统win10+manjaro-kde搭建
电脑sdd+hdd双硬盘,默认win10装在了sdd分区,uefi+gpt引导.现在想要在hdd中划分出一个分区安装manjaro,并在开机多重引导. 1. 制作安装盘 先去下载最新的镜像,最好在国内 ...
- shell习题第9题:sed的常用用法
[题目要求] 把一个文本文档的前5行中包含字母的行删除掉,同时把6到10行中的全部字母删除掉. [核心要点] sed命令 [脚本] .txt |sed '/[a-zA-Z]/d' .txt |sed ...
- 大数据IDEA调试flink程序
Flink在IDEA中开发是一件比较困难的事情,网上没有参考资料,就算就业说的太过笼统,不知道是会了不说还是不会瞎说,为了解决flink这个问题,本人特别做了一遍开发的简单说明.主要考虑两个问题,1. ...
- Altiun designer问题汇总(不断更新)
(1)元件库-引脚名称被矩形方框遮住 该问题可能是因为设置中文版而产生的错误,可以尝试在旁边再摆一个矩形,并且摆上引脚观察是否会被隐藏.如果还存在该现象,先将版本语言改为原版(英文版),再重新绘制即可
- [Golang学习笔记] 01 工作区和GOPATH
Go语言3个环境变量: GOROOT:GO语言按照根路径,也就是GO语言的安装路径. GOPATH:若干工作区目录的路径.是我自己定义的工作空间. GOBIN:GO程序生成的可执行文件(executa ...
- 自己用原生JS写的轮播图,支持移动端触摸滑动,分页器圆点可以支持mouseover鼠标移入和click点击,高手看了勿喷哈
自己用原生JavaScript写的轮播图,分页器圆点按钮可支持click点击,也可支持mouseover鼠标悬浮触发,同时支持移动端触摸滑动,有兴趣的友友可以试试哈,菜鸟一枚,高手看了勿喷,请多多指正 ...