Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

SOCKET编程

Socket套接字介绍

网络应用程序是由通信进程对组成,每对互相通信的应用程序进程互相发送报文,他们之间的通信必须通过下面的网络来进行。为了将应用程序和底层的网络通信协议屏蔽开来,采用套接字(Socket)这样一个抽象概念来作为应用程序和底层网络之间的应用程序编程接口(API)。
因为网络应用程序是进程之间的通信,为了唯一的标识通信对等方的通信进程,套接字必须包含2种信息:(1) 通信对等方的网络地址。(2) 通信对等方的进程号,通常叫端口号。
就像Unix操作系统下有一套实现TCP/IP网络通信协议的开发接口:BSD Sockets一样,在Windows操作系统下,也提供了一套网络通信协议的开发接口,称为Windows Sockets或简称Winsock。
Winsock 是通过动态链接库的方式提供给软件开发者,而且从Windows 95以后已经被集成到了Windows操作系统中。
Winsock主要经历了2个版本:Winsock 1.1和Winsock 2.0。Winsock 2.0是Winsock 1.1的扩展,它向下完全兼容。
Winsock同时包括了16位和32位的编程接口,16位的Windows Socket 2应用程序使用的动态链接库是WINSOCK.DLL,而32位的Windows Socket应用程序使用WSOCK32.DLL(Winsock 1.1版)和WS2_32.DLL(Winsock 2.0版)。另外,使用Winsock API时要包含头文件winsock.h(Winsock 1.1版)或winsock2.h(Winsock 2.0版)。

Socket套接字编程原理

Socket的2种类型

Socket是一个抽象概念,代表了通信双方的端点(Endpoint),通信双方通过Socket发送或接收数据。
在Winsock里,用数据类型SOCKET作为Windows Sockets对象的句柄,就好像一个窗口的句柄HWND、一个打开的文件的文件指针一样。下面我们会看到,在Winsock API的许多函数里,都会用到SOCKET类型的参数。
Socket有2种类型:

  • 流类型(Stream Sockets)。
    流式套接字提供了一种可靠的、面向连接的数据传输方法,使用传输控制协议TCP。
  • 数据报类型(Datagram Sockets)。
    数据报套接字提供了一种不可靠的、非连接的数据包传输方式,使用用户数据报协议UDP。

Socket I/O的2种模式

一个SOCKET句柄可以看成代表了一个I/O设备。在Windows Sockets里,有2种I/O模式:

  • 阻塞式I/O(blocking I/O)
    在阻塞方式下,收发数据的函数在调用后一直要到传送完毕或者出错才能完成,在阻塞期间,除了等待网络操作的完成不能进行任何操作。阻塞式I/O是一个Winsock API函数的缺省行为。
  • 非阻塞式I/O(non-blocking I/O)
    对于非阻塞方式,Winsock API函数被调用后立即返回;当网络操作完成后,由Winsock给应用程序发送消息(Socket Notifications)通知操作完成,这时应用程序可以根据发送的消息中的参数对消息做出响应。Winsock提供了2种异步接受数据的方法:一种方法是使用BSD类型的函数select(),另外一种方法是使用Winsock提供的专用函数WSAAsyncSelect()。

使用数据报套接字

首先,客户机和服务器都要创建一个数据报套接字。接着,服务器调用bind()函数给套接字分配一个公认的端口。一旦服务器将公认的端口分配给了套接字,客户机和服务器都能使用sendto()和revfron()来传递数据报。通信完毕调用closesocket()来关闭套接字。流程如图2.1所示:

使用流式套接字

由于流式套接字使用的是基于连接的协议,所以你必须首先建立连接,而后才能从数据流中读出数据,而不是从一个数据报或一个记录中读出数据,其流程如图2.2所示。

套接字部分库函数列表

WSAStartup()

函数原型
int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData );

参数
wVersionRequested
    [in] 表示欲使用的Windows Sockets API版本;这是个WORD类型的整数,高字节定义的是次版本号,低字节定义的是主版本号。
lpWSAData
    [in] 指向WSAData资料的指针。WSAData是结构数据类型,描述了关于Windows Sockecs底层实现的相关信息。
    
返回值
函数执行成功返回0,失败则返回如下错误代码:
    WSASYSNOTREADY:         底层网络子系统没有准备好。
    WSAVERNOTSUPPORTED:Winsock版本信息号不支持。WSAEINPROGRESS:         阻塞式Winsock1.1存在于进程中。
    WSAEPROCLIM:             已经达到Winsock使用量的上限。
    WSAEFAULT:             lpWSAData不是一个有效的指针。
    
函数功能
这个函数是应用程序应该第一个调用的Winsock API函数,以完成一系列初始化的工作。

相关数据结构
WSADATA的定义如下:
    typedef struct WSAData {
        WORD    wVersion;
        WORD     wHighVersion;
        char        szDescription[WSADESCRIPTION_LEN+1];
        char     szSystemStatus[WSASYS_STATUS_LEN+1];
        unsigned short        iMaxSockets;
        unsigned short        iMaxUdpDg;
        char FAR         lpVendorInfo;
} WSADATA, FAR
LPWSADATA;
其中,各结构成员的含义为:
wVersion
    应用程序应该使用的Winsock版本号。
