#include "main.h"
/*1.打开网络库
* 2.校验网络库版本
* 3.创建SOCKET
* 4.绑定IP地址和端口
* 5.开始监听
* 6.创建客户端socket/接受链接
* 7.与客户端收发消息
* 8.(6.7)两步的函数accept,send,recv 有堵塞,可以用select解决,这种函数可以处理小型网络
*/
int create(const char* IpAdress)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* 使用Windef.h中声明的MAKEWORD(低字节、高字节)宏 */
wVersionRequested = MAKEWORD(2, 2);
/*启用网络链接库,调用的封装库命令*/
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return -1;
}
/*确认WinSock DLL支持2.2*/
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
//清理网络库
WSACleanup();
return -1;
}
//创建套接字。 创建网络类型 tcp或者upd
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socketServer)
{
string ret = to_string( WSAGetLastError());
MessageBoxA(0, ret.c_str(),"error_socket",0);
//清理网络库
WSACleanup();
return -1;
}
//设置sockaddr结构
sockaddr_in saServer;
saServer.sin_family = AF_INET;
saServer.sin_addr.s_addr = INADDR_ANY;
saServer.sin_port = htons(9999);
// 绑定本机(服务器)IP和端口
//sockaddr结构中的信息
if (SOCKET_ERROR == bind(socketServer, (SOCKADDR*)&saServer, sizeof(saServer)))
{
string ret = to_string(WSAGetLastError());
MessageBoxA(0, ret.c_str(), "error_bind", 0);
//释放stocket
closesocket(socketServer);
//清理网络库
WSACleanup();
return -1;
}
/*监听本机(服务器)的套接字*/
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
string ret = to_string(WSAGetLastError());
MessageBoxA(0, ret.c_str(), "error_listen", 0);
//释放stocket
closesocket(socketServer);
//清理网络库
WSACleanup();
return -1;
}
fd_set allSockets;
//清空
FD_ZERO(&allSockets);
//将服务器加入链表
FD_SET(socketServer,&allSockets);
while (true)
{
//中间变量
fd_set read_allSockets = allSockets;//可读
fd_set write_allSockets = allSockets;//可写
fd_set except_allSockets = allSockets;//错误
timeval st;
st.tv_sec = 3;
st.tv_usec = 0;
/* select解决阻塞问题accept recv send函数,*/
int select_ret = select(0, &read_allSockets, &write_allSockets,&except_allSockets, &st);
if (0 == select_ret)
{
//select 无响应
continue;
}
else if (select_ret > 0)
{
有响应
/*处理错误*/
for (u_int loop = 0; loop < except_allSockets.fd_count; loop++)
{
char error[100] = {0};
int len = sizeof(error) -1;
if (SOCKET_ERROR == getsockopt(except_allSockets.fd_array[loop], SOL_SOCKET, SO_ERROR, error, &len))
{
printf("无法得到错误信息");
}
printf("%s\n", error);
}
//for (u_int loop = 0; loop < write_allSockets.fd_count; loop++)
//{
// //printf("write:%d \n", write_allSockets.fd_array[loop]);
// char send_buff[] = "服务器:write链接成功!";
// send(write_allSockets.fd_array[loop], send_buff, strlen(send_buff), 0);
//}
for (u_int loop = 0; loop < read_allSockets.fd_count; loop++)
{
/*本机(服务器)收到accept请求链接消息*/
if (read_allSockets.fd_array[loop] == socketServer)
{
sockaddr_in clientMsg;
int len = sizeof(clientMsg);
/*保存监听到的客户端数据,这个函数是阻塞的,没客户端链接就会死等,故放进select调用解决*/
SOCKET socketClient = accept(socketServer, (SOCKADDR*)&clientMsg, &len);
if (INVALID_SOCKET == socketClient)
{
continue;
}
/*将取到的客户端socket放进allSockets链表*/
FD_SET(socketClient,&allSockets);
printf("%d.%d.%d.%d:%d \n", (
SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b1,
(SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b2,
(SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b3,
(SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b4,
(SOCKADDR*)clientMsg.sin_port);
/*参数二还要打包ip 链路层数据等,所以要预留100字节,故参数2最大1400*/
char send_buff[] = "服务器:链接成功!";
if (SOCKET_ERROR == send(socketClient, send_buff, strlen(send_buff), 0))
{
string ret = to_string(WSAGetLastError());
MessageBoxA(0, ret.c_str(), "error_send", 0);
}
}
else /*客户端和服务器通信消息*/
{
/*将客户端数据复制到自己的缓存区
* 缓冲区大小最大1500,为了保留/0,故大小-1
* 如果未发生错误, recv 将返回收到的字节数, buf 参数指向的缓冲区将包含接收的此数据。 如果连接已正常关闭,则返回值为零。
否则,将返回SOCKET_ERROR值,并且可以通过调用 WSAGetLastError 来检索特定的错误代码
*/
char buff[1024] = { 0 };
int recv_ret = recv(read_allSockets.fd_array[loop], buff, 1024, 0);
if (0 == recv_ret) /*客户端正常下线*/
{
//在链表中释放掉这个socket
FD_CLR(read_allSockets.fd_array[loop], &allSockets);
//释放stocket
closesocket(read_allSockets.fd_array[loop]);
MessageBoxA(0, "网络中断,客户端已下线", "error_recv", 0);
}
else if (SOCKET_ERROR == recv_ret)
{
int error_ret = WSAGetLastError();
if (error_ret == 10054) //客户端强制下线了
{
//在链表中释放掉这个socket
FD_CLR(read_allSockets.fd_array[loop], &allSockets);
//释放stocket
closesocket(read_allSockets.fd_array[loop]);
MessageBoxA(0, "网络中断,客户端已强制下线", "error_recv", 0);
continue;
}
string ret = to_string(error_ret);
MessageBoxA(0, ret.c_str(), "error_recv", 0);
}
else
{
/*接收到了消息*/
printf("%s\n", buff);
}
}
}
}
else if (SOCKET_ERROR == select_ret )
{
//有错误
string ret = to_string(WSAGetLastError());
MessageBoxA(0, ret.c_str(), "error_select_ret", 0);
}
}
system("pause");
//释放stocket
for (int loop = 0; loop < allSockets.fd_count; loop++)
{
closesocket(allSockets.fd_array[loop]);
}
//清理网络库
WSACleanup();
}
int main()
{
create("127.0.0.1");
return 0;
}
select是将accept和recv的阻塞变成半阻塞,(接收一个客户端消息的时候无法处理其它客户端消息),本质是把客户端socket放进数组里,然后遍历数组查询哪个socket有消息