需求:
使用io复用(select) 实现一个服务器与客户端, 用于模拟用户登录,登出,以及广播消息;
下面代码并没有解决粘包烧包 和解包的问题 , 具体解决方案: tcp 粘包 解包 少包 两种解决方式
以下为win32平台, unix平台稍作修改即可运行;
原本想用iocp或者eventselect 实现更为简单,但人说非得要用select,好把.
下面代码的发送消息和接受消息都使用了结构, 分2个部分一个消息头一个消息体
用于登录登出,用户加入的头文件,定义消息结构的:
trans.h
enum CMD{
CMD_LOGIN, //登录 由客户端发送给服务端
CMD_LOGOUT, //登出
CMD_LOGIN_RESULT,//登录结果,由服务端发送给客户端
CMD_LOGOUT_RESULT,//登出结果
CMD_USER_JOIN, //由服务端群发给客户端
CMD_ERROR
};
//消息头
typedef struct _DataHeader{
short dataLen; //用于定义消息体长度
short cmd; //对应上面的命令
} DataHeader, *LPDataHeader;
//登录
typedef struct _Login {
DataHeader header; //消息头
char uname[32]; //用户名
char passwd[32]; //密码
} Login, *LPLogin;
//登录结果
typedef struct _LoginResult{
DataHeader header;
short result; //一个示意
}LoginResult, *LPLoginResult;
//登出
typedef struct _Logout{
DataHeader header;
char uname[32]; //一个示意
}Logout, *LPLogout;
//登出结果
typedef struct _LogoutResult{
DataHeader header;
short result; //示意
}LogoutResult, *LPLogoutResult;
//群发命令
typedef struct _UserJoin{
DataHeader header;
int sock; //占位符,没鸟用
}UserJoin, *LPUserJoin;
用于打印错误消息的, 好像没用到,忘了
utils.h
#include <stdlib.h>
#include <TCHAR.h>
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#include <Ws2ipdef.h>
#include <locale.h>
#pragma comment(lib, "ws2_32.lib")
const SIZE_T ERR_MSG_SIZE = 1 << 13;
const unsigned short PORT = 9988;
const int BACKLOG = 20;
void print_error(DWORD err){
//使用当前平台的字符集
_tsetlocale(LC_ALL, L"");
//创建一块内存一直存放错误信息
static HANDLE g_heap = HeapCreate(0, ERR_MSG_SIZE, 0);
static TCHAR *buf = (TCHAR*)HeapAlloc(g_heap, 0, ERR_MSG_SIZE);
//使用当前平台的语言
DWORD syslocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
DWORD ret = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, err, syslocale, buf, ERR_MSG_SIZE, NULL);
if (!ret){
//如果上面没找到错误,去网络错误中查找
static HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL, DONT_RESOLVE_DLL_REFERENCES);
if (hDll){
//如果在dll中查找,FORMAT_MESSAGE_FROM_HMODULE 添加上去, 第2个参数填写句柄
ret = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
hDll, err, syslocale, buf, ERR_MSG_SIZE, NULL);
}
}
if (ret && buf){
buf[ret] = 0;
_tprintf(TEXT("buf:%s\n"), buf);
}
else{
_tprintf(TEXT("unknow error : %ld\n"), err);
}
}
正式代码:
serv.cpp
#include "stdafx.h"
#include "../trans.h"
#define _UNICODE
#define UNICODE
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUFSIZE 1024
#define PORT 9988
#define BACKLOG 10
//没用到
int setNonBlockMode(SOCKET sock, u_long bEnable)
{
return ioctlsocket(sock, FIONBIO, &bEnable);
}
//处理每个客户端传送的数据,maxi 最大索引,cliens 存放socket, readset 有响应的socket
void process_clients(int maxi, SOCKET * clients, FD_SET *readset, FD_SET *allreadset, int *nready);
//广播消息 , sock_arr_index 不想给这个socket发送数据的索引
void boardcast_msg(int cmd, int * sock_arr_index, int maxi = -1)