wHighVersion
    DLL所支持的最高版本号。通常应该等于wVersion。
szDescription
    以0结尾的ASCII字符串,关于Winsock底层实现的描述信息。
szSystemStatus
    以0结尾的ASCII字符串,关于Winsock底层状态或者配置信息。
iMaxSockets
    一个进程最多可使用的套接字数,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。
iMaxUdpDg
    最大的UDP报文大小,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。对于Winsock 2.0,应该使用getsockopt函数取得SO_MAX_MSG_SIZE。
lpVendorInfo
    Winsock开发厂商信息,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。对于Winsock 2.0,应该使用getsockopt函数取得PVD_CONFIG。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <winsock.h>
//对于Winsock 2, include <winsock2.h>

WSADATA wsaData;
int nRc = WSAStartup(0x0101, & wsaData);
if(nRc)
{
//Winsock初始化错误
&nbsp;&nbsp;&nbsp;&nbsp;return;
}
if(wsaData.wVersion != 0x0101)
{
//版本支持不够
//报告错误给用户,清除Winsock,返回
&nbsp;&nbsp;&nbsp;&nbsp;WSACleanup();
&nbsp;&nbsp;&nbsp;&nbsp;return;
}

socket()

函数原型
SOCKET socket(int af, int type, int protocol);

参数
af
    [in] 指定地址族(address family),一般填AF_INET(使用Internet地址)。
type
    [in] 指定SOCKET的类型:SOCK_STREAM(流类型),SOCK_DGRAM(数据报类型)。
protocol
    [in] 指定af参数指定的地址族所使用的具体一个协议。建议设为0,那么它就会根据地址格式和SOCKET类型,自动为你选择一个合适的协议。另外2个常用的值为:IPPROTO_UDP和IPPROTO_TCP。
返回值
函数执行成功返回一个新的SOCKET,失败则返回INVALID_SOCKET。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
所有的通信在建立之前都要创建一个SOCKET。

示例代码:

1
2
3
4
5
6
&nbsp;&nbsp;&nbsp;&nbsp;//创建数据报socket
&nbsp;&nbsp;&nbsp;&nbsp;SCOKET udpSock = socket(AF_INET,
&nbsp;&nbsp;&nbsp;&nbsp;SOCK_DGRAM, IPPROTO_UDP);
&nbsp;&nbsp;&nbsp;&nbsp;//创建流socket
&nbsp;&nbsp;&nbsp;&nbsp;SCOKET tcpSock = socket(AF_INET,
&nbsp;&nbsp;&nbsp;&nbsp;SOCK_STREAM, IPPROTO_TCP);

bind()

函数原型
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);

参数
s
    [in] 一个需要绑定的SOCKET,例如用socket函数创建的SOCKET。
name
    [in] 指向描述通信对象地址信息的结构体sockaddr的指针。在该结构体中可以指定地址族(一般为 AF_INET)、主机的地址和端口。通常把主机地址指定为INADDR_ANY(一个主机可能有多个网卡)。
namelen
    [in] name指针指向的结构体的长度。
返回值
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
成功地创建了一个SOCKET后,用bind函数将SOCKET和主机地址绑定。

相关数据结构
struct sockaddr {
    u_short sa_family;
    char sa_data[14];
};
sa_family
地址族,比如AF_INET,2个字节大小。
sa_data
用来存放地址和端口,14个字节大小。
sockaddr结构是一个通用的结构(因为Winsock支持的协议族不只是TCP/IP)。对TCP/IP协议,用如下结构来定义地址和端口。
struct sockaddr_in {
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};
sin_family
    地址族,设为AF_INET。
sin_port
    端口号。如果端口号为0,Winsock会自动为应用程序分配一个值在1024-5000间的一个端口号,所以客户端一般把sin_port设为0。
sin_addr
    为in_addr结构类型,用来指定IP地址。通常把主机地址指定为INADDR_ANY(一个主机可能有多个网卡)。结构in_addr下面介绍。
sin_zero
    8字节的数组,值全为0。这个8个字节用来填充结构sockaddr_in,使其大小等于结构sockaddr(16字节)。
结构in_addr用来指定IP地址,其定义为:
struct in_addr {
 union {
    struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
    struct { u_short s_w1,s_w2; } S_un_w;
     u_long S_addr;
 } S_un;
};
对于IP地址10.14.25.90,sockaddr_in结构中的sin_addr可以这样赋值:
sin_addr. S_un .S_un_b. s_b1 = 10;
sin_addr. S_un .S_un_b. s_b2 = 14;
sin_addr. S_un .S_un_b. s_b3 = 25;
sin_addr. S_un .S_un_b. s_b4 = 90;
或者
sin_addr. S_un . S_un_w. s_w1 = (14<<8)|10;
sin_addr. S_un . S_un_w. s_w2 = (90<<8)|25;
或者
sin_addr. S_un . S_addr = (90<<24)|(25<<16)|(14<<8)|10;
或者
sin_addr. S_un . S_addr = inet_addr(“10.14.25.90”);
这里的inet_addr函数可以将字符串形式的IP地址转换为unsigned long形式的值。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SOCKET sServSock;
sockaddr_in addr;

//创建socket
sServSock = socket(AF_INET, SOCK_STREAM, 0);

