Socket
服务器
#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif
#define IP "127.0.0.1"
#define PORT 6080
int main(int argc, char** argv)
{
#ifdef WIN32 //配置win socket 版本
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
int ret = WSAStartup(wVersionRequested, &wsaData);
if (ret != 0)
{
printf("WSAStart up error\n");
return -1;
}
#endif
int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenfd == INVALID_SOCKET)
{
goto failed;
}
struct sockaddr_in sockaddr;
sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(PORT);
ret = bind(listenfd, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
if (ret != 0)
{
goto failed;
}
ret = listen(listenfd, 1);
while (1)
{
struct sockaddr_in c_address;
int address_len = sizeof(c_address);
printf("Waiting...");
int client_fd = accept(listenfd, (struct sockaddr*)&c_address, &address_len);
printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));
char buf[11];
memset(buf, 0, 11);
recv(client_fd, buf, 5, 0);
printf("recv: %s\n", buf);
send(client_fd, buf, 5, 0);
closesocket(client_fd);
}
failed:
if (listenfd != INVALID_SOCKET)
{
closesocket(listenfd);
}
#ifdef WIN32
WSACleanup();
#endif
return 0;
}
客户端
#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif
#define IP "127.0.0.1"
#define PORT 6080
int main(int argc, char** argv)
{
#ifdef WIN32 //配置win socket 版本
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
int ret = WSAStartup(wVersionRequested, &wsaData);
if (ret != 0)
{
printf("WSAStart up error\n");
return -1;
}
#endif
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == -1)
{
printf("create socket error!\n");
goto falise;
}
struct sockaddr_in sockaddr;
sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(PORT);
ret = connect(s, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
if (ret != 0)
{
goto falise;
}
char buff[11];
memset(buff, 0, 11);
send(s, "Hello", 5, 0);
recv(s, buff, 5, 0);
printf("recv: %s", buff);
falise:
if (s != INVALID_SOCKET)
{
closesocket(s);
s = INVALID_SOCKET;
}
#ifdef WIN32
WSACleanup();
#endif
return 0;
}
上述方法一次只能接入一个连接.下面使用select
select
定义一个句柄集合
fd_set set;
清空
FD_ZERO(&set);
将监听句柄加入到等待集合里面
FD_SET(listenfd, &set); // 将监听句柄加入到等待集合里面
设置select
ret = select(0, &set, NULL, NULL, NULL); //读事件
if (ret < 0)
{
printf("select error\n");
continue;
}
else if (ret == 0) //超时, 参数5决定, NULL表示阻塞
{
printf("select timeout\n");
continue;
}
判断句柄是否有新事件
//main外,demo用,接入的客户端会单独管理,这里存到数组以作为演示
static int client_fd[4096]; //保存连接句柄
static int socket_count = 0;
//--------------------------
if (FD_ISSET(listenfd, &set))//发送过来了新的连接请求数据
{
struct sockaddr_in c_address;
int address_len = sizeof(c_address);
printf("Waiting...");
int cs_fd= accept(listenfd, (struct sockaddr*)&c_address, &address_len);
printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));
client_fd[socket_count] = cs_fd;
socket_count++;
continue;
}
将新的客户端链接句柄加入到等待集合中
//将新的客户端链接句柄加入到等待集合中
for (int j = 0; j < socket_count; j++)
{
FD_SET(client_fd[j], &set);
}
历所有的客户端句柄, 判断连接句柄有没有读事件
//遍历所有的客户端句柄, 判断连接句柄有没有读事件
for (int j = 0; j < socket_count; j++)
{
if (client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set))//有读事件
{
char buff[1024];
ret = recv(client_fd[j], buff, sizeof(buff), 0);
if (ret <= 0) //客户端关闭
{
closesocket(client_fd[j]);
client_fd[j] = INVALID_SOCKET;
}
else
{
buff[ret] = 0;
printf("%s\n", buff);
send(client_fd[j], buff, ret, 0);
}
}
}
完整代码
#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif
#define IP "127.0.0.1"
#define PORT 6080
static int client_fd[4096]; //保存连接句柄
static int socket_count = 0;
int main(int argc, char** argv)
{
#ifdef WIN32 //配置win socket 版本
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
int ret = WSAStartup(wVersionRequested, &wsaData);
if (ret != 0)
{
printf("WSAStart up error\n");
return -1;
}
#endif
int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenfd == INVALID_SOCKET)
{
goto failed;
}
struct sockaddr_in sockaddr;
sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(PORT);
ret = bind(listenfd, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
if (ret != 0)
{
goto failed;
}
ret = listen(listenfd, 1);
fd_set set;
while (1)
{
FD_ZERO(&set);
FD_SET(listenfd, &set); // 将监听句柄加入到等待集合里面
//将新的客户端链接句柄加入到等待集合中
for (int j = 0; j < socket_count; j++)
{
if (client_fd[j] != INVALID_SOCKET)
FD_SET(client_fd[j], &set);
}
ret = select(0, &set, NULL, NULL, NULL);
if (ret < 0)
{
printf("select error\n");
continue;
}
else if (ret == 0) //超时, 参数5决定, NULL表示阻塞
{
printf("select timeout\n");
continue;
}
//判断句柄事件
if (FD_ISSET(listenfd, &set))//发送过来了新的连接请求数据
{
struct sockaddr_in c_address;
int address_len = sizeof(c_address);
int cs_fd = accept(listenfd, (struct sockaddr*)&c_address, &address_len);
printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));
client_fd[socket_count] = cs_fd;
socket_count++;
continue;
}
//遍历所有的客户端句柄, 判断连接句柄有没有读事件
for (int j = 0; j < socket_count; j++)
{
if (client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set))//有读事件
{
char buff[1024];
ret = recv(client_fd[j], buff, sizeof(buff), 0);
if (ret <= 0) //客户端关闭
{
closesocket(client_fd[j]);
client_fd[j] = INVALID_SOCKET;
}
else
{
buff[ret] = 0;
printf("%s\n", buff);
send(client_fd[j], buff, ret, 0);
}
}
}
}
failed:
if (listenfd != INVALID_SOCKET)
{
closesocket(listenfd);
}
#ifdef WIN32
WSACleanup();
#endif
return 0;
}
select缺点
1: 性能不好,每次有事件的时候都要遍历所有的句柄,然后查是哪个句柄的事件;
2: 能够管理的句柄的数目是有限制的, 2048个
3: socket的读和写仍然是同步的, 我们发送和接受数据的时候会等在网卡上面;
IOCP管理模型
1: IOCP: 是windows针对高性能服务器做的IO的管理模式,又叫完成端口;
2: IOCP的核心模式:
1>提交请求;
2>等待结果;
3>继续提交请求;
3: 监听:
1>提交一个监听请求,使用完成端口来等待这个请求到来;
2>请求来了后,处理,继续提交请求;
4: 读取数据:
1>提交一个读取数据的请求。
2>请求完成后,处理完后继续提交;
5: 发送数据的请求:
1>提交一个发送数据的请求;
2>请求完成后,继续处理;
IOCP
#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <mswsock.h>
#include <windows.h>
#pragma comment(lib, "WSOCK32.lib ")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "odbc32.lib")
#pragma comment(lib, "odbccp32.lib")
#define IP "127.0.0.1"
#define PORT 6080
enum
{
IOCP_ACCEPT = 0, //监听socket,接入请求
IOCP_RECV, //读请求
IOCP_WRITE //写请求
};
//接受数据时候最大的buf大小;
#define MAX_RECV_SIZE 8192
struct io_package
{
WSAOVERLAPPED overlapped;
int opt; //操作类型, 监听, 读, 写, IOCP_ACCEPT/IOCP_RECV/IOCP_WRITE
int accept_sock; //提交请求的句柄, accept的句柄或是读写请求的句柄
WSABUF wsabuffer; //配合读写数据时候用的buffer
char pkg[MAX_RECV_SIZE]; //定义了一个buff, 这个buff就是真正的内存
};
//投递一个用户请求
static void
post_accept(SOCKET l_sock)
{
//分配一个io_package 数据结构
struct io_package* pkg_data = (struct io_package*)malloc(sizeof(struct io_package));
memset(pkg_data, NULL, sizeof(struct io_package));
//初始化接受数据的buf
pkg_data->wsabuffer.buf = pkg_data->pkg;
pkg_data->wsabuffer.len = MAX_RECV_SIZE - 1;
pkg_data->opt = IOCP_ACCEPT; //请求类型
DWORD dwBytes = 0;
SOCKET client = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
int addr_size = (sizeof(struct sockaddr_in) + 16);
pkg_data->accept_sock = client;
AcceptEx(l_sock, client, pkg_data->wsabuffer.buf, 0,
addr_size, addr_size, &dwBytes, &pkg_data->overlapped);
}
static void
post_recv(SOCKET client_fd)
{
struct io_package* io_data = (struct io_package*)malloc(sizeof(struct io_package));
memset(io_data, NULL, sizeof(struct io_package));
io_data->opt = IOCP_RECV;
io_data->wsabuffer.buf = io_data->pkg;
io_data->wsabuffer.len = MAX_RECV_SIZE - 1;
io_data->accept_sock = client_fd;
DWORD dwRecv = 0;
DWORD dwFlags = 0;
//异步读数据
int ret = WSARecv(client_fd, &(io_data->wsabuffer),
1, &dwRecv, &dwFlags,
&(io_data->overlapped), NULL);
}
int main(int argc, char** argv)
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
int ret = WSAStartup(wVersionRequested, &wsaData);
if (ret != 0)
{
printf("WSAStart up error\n");
return -1;
}
SOCKET l_sock = INVALID_SOCKET;
//创建一个完成端口
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (iocp == INVALID_HANDLE_VALUE)
{
goto failed;
}
//创建socket
l_sock = socket(AF_INET, SOCK_STREAM, 0);
if (l_sock == INVALID_SOCKET)
{
goto failed;
}
struct sockaddr_in s_address;
memset(&s_address, 0, sizeof(s_address));
s_address.sin_family = AF_INET;
s_address.sin_addr.s_addr = inet_addr(IP);
s_address.sin_port = htons(PORT);
if (bind(l_sock, (struct sockaddr*)&s_address, sizeof(s_address)))
goto failed;
if (listen(l_sock, SOMAXCONN) != 0)
goto failed;
//IOCP管理l_sock
//参数三: 用户传入的自定义数据
CreateIoCompletionPort((HANDLE)l_sock, iocp, (DWORD)0, 0);
//发送一个监听用户进来的请求
post_accept(l_sock);
while (1)
{
DWORD dwTrans; //当有数据读到时, 读到的数据数量大小
DWORD udata; //用户数据
struct io_package* io_data;
// 通过完成端口, 来获得这个请求的结果
// 调用操作系统的API函数, 查看哪个请求完成了
// 如果没有状态完成, 那么任务会挂起
ret = GetQueuedCompletionStatus(iocp, &dwTrans,
&udata, (LPOVERLAPPED*)&io_data, WSA_INFINITE);
if (ret == 0)
{
if (io_data && io_data->opt == IOCP_RECV)
{
closesocket(io_data->accept_sock);
//关闭socket
free(io_data);
continue;
}
else if (io_data->opt == IOCP_ACCEPT)
{
free(io_data);
post_accept(l_sock);
}
}
if (dwTrans == 0 && io_data->opt == IOCP_RECV) //读请求
{
closesocket(io_data->accept_sock);
//关闭socket
free(io_data);
continue;
}
switch (io_data->opt)
{
case IOCP_ACCEPT: // 接入一个socket
{
int client_fd = io_data->accept_sock; //接入的client_socket
int addr_size = (sizeof(struct sockaddr_in) + 16);
struct sockaddr_in* l_addr = NULL;
int l_len = sizeof(struct sockaddr_in);
struct sockaddr_in* r_addr = NULL;
int r_len = sizeof(struct sockaddr_in);
//可以拿到端口和地址
GetAcceptExSockaddrs(io_data->wsabuffer.buf,
0, addr_size, addr_size,
(struct sockaddr**)&l_addr, &l_len,
(struct sockaddr**)&r_addr, &r_len);
// 将新进来的client_fd, 加入完成端口, 来帮助管理完成的请求
// 第三个参数是用户自定义数据, 可以携带自己的数据结构
CreateIoCompletionPort((HANDLE)client_fd, iocp, (DWORD)client_fd, 0);
//投递一个读的请求;
post_recv(client_fd);
free(io_data);
//重新投递一个接受请求;
post_accept(l_sock);
}
break;
case IOCP_RECV:
{
// dwTrans : 读到的数据大小
// socket : io_data->accept_sock
// Buf : io_data->wsabuffer.buf/pkg
io_data->pkg[dwTrans] = 0;
printf("IOCP recv %d : %s\n", dwTrans, io_data->pkg);
//直接投递下一个请求
DWORD dwRecv = 0;
DWORD dwFlags = 0;
int ret = WSARecv(io_data->accept_sock, &(io_data->wsabuffer),
1, &dwRecv, &dwFlags,
&(io_data->overlapped), NULL);
}
break;
}
}
failed:
if (l_sock)
{
closesocket(l_sock);
}
if (iocp != INVALID_HANDLE_VALUE)
{
CloseHandle(iocp); //释放完成端口
}
WSACleanup();
return 0;
}