- 客户端代码:
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define DEF_PORT 27015
#define DEF_SIZE 512
int main()
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
int iResult;
//初始化Winsock
iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
if(iResult != 0){
cout<<"WSAStartup failed with error: "<<iResult<<endl;
return 1;
}
//创建用于连接的socket
ConnectSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(ConnectSocket == INVALID_SOCKET){
cout<<"socket created with error: "<<GetLastError()<<endl;
WSACleanup();
return 1;
}
//连接
sockaddr_in addserver;
addserver.sin_family = AF_INET;
addserver.sin_port = htons(DEF_PORT);
addserver.sin_addr.S_un.S_addr = inet_addr("10.50.92.211");
//连接
iResult = connect(ConnectSocket,(const sockaddr*)&addserver,sizeof(addserver));
if(iResult != 0){
printf("connect failed with error :%d\n",GetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
char sendbuf[DEF_SIZE];
printf("conntect successful...\n");
printf("Please input sendbuf:");
cin>>sendbuf;
//发送数据
iResult = send(ConnectSocket,sendbuf,sizeof(sendbuf),0);
if(iResult > 0){
printf("host send: %s\n",sendbuf);
}
closesocket(ConnectSocket);
WSACleanup();
system("pause");
return 0;
}
- 使用select模型的服务器代码:
首先说一下select模型是通过fd_set结构和几个系统给定的宏来对多个套接字进行管理的
结构体:
typedef struct fd_set{
u_int fd_count; //集合中包含套接字的数量
SOCKET fd_array[FD_SETSIZE]; //套接字数组集合,FD_SETSIZE默认是64
}fd_set;
宏:
FD_CLR(s,*set):从集合set中删除套接字s
FD_ISSET(s,*set):判断套接字s是否为集合set中的一个,是返回非0,否则返回0
FD_SET(s,*set):将套接字s添加到集合set中
FD_ZERO(*set):将set集合初始化为空集NULL
select()函数
int WSAAPI select(
int nfds, //是为了和Berkeley套接字兼容保留,执行函数会忽略。
//注意:linux一般是最大描述符+1
fd_set *readfds, //指向一个套接字集合,用于检测其可读性
fd_set *writefds, //指向一个套接字集合,用于检测其可写性
fd_set *exceptfds, //指向一个套接字集合,用于检测错误
const timeval *timeout //用于指定等待的时间,NULL代表阻塞态,一直等待
);
服务器的代码:
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define DEF_PORT 27015
#define DEF_SIZE 512
int main()
{
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET AcceptSocket = INVALID_SOCKET;
char recvbuf[DEF_SIZE] = {0};
//1.初始化
iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
if(iResult != 0)
{
printf("WSAStartup failed with error :%d\n",iResult);
return 1;
}
//2.创建用于监听的套接字
ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(ListenSocket == INVALID_SOCKET){
printf("create ListenSocket failed with error:%d\n",GetLastError());
WSACleanup();
return 1;
}
//3.为监听套接字绑定地址和端口号
sockaddr_in addServer;
addServer.sin_family = AF_INET;
addServer.sin_port = htons(DEF_PORT);
addServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
iResult = bind(ListenSocket,(const sockaddr*)&addServer,sizeof(addServer));
if(iResult == SOCKET_ERROR){
printf("bind failed with error:%d",GetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//4.监听客户端的连接
iResult = listen(ListenSocket,SOMAXCONN);
if(iResult == SOCKET_ERROR){
printf("Listen failed with error:%d\n",GetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("TCP Server is starting...\n");
//5.Select模型
/* 使用以下结构来管理多个套接字
typedef struct fd_set{
u_int fd_count; //集合中包含套接字的数量
SOCKET fd_array[FD_SETSIZE]; //套接字数组集合
}fd_set;
*/
sockaddr_in addrClient;
int addrClientSize = sizeof(addrClient);
fd_set fdRead,fdSocket;
FD_ZERO(&fdSocket); //初始化fdSocket集合
FD_SET(ListenSocket,&fdSocket); //将服务器的用于监听的ListenSocket加入到集合中
while (1)
{
//通过select(等待数据到达事件)
//如果有事件到达,移除fdRead集合中没有解决的IO操作的套接字句柄,然后返回
fdRead = fdSocket;
iResult = select(0,&fdRead,NULL,NULL,NULL);
//有网络时间发生
//确定有哪些套接字有未解决的I/O,并进一步处理这些I/O
if(iResult > 0){
for(int i = 0; i < (int)fdSocket.fd_count;++i)
{
if(FD_ISSET(fdSocket.fd_array[i],&fdRead))
{
if(fdSocket.fd_array[i] == ListenSocket)
{
if(fdSocket.fd_count < FD_SETSIZE)
{
//同时复用的套接字数量不能大于64
//有新的连接请求
AcceptSocket = accept(ListenSocket,(sockaddr*)&addrClient,&addrClientSize);
if(AcceptSocket == INVALID_SOCKET)
{
printf("accpet failed!\n");
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//增加新的连接套接字进行复用等待
FD_SET(AcceptSocket,&fdSocket);
printf("收到新的连接:%s\n",inet_ntoa(addrClient.sin_addr));
}
else
{
printf("连接个数受限\n");
continue;
}
}
else
{
//有数据到达
memset(recvbuf,0,sizeof(recvbuf));
iResult = recv(fdSocket.fd_array[i],recvbuf,sizeof(recvbuf),0);
if(iResult > 0){
//case1:数据到达
printf("数据内容: %s\n",recvbuf);
continue;
}
else if(iResult == 0){
//case2:连接关闭
printf("连接正常关闭\n");
closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i],&fdSocket);
}
else{
//case3:接收失败
printf("recv failed with error: %d\n",GetLastError());
closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i],&fdSocket);
WSACleanup();
return 1;
}
}
}
}
}
else {
printf("select failed with error\n");
break;
}
}
closesocket(ListenSocket);
WSACleanup();
system("pause");
return 0;
}
-
测试结果:
-
模型评价:
实现了单线程处理多个socket的I/O事件,避免了阻塞模式下线程膨胀的问题
但是可以管理的套接字数量有限,默认是64个,最大也是1024个。不过足以解决小型局域网中的I/O问题。另外用集合的方式管理套接字比较麻烦,每次在使用套接字进行网络操作前都需要调用select函数来判断套接字的状态,给cpu带来了额外的系统调用开销。