addr.sin_family = AF_INET;

//htons和htonl函数把主机字节顺序转换为网络字节顺序,分别用于//短整型和长整型数据
addr.sin_port = htons(5050);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

// LPSOCKADDR类型转换是必须的
int nRc = bind(sServSock, (LPSOCKADDR)&addr, sizeof(addr) );

listen()

函数原型
int listen (SOCKET s, int backlog);

参数
s
    [in] 一个已经绑定但未连接的SOCKET。
backlog
    [in] 等待连接的队列的长度,可取SOMAXCONN。如果某个客户程序要求连接的时候,服务器已经与其他客户程序连接,则后来的连接请求会放在等待队列中,等待服务器空闲时再与之连接。当等待队列达到最大长度(backlog指定的值)时,再来的连接请求都将被拒绝。
返回值
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
对于服务器的程序,当申请到SOCKET,并将通信对象指定为INADDR_ANY之后,就应该等待一个客户机的程序来要求连接,listen函数就是把一个SOCKET设置为这个状态。

accept()

函数原型
SOCKET accept (SOCKET s, struct sockaddr FAR* addr,
int FAR* addrlen );

参数
s
    [in] 一个已经处于listen状态的SOCKET。
addr
    [out] 指向sockaddr结构体的指针,里面包含了客户端的地址和端口。
addrlen
    [out] int型指针,指向的内容为addr指针指向的结构体的长度。
返回值
如果函数执行成功,会建立并返回一个新的SOCKET来与对方通信,新建的SOCKET与原来的SOCKET(函数的第一个参数s)有相同的特性,包括端口号。原来的SOCKET继续等待其他的连接请求。而新生成的SOCKET才是与客户端通信的实际SOCKET。所以一般将参数中的SOCKET称作“监听”SOCKET,它只负责接受连接,不负责通话;而对于函数返回的SOCKET,把它称作“会话”SOCKET,它负责与客户端通话。
如果失败则返回INVALID_SOCKET。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
accept函数从等待连接的队列中取第一个连接请求,并且创建一个新的SOCKET来负责与客户端会话。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
SOCKET sServSock;	//服务器监听socket
sockaddr_in addr;
int nSockErr;
int nNumConns = 0; //当前请求连接数
SOCKET sConns[5]; //会话SOCKET数组
sockaddr ConnAddrs[5];//请求连接的客户端地址
int nAddrLen;

//创建服务器监听socket
sServSock = socket(AF_INET, SOCK_STREAM, 0);

addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

if( bind(sServSock,(LPSOCKADDR)&addr,sizeof(addr)) ==
SOCKET_ERROR )
{
nSockErr = WSAGetLastError();
//绑定出错处理
}
//监听客户端请求连接
if( listen(sServSock, 2) == SOCKET_ERROR)
{
nSockErr = WSAGetLastError();
//出错处理
}

while( nNumConns < 5){
//每当收到客户端连接请求,创建新的会话SOCKET,保存在/ //sConns数组中
//客户端地址保存在ConnAddrs数组中
sConns[nNumConns] = accept(sServSock,
ConnAddrs[nNumConns], &nAddrLen);
if(sConns[nNumConns] == INVALID_SOCKET)
{
nSockErr = WSAGetLastError();
//创建会话SOCKET出错处理
}
else
{
//创建会话SOCKET成功,启动新的线程与客户端会话
StartNewHandlerThread(sConns[nNumConns]);
//当前请求连接数+1
nNumConns ++;
}
}

connect()

函数原型
int connect (SOCKET s, const struct sockaddr FAR* name,
int namelen );

参数
s
    [in] 一个未连接SOCKET,一般是由socket函数建立的。
name
    [in] 同bind函数。
namelen
    [in] 同bind函数。
返回值
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
向对方主动提出连接请求。

send()

函数原型
int send (SOCKET s, char buf, int len ,int flags);
【参数】
s
    [in] 一个已经连接的SOCKET。
buf
    [in] 指向要传输的数据的缓冲区的指针。
len
    [in] buf的长度。
flags
    [in]指定函数调用的方式。一般取0。
返回值
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
*函数功能

通过已经连接的SOCKET发送数据。

recv()

函数原型
int recv (SOCKET s, char * buf, int len ,int flags);

参数
s
    [in] 一个已经连接的SOCKET。
buf
    [out] 指向接收数据的缓冲区的指针。
len
    [in] buf的长度。
flags
    [in]指定函数调用的方式。一般取0。
返回值
函数执行成功返回接收到数据的字节数。如果失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
通过已经连接的SOCKET接收数据。当读到的数据字节少于规定接受的数目(len)时,就把数据全部接收,并返回实际接收到的字节数;当读到的数据多于规定的值时,在流方式下剩余的数据由下个recv读出,在数据报方式下多余的数据被丢弃。

sendto()

函数原型
int sendto (SOCKET s, char * buf, int len ,int flags,
struct sockaddr_in * to, int tolen);

参数
s
    [in] 一个SOCKET(可能已连接)。
buf
    [in] 指向要传输的数据的缓冲区的指针。
len
    [in] buf的长度。
flags
    [in] 指定函数调用的方式。一般取0。
to
    [in] 指向目标地址结构体的指针。
tolen
    [in] 目标地址结构体的长度。
返回值
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
该函数一般用于通过无连接的SOCKET发送数据报文,报文的接受者由to参数指定。

recvfrom()

函数原型
int recvfrom (SOCKET s, char * buf, int len ,int flags,
struct sockaddr_in * from, int * fromlen);
参数
s
    [in] 一个已经绑定的SOCKET。
buf
    [out] 指向接收数据的缓冲区的指针。
len
    [in] buf的长度。
flags
    [in] 指定函数调用的方式。一般取0。
from
    [out] 指向源地址结构体的指针。
fromlen
    [in/out] 源地址结构体的长度。
返回值
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
该函数一般用于通过无连接的SOCKET接收数据报文,报文的发送者由from参数指定。

closesocket()

函数原型
int closesocket (SOCKET s,);

参数
s
    [in] 要关闭的SOCKET。
返回值
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。
函数功能
关闭指定的SOCKET。

Windows Socket 2的扩展特性

Winsock 2.0简介

Winsock 1.1原先设计的时候把API限定在TCP/IP的范畴里,它不象Berkerly模型那样支持多种协议。而Winsock 2.0正规化了一些其它的协议(如ATM、IPX/SPX和DECNet协议)的API。
Winsock 2.0之所以能支持多种协议,是因为Winsock 2.0在Windows Sockets DLL和底层协议栈之间定义了一个SPI(Service Provider Interface)接口,这样,通过一个Windows Sockets DLL可以同时访问底层不同厂商的协议栈。
Winsock 2.0不仅允许多种协议栈的并存,而且从理论上讲,它还允许创造一个与网络协议无关的应用程序。Winsock 2.0可以基于服务的需要透明地选择协议,应用程序可以适用于不同的网络名和网络地址。
Winsock 2.0还扩展了它的API函数集,当然Winsock 2.0是向下兼容的,可以把Winsock 1.1的代码原封不动地用在Winsock 2.0中。

Winsock 2.0新特性

下面列出了一些Winsock 2.0的重要新特性:

  • 多重协议支持:SPI接口使得新的协议可以被支持。
  • 传输协议独立:根据服务提供不同的协议。
  • 多重命名空间:根据需要的服务和解析的主机名选择协议。
  • 分散和聚集: 从多个缓冲区接受和发送数据。
  • 重叠I/O和事件对象:增强吞吐量。
  • 服务质量(Qos):协商和跟踪网络带宽。
  • 条件接受:可以选择性地决定是否接受连接。
  • Socket共享:多个进程可以共享一个SOKCKET句柄。

Winsock 2.0新增函数

下面列出了一些Winsock 2.0的重要新增函数:

  • WSAAccept():accept()函数的扩展版本,支持条件接受和套接字分组。
  • WASCloseEvent():释放一个时间对象。
  • WSAConnect():connect()函数的扩展版本,支持连接数据交换和Qos规范。
  • WSACreatEvent():创建一个事件对象。
  • WSADuplicateSocket():为一个共享套接字创建一个新的套接字。
  • WSAEnumNetworkEvents():检查是否有网络事件发生。
  • WSAEnumProtocols():得到每个可用的协议的信息。
  • WSAEventSelect():把一个网络事件和一个事件对象连接。
  • WSAGetOverlappedResu():得到重叠操作的完成状态。
  • WSAHtonl():htonl()函数的扩展版本。
  • WSAHtons():htons()函数的扩展版本。
  • WSAIoctl():ioctlsocket()函数允许重叠操作的扩展版本。
  • WSANtohl():ntohl()函数的扩展版本。
  • WSANtohs():ntohs()函数的扩展版本。
  • WSARecv():recv()的扩展版本,支持分散/聚集/重叠I/O。
  • WSARecvDisconnect():终止套接字的接受操作。
  • WSARecvFrom():recvfrom()的扩展版本,支持分散/聚集/重叠I/O。
  • WSAResetEvent():重新初始化事件对象。
  • WSASend():send()的扩展版本,支持分散/聚集/重叠I/O。
  • WSARecvDisconnect():终止套接字的接受操作。
  • WSASendDisconnect():终止套接字的发送操作。
  • WSASendTo():sendto()的扩展版本,支持分散/聚集/重叠I/O。
  • WSASetEvent():设置事件对象。
  • WSASocket():socket()函数的扩展版本。它以一个PROTOCOL_INFO结构作为输入参数,并且允许创建重叠套接字,还允许创建套接字组。
  • WSAWaitForMultipleEvents():阻塞多个事件对象。
    关于这些函数的具体细节,请查阅MSDN。

基于SOCKET实现的小型web服务器

项目划分为启动,配置,服务器收发3个模块。

启动服务器

start部分用于启动服务器,并在启动后可关闭服务器。

start.cpp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "server.h"
#include <iostream>

//关闭服务器线程
int closeServer(Server *server) {
cout << "If you want to close server, please input exit" << endl;
std::string Exit;
cin >> Exit;
while (1) {
if (Exit == "exit")
break;
}
//到这里意味着要关闭服务器
(*server).~Server();
exit(0);
}

int main() {
Server server;
if (server.WinsockStartup() == -1) return -1;
if (server.ServerStartup() == -1 ) return -2;
if (server.ListenStartup() == -1) return -3;
thread closeThread(closeServer, &server);
closeThread.detach();
if (server.ServerWork() == -1) return -4;
}

服务器收发

server部分实现了服务器绑定IP端口,监听并接收HTTP请求,并建立连接,传送文件的功能。

server.h代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <winsock2.h>
#include <list>
#include <string>
#include <map>
#include<mutex>

using namespace std;

class Server
{
private:
SOCKET ServerSocket; //服务器socket
sockaddr_in ServerAddr; //服务器端IP地址
char* RecvBuf; //接受缓冲区
char* SendBuf; //发送缓冲区
int SocketNums;//socket总数

fd_set rfds; //用于检查socket是否有数据到来的的文件描述符
fd_set wfds; //用于检查socket是否可以发送的文件描述符

mutex lockRecBuf; //写接收缓冲区信号灯
mutex lockRecBuf2; //读接收缓冲区信号灯
mutex sendHeader; //发送报文头部信号灯
mutex sendMessage; //发送报文主体信号灯
mutex ClosedList; //失效队列信号灯
mutex ClosedList2; //失效队列信号灯

list<SOCKET>* threadsessions; //线程队列
list<SOCKET>* SocketList; //当前的会话socket队列
list<SOCKET>* ClosedSocketList; //所有已失效的会话socket队列

public:

Server(void);
virtual ~Server(void);

int closeServer(); //关闭服务器
void RecvMessage(SOCKET socket); //从SOCKET接受消息
void SendMessage(SOCKET socket, string msg); //向SOCKET发送消息
bool endWith(const std::string& fullStr, const std::string& endStr); //判断是否以特定格式结尾
int WinsockStartup(); //初始化Winsock
int ServerStartup(); //初始化Server,包括创建SOCKET,绑定到IP和PORT
int ListenStartup(); //开始监听客户端请求
int ServerWork(); //收客户机请求 连接并生成会话socket
void AddSocket(SOCKET socket); //将新的会话SOCKET加入队列
void AddClosedSocket(SOCKET socket); //将失效的会话SOCKET加入失效队列
void RemoveClosedSocket(SOCKET socket); //将失效的SOCKET从会话SOCKET队列删除
void RemoveClosedSocket(); //将所有失效的SOCKET从会话SOCKET队列删除
};

server.cpp代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
#include <iostream>
#include <string>
#include <fstream>
#include <streambuf>
#include "server.h"
#include "config.h"
#include <winsock2.h>
#include <thread>
#include <regex>
#include<mutex>
#pragma comment(lib, "Ws2_32.lib")

using namespace std;

Server::Server(void)
{
this->RecvBuf = new char[Config::BufferLength]; //初始化接受缓冲区
memset(this->RecvBuf, '\0', Config::BufferLength); // 并设置为0
this->SendBuf = new char[Config::BufferLength]; //初始化发送缓冲区
memset(this->SendBuf, '\0', Config::BufferLength); // 并设置为0

this->SocketList = new list<SOCKET>(); //初始化有关List和Map
this->ClosedSocketList = new list<SOCKET>();
}

Server::~Server(void)
{
if (this->RecvBuf != NULL) {
//释放接收缓冲区
free(this->RecvBuf);
this->RecvBuf = NULL;
cout <<"RecvBuf has been free!" << endl;
}

if (this->SendBuf != NULL) {
//释放接收缓冲区
free(this->SendBuf);
this->SendBuf = NULL;
cout << "SendBuf has been free!" << endl;
}

//关闭服务器端socket
if (this->ServerSocket != NULL) {
closesocket(this->ServerSocket);
this->ServerSocket = NULL;
cout << "ServerSocket has been closed!" << endl;
}

//关闭所有会话socket并释放会话队列
if (this->SocketList != NULL) {
for (list<SOCKET>::iterator it = this->SocketList->begin(); it != this->SocketList->end(); it++)
closesocket(*it); //关闭会话
delete this->SocketList; //释放队列
this->SocketList = NULL;
cout << "SocketList has been deleted!" << endl;
}

//释放失效会话队列
if (this->ClosedSocketList != NULL) {
for (list<SOCKET>::iterator it = this->ClosedSocketList->begin(); it != this->ClosedSocketList->end(); it++)
closesocket(*it);
delete this->ClosedSocketList;
this->ClosedSocketList = NULL;
cout << "ClosedSocketList has been deleted!" << endl;
}

WSACleanup(); //清理winsock 运行环境
}

//判断是否以某一特定后缀名结尾
bool Server::endWith(const std::string& fullStr, const std::string& endStr) {
if (endStr.size() > fullStr.size())
return false;
int indexFull = fullStr.size() - 1;
int indexEnd = endStr.size() - 1;
while (indexEnd >= 0)
{
if (fullStr[indexFull] != endStr[indexEnd])
{
return false;
}
indexFull--;
indexEnd--;
}
return true;
}

//初始化Winsock
int Server::WinsockStartup() {

WSADATA wsaData;//用于返回Winsock的环境信息
int nRc = WSAStartup(0x0202, &wsaData);

if (nRc) {
//Winsock初始化失败
cout << "WSAstartup error!\n";
return -1;
}

//判断返回的Winsock版本号
if (wsaData.wVersion != 0x0202) { //如果版本不对
cout << "Server's winsock version error!";
WSACleanup(); //清除Winsock
return -1;
}

cout << "Server's winsock startup success!\n";
return 0;
}

//初始化Server
int Server::ServerStartup() {

cout << "Please input the ServerAddr: ";
cin >> Config::ServerAddr;
cout << "\rPlease input the ServerPort: ";
cin >> Config::ServerPort;
cout << "Server mainpath is:" <<Config::mainPath << " now" << endl;
cout << "Do you want to change it: y/n ";
char changePath;
cin >> changePath;

if (changePath == 'Y' || changePath == 'y')
{
cout << "Please input the new server mainpath:";
cin >> Config::mainPath;
}

//创建 TCP socket
this->ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (this->ServerSocket == INVALID_SOCKET) {
cout << "Server create socket error!!"<<endl;
WSACleanup();
return -1;
}

cout << "Server TCP socket create OK!"<<endl;

//Bind socket to Server's IP and port 5050
this->ServerAddr.sin_family = AF_INET;
this->ServerAddr.sin_port = htons(Config::ServerPort);
this->ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int nRC = bind(this->ServerSocket, (LPSOCKADDR) & (this->ServerAddr), sizeof(this->ServerAddr));
if (nRC == SOCKET_ERROR) {
cout << "Server socket bind error!\n";
closesocket(this->ServerSocket);
WSACleanup();
return -1;
}
cout << "Server socket bind ok!\n";
return 0;
}

///关闭服务器线程
int Server::closeServer() {
cout << "If you want to close server, please input exit" <<endl ;
std::string Exit;
cin >> Exit;
while (1) {
if (Exit == "exit")
break;
}
//到这里意味着要关闭服务器
this->~Server();
exit(0);
}

//开始监听过程,等待客户的连接
int Server::ListenStartup() {
int nRC = listen(this->ServerSocket, Config::MaxConnetion);
if (nRC == SOCKET_ERROR) {
cout << "Server socket listen error!"<<endl;
closesocket(this->ServerSocket);
WSACleanup();
return -1;
}
cout << "Server socket listen ok!"<<endl;
return 0;
}

//将新的会话socket加入队列
void Server::AddSocket(SOCKET socket) {
if (socket != INVALID_SOCKET) {
this->SocketList->insert(this->SocketList->end(), socket);
}
}

//将失效的会话socket加入失效队列
void Server::AddClosedSocket(SOCKET socket) {
if (socket != INVALID_SOCKET) {
this->ClosedSocketList->insert(this->ClosedSocketList->end(), socket);
}
}

//将失效的socket从会话socket队列删除
void Server::RemoveClosedSocket(SOCKET socket) {
if (socket != INVALID_SOCKET) {
list<SOCKET>::iterator it = find(this->SocketList->begin(), this->SocketList->end(), socket);
if (it != this->SocketList->end()) {
this->SocketList->erase(it);
//closesocket(*it);
}
}
}

//将所有失效的socket从会话socket队列删除
void Server::RemoveClosedSocket() {
for (list<SOCKET>::iterator it = this->ClosedSocketList->begin(); it != this->ClosedSocketList->end(); it++) {
/*if ((*it) != INVALID_SOCKET) {
if (it != this->SocketList->end())
this->SocketList->erase(it);
}*/
this->RemoveClosedSocket(*it);
}
}

//向socket发送消息
void Server::SendMessage(SOCKET socket, string msg) {
int rtn = send(socket, msg.c_str(), msg.length(), 0);
unique_lock<mutex>closedList(this->ClosedList2, defer_lock);
if (rtn == SOCKET_ERROR) { //发送数据错误
closedList.lock();
this->AddClosedSocket(socket);
closedList.unlock();
}
}

//从socket接受消息
void Server::RecvMessage(SOCKET socket) {

unique_lock<mutex>writeRecBuf(this->lockRecBuf, defer_lock); //接收资源写信号灯
unique_lock<mutex>readRecBuf(this->lockRecBuf2, defer_lock); //接收资源读信号灯
unique_lock<mutex>sendHeader(this->sendHeader, defer_lock); //发送报文头部信号灯
unique_lock<mutex>sendMessage(this->sendMessage, defer_lock); //发送报文主体信号灯
unique_lock<mutex>closedList(this->ClosedList, defer_lock); //发送报文主体信号灯

//writeRecBuf.lock(); //写RecBuf时需要上锁
char* RecvBufn = new char[Config::BufferLength];
memset(RecvBufn, '\0', sizeof(RecvBufn)); //缓存清零
int receivedBytes = recv(socket, RecvBufn, Config::BufferLength, 0);
//writeRecBuf.unlock();

//如果会话SOCKET有数据到来,则接受客户的数据
if (receivedBytes == SOCKET_ERROR) { //接受数据错误
/*receivedBytes = WSAGetLastError();
cout << "Thread falied!" << std::endl;
return;*/
//cout << "ReceiveBytes == -1,Receive Error!" << endl;
closedList.lock();
this->AddClosedSocket(socket);
closedList.unlock();
}
else if (receivedBytes == 0) { //对方断开连接
//cout << "ReceiveBytes ==0,Server disconnected!" << std::endl;
closedList.lock();
this->AddClosedSocket(socket);
closedList.unlock();
//return;
}
else {
//cout << "\r\n" << RecvBuf;
//找到报文头部
std::string fullPath;
std::string respondHttpHeader, clientHttpHeader;
std::string statusCode, firstHeader, typeStr, lengthStr;
std::regex regRequest(R"(([A-Z]+) (.*?) HTTP/\d\.\d)");
std::smatch matchRst;
std::string strRecv, strSend;
std::string method;
std:string url;

respondHttpHeader = "";
statusCode = Config::Ok;
firstHeader = "HTTP/1.1 200 OK\r\n";

//找到HTTP报文头部
//readRecBuf.lock(); //读RecBuf时需要上锁
strRecv.assign(RecvBufn);
size_t headerEnd = strRecv.find("\r\n\r\n");
clientHttpHeader = strRecv.substr(0, headerEnd);

//正则表达式提取关键字method(GET,POST),url
if (std::regex_search(clientHttpHeader, matchRst, regRequest)) {
method = matchRst[1].str();
url = matchRst[2].str();
}

for (int i = 0; i < url.size(); i++) {
//替换成windows路径
if (url[i] == '/')
url[i] = '\\';
}

fullPath = Config::mainPath + url; //完整路径
//writeRecBuf.unlock();
//readRecBuf.unlock();

cout << "Client http header:\r\n" << clientHttpHeader.c_str() << endl;
cout << "\r\nmethod:" << method.c_str() << endl;
cout << "url:" << url.c_str() << endl;

//sendHeader.lock(); //发送报文头部信号灯

DWORD ftyp;
ftyp = GetFileAttributesA(fullPath.c_str());
if ((ftyp & FILE_ATTRIBUTE_DIRECTORY) && (!INVALID_FILE_ATTRIBUTES)) {
//如果是一个目录,打开该目录下的索引index.html
fullPath = fullPath + "\\index.html";
}

FILE* infile = fopen(fullPath.c_str(), "rb");

if (!infile) {
//文件不存在
//TODO:发送自定义404页面
infile = fopen((Config::mainPath + "\\404.html").c_str(), "rb");
statusCode = Config::Error;
firstHeader = "HTTP/1.1 404 Not Found\r\n";
typeStr = "Content-Type: text/html\r\n";
}
else if (endWith(url, ".html") || endWith(url, "htm"))
typeStr = "Content-Type: text/html\r\n";
else if (endWith(url, ".txt"))
typeStr = "Content-Type: text /plain\r\n";
else if (endWith(url, ".jpg"))
typeStr = "Content-Type: image/jpeg\r\n";
else if (endWith(url, ".jpeg") || endWith(url, ".png"))
typeStr = "Content-Type: image/" + (url.substr(url.rfind('.') + 1)) + "\r\n";
else if (endWith(url, ".ico"))
typeStr = "Content-Type: image/x-icon\r\n";
else if (endWith(url, ".css"))
typeStr = "Content-Type: text/css\r\n";
else if (endWith(url, ".gif"))
typeStr = "Content-Type: image/gif\r\n";
else if (endWith(url, ".png"))
typeStr = "Content-Type: image/png\r\n";
else if (endWith(url, ".js"))
typeStr = "Content-Type: application/javascript\r\n";
else if (endWith(url, ".eot"))
typeStr = "Content-Type: application/vnd.ms-fontobject\r\n";
else if(endWith(url,".mp3"))
typeStr = "Content-Type: audio/mpeg\r\n";
else {
if (infile != NULL) //文件存在,但是不支持解析
fclose(infile);
//TODO:发送自定义501页面
infile = fopen((Config::mainPath + "\\501.html").c_str(), "rb");
statusCode = Config::Error;
firstHeader = "HTTP/1.1 501 Not Implemented\r\n";
typeStr = "Content-Type: text/html\r\n";
}

//获取文件大小
fseek(infile, 0, SEEK_SET);
fseek(infile, 0, SEEK_END);
int fileLength = ftell(infile);
//文件指针归位
fseek(infile, 0, SEEK_SET);

respondHttpHeader = firstHeader + typeStr + "Content-Length: " + std::to_string(fileLength) + "\r\n" + "Server: CServer_HTTP1.1\r\n" + "Connection: close\r\n" + "\r\n";
//发送报文头部
//send(socket, respondHttpHeader.c_str, respondHttpHeader.length(), 0);
this->SendMessage(socket, respondHttpHeader);
//sendHeader.unlock();

cout << "respond http header:" << respondHttpHeader.c_str();

//发送请求的文件
//sendMessage.lock();
int bufReadNum;
while (true) {
//缓存清零
memset(RecvBufn, 0, sizeof(RecvBufn));
bufReadNum = fread(RecvBufn, 1, Config::BufferLength, infile);
if (SOCKET_ERROR == (send(socket, RecvBufn, bufReadNum, 0)))
{//发送失败
//rtn = SOCKET_ERROR;
cout << "File: " << url << " transfer error!" << endl;
break;
}
if (feof(infile))
break;
}
fclose(infile);
//memset(RecvBufn, '\0', Config::BufferLength);//清除接受缓冲区
//sendMessage.unlock();
if (RecvBufn != NULL)
delete(RecvBufn);
cout << "File: " << fullPath << " transfer successful!" << endl;
}
//writeRecBuf.unlock();
//closesocket(socket);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return;
}

//接受客户端发来的请求连接并生成会话socket
int Server::ServerWork() {

u_long blockMode = Config::uNonBlock;
int rtn;
//nThread.MemberProc = &Server::RecvMessage;

//将ServerSocket设为非阻塞模式以监听客户连接请求
if ((rtn = ioctlsocket(this->ServerSocket, FIONBIO, &blockMode) == SOCKET_ERROR)) { //FIONBIO:允许或禁止套接口s的非阻塞模式。
cout << "Set Server unblockMode error!"<<endl;
return -1;
}

while (1) {
//等到客户端的连接请求

//设置socket集
//FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,
//如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_ZERO(&this->rfds);
FD_ZERO(&this->wfds);

//FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
//把serverSocket加入到rfds,等待用户连接请求
FD_SET(this->ServerSocket, &this->rfds);

//把当前的会话socket加入到rfds,等待用户数据的到来;加到wfds,等待socket可发送数据
for (list<SOCKET>::iterator it = this->SocketList->begin(); it != this->SocketList->end(); it++) {
FD_SET(*it, &rfds);
FD_SET(*it, &wfds);
}

//等待用户连接请求或用户数据到来或会话socke可发送数据
//返回有可读或可写的socket的总数
if ((this->SocketNums = select(0, &this->rfds, &this->wfds, NULL, NULL)) == SOCKET_ERROR)
{
cout << "Select socket nums error!" << endl;
return -1;
}

//此时有客户端请求到来
sockaddr_in ClientAddr; //客户端IP地址
int AddrLen = sizeof(ClientAddr);

//检查srvSocket是否收到用户连接请求
if (this->SocketNums > 0) {

if (FD_ISSET(this->ServerSocket, &rfds)) { //检查有socket就绪
this->SocketNums--;

//产生会话socket
SOCKET sockket = accept(this->ServerSocket, (LPSOCKADDR) & (ClientAddr), &AddrLen);
if (sockket == INVALID_SOCKET) {
cout << "Server accept error!\n";
//TODO:
/*closesocket(this->ServerSocket);
WSACleanup();
return -1;*/
}

//将socket设为非阻塞模式以监听客户连接请求
if (ioctlsocket(sockket, FIONBIO, &blockMode) == SOCKET_ERROR) {
cout << "Set accept socket unbolockmode error!\n";
return -1;
}

//将产生的会话SOCKET保存在SocketList中
getpeername(sockket, (struct sockaddr*) & ClientAddr, &AddrLen);
cout << "Receive Ip:" << inet_ntoa(ClientAddr.sin_addr) << " from Port:" << ntohs(ClientAddr.sin_port) << " request";
cout << "" << endl;
this->AddSocket(sockket);
cout << "Server accept success!" << endl;
}
}

//接收用户请求
if (this->SocketNums > 0) {
//遍历socket队列,检查是否有数据到来
for (list<SOCKET>::iterator it = this->SocketList->begin(); it != this->SocketList->end(); it++) {
if (*it != INVALID_SOCKET) {
//FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
if (FD_ISSET(*it, &rfds)) { //某会话socket有数据到来
SOCKADDR_IN RequestSocket;
int socketAddrLen = sizeof(RequestSocket);
getpeername(*it, (struct sockaddr*) & RequestSocket, &socketAddrLen);
/*cout << "Receive Ip:" << inet_ntoa(RequestSocket.sin_addr) << " from Port:" << ntohs(RequestSocket.sin_port) << " request";
cout << "" << endl;*/
thread talkThread(&Server::RecvMessage, std::ref(*this), *it); //开启一个新的线程来处理这一个客户的请求
talkThread.detach();
}
}
}
}

//从socketList里删掉已经关闭的socket
this->RemoveClosedSocket();
}

return 0;
}

配置

config 配置了一些常用变量

config.h代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
#include <winsock2.h>

using namespace std;

class Config
{
public:
static int ServerPort; //服务器端口号
static const int ClientPort; //客户端端口
static string ServerAddr; //服务器IP地址
static const int MaxConnetion; //最大连接数
static const int BufferLength; //缓冲区大小
static string mainPath; //虚拟主路径
static const string Ok; //客户端请求成功
static const string Error; //客户端语法错误,服务端尚未实现
static const string NotFound; //文件不存在
static const string HttpVersion; //HTTP版本
static const u_long uNonBlock; //SOCKET阻塞模式
private:
Config();
~Config();
};

config.cpp代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "config.h"
#include <winsock2.h>

Config::Config()
{

}
Config::~Config()
{

}

int Config::ServerPort = 5050;
const int Config::ClientPort = 8080;
string Config::ServerAddr = "127.0.0.1";
const int Config::MaxConnetion = 100;
const int Config::BufferLength = 65535;
const string Config::Ok= "200 OK";
const string Config::Error = "501 Not Implemented";
const string Config::NotFound = "404 Not Found";
const string Config::HttpVersion = "HTTP/1.1 ";
const u_long Config::uNonBlock = 1; //SOCKET为非阻塞模式
string Config::mainPath = "D:\\html\\";