《TCP/IP网络编程》笔记

目录

创建套接字

TCP/IP服务端、客户端简单示例

端口号

地址信息表示

字节序与网络字节序

字符串转网络字节序

WSAStringToAddress & WSAAddressToString

TCP服务端函数调用顺序

TCP客户端函数调用顺序

TCP套接字中的I/O缓冲

TCP比UDP慢的原因

UDP套接字的已连接和未连接

流半关闭

获取域名和IP地址

套接字可选项

处理僵尸进程

SELECT模型

SEND() & RECV()

READV() & WRITEV()

多播

广播

标准I/O函数

I/O流分离

EPOLL模型(仅Linux)

条件触发和边缘触发

Linux线程相关函数

Window线程相关函数

WSAEventSelect异步模型

重叠I/O

纯重叠I/O实现的回声服务端

IOCP模型

简易HTTP服务端


TCP Transmission Control Protocol(传输控制协议)

IP Internet Protocal(网络协议)

 

创建套接字

#include<sys/socket.h>

int socket(int domain, int type, int protocol);
//成功时返回文件描述符,失败返回-1 (Windows下返回INVALID_SOCKET)

domain:
    PF_INET            IPv4互联网协议族(常用)
    PF_INET6           IPv6互联网协议族
    PF_LOCAL           本地通信的UNIX协议族
    PF_PACKET          底层套接字的协议族
    PF_IPX             IPX Novell协议族

type:
    SOCK_STREAM       面向连接的套接字 
                      1.传输过程中数据不会丢
                      2.按序传输数据
                      3.传输的数据不存在数据边界(即写的次数无需对应读的次数)

    SOCK_DGRAM        面向消息的套接字
                      1.强调快速传输而非传输顺序
                      2.数据可能丢失
                      3.传输的数据有数据边界(即写一次就需要读一次)
                      4.限制的每次传输的数据大小

protocol: (通常情况下可以写0,除非遇到“同一协议族中存在多个数据传输方式相同的协议”时,数据传输方式相同,但协议不同。此时需要通过第三个参数具体指定协议信息)
    IPPROTO_TCP        仅对应PF_INET,SOCK_STREAM
    IPPROTO_UDP        仅对应PF_INET,SOCK_DGRAM
    

TCP/IP服务端、客户端简单示例

//服务端
#include <string.h>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

void main()
{
    int serv_sock,client_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in client_addr;
    socklen_t client_addr_size;

    char message[] = "Hello!";

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock == -1)
    {
        cout << "create socket error" << endl;
        return;
    }

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7899);

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
    {
        cout << "bind error" << endl;
        return ;
    }

    if(listen(serv_sock,5) == -1)
    {
        cout << "listen error" << endl;
        return;
    }

    client_addr_size = sizeof(client_addr);
    client_sock = accept(serv_sock,(struct sockaddr*)&client_addr,&client_addr_size);
    if(client_sock == -1)
    {
        cout << "accept error" << endl;
        return;
    }

    write(client_sock,message,sizeof(message));
    close(client_sock);
    close(serv_sock);
}

//客户端
#include <string.h>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
void main()
{
    int sock;
    struct sockaddr_in serv_addr;

    sock = socket(PF_INET,SOCK_STREAM,0);
    if(sock == -1)
    {
        cout << "create socket error" << endl;
        return;
    }

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(7899);

    if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
    {
        cout << "connect error" << endl;
        return;
    }

    char message[30];
    int strlen = read(sock,message,sizeof(message)-1);
    if(strlen == -1)
        cout << "read error" << endl;

    message[strlen] = '\0';
    cout << message << endl;

    close(sock);
}

端口号

端口号由16位构成,可分配范围0-65535,其中0-1023是知名端口给特定程序使用。

TCP套接字和UDP套接字可以共用同一个端口号。

地址信息表示

struct sockaddr_in
{
    sa_family_t     sin_family;  //地址族
    uint16_t        sin_port;    //16位端口号
    struct in_addr  sin_addr;    //32位IP地址
    char            sin_zero[8]; //不使用 
                                 //(只是为了使结构体与sockaddr保持一致而插入的成员,必须填0)
};


struct in_addr
{
    In_addr_t     s_addr; //32位IPv4地址 uint32_t
};

struct sockaddr
{
    sa_family_t    sin_family;    //地址族
    char           sa_data[14];   //地址信息 包含IP地址和端口号,剩余部分填充0
};

字节序与网络字节序

大端序(Big Endian):高位字节存放低位地址。(网络字节序)

小端序(Little Endian):高位字节存放高位地址。(Intel AMD是小端序存放)

0x12345678 

大端序:0x12 0x34 0x56 0x78

小端序:0x78 0x56 0x34 0x12

转换字节序的函数

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

h: 主机host
n:网络network
s: short
l: long

字符串转网络字节序

#include <arpa/inet.h>

//API
in_addt_t inet_addr(const char* string);
//成功返回32位大端序整数,失败返回INADDR_NONE

//API
int inet_aton(const char* string, struct in_addr* addr);
/*
成功1 失败0
a 地址address
n 网络network
string 需要转换的IP地址
addr   存储转换结果
*/


示例:
char* addr="127.0.0.1";
struct sockaddr_in addr_inet;

inet_aton(addr,&addr_inet.sin_addr);

//API
char* inet_ntoa(struct in_addr adr);
//成功返回字符串地址,失败返回-1

WSAStringToAddress & WSAAddressToString

在IPv4和IPv6下均适用

#include <winsock2.h>

INT WSAStringToAddress(
    LPTSTR              AddressString,      //含IP和端口的字符串
    INT                 AddressFamily,      //地址族
    LPWSAPROTOCOL_INFO  lpProtocolInfo,     //协议提供者,默认NULL
    LPSOCKADDR          lpAddress,          //保存转换后的地址信息
    LPINT               lpAddressLength     //第四个参数的长度
);
//成功0,失败 SOCKET_ERROR

INT WSAAddressToString(
    LPSOCKADDR          lpsaAddress,        //需要转换的地址信息
    WORD                dwAddressLength     //第一个参数的长度
    LPWSAPROTOCOL_INFO  lpProtocolInfo,     //协议提供者,默认NULL
    LPTSTR              AddressString,      //保存转换后的结果
    LPDWORD             AddressFamily,      //第四个参数长度    
);
//成功0,失败 SOCKET_ERROR

示例:
char * strAddr = "127.0.0.1:6677";
char strBuff[50];
SOCKADDR_IN servAddr;
int size = sizeof(servAddr);

WSAStringToAddress(strAddr,AF_INET,NULL,(SOCKADDR*)&servAddr,&size);

TCP服务端函数调用顺序

socket() 创建套接字

bind() 分配套接字地址

listen() 等待连接请求

accept() 允许连接

read()/write() 数据交换

close() 断开连接

#include <sys/socket.h>

int listen(int sock, int backlog);
//成功0,失败-1
sock: 监听套接字
backlog: 连接请求等待队列的长度,表同时能接受最多的连接请求。默认5

int accept(int sock, struct sockaddr* addr, socklen_t * addrlen);
//成功返回创建的套接字文件描述符,失败返回-1

在没有成功连接之前都会处于连接请求状态

TCP客户端函数调用顺序

socket() 创建套接字

connect() 请求连接

read()/write() 数据交换

close() 断开连接

TCP套接字中的I/O缓冲

I/O缓冲在每个TCP套接字中单独存在

I/O缓冲在创建套接字时自动生成

即使关闭套接字也会继续传递输出缓冲中遗留的数据

关闭套接字将丢失输入缓冲的数据。

write()/send()在数据传输完成时返回。

TCP比UDP慢的原因

  1. 收发数据前后进行的连接设置及清除过程。
  2. 收发数据过程中为保证可靠性而添加的流控制

UDP套接字的已连接和未连接

未连接UDP

每次发送数据时(sendto)要经历三个步骤:

  1. 向UDP套接字注册目标IP和端口号
  2. 传输数据。
  3. 删除UP套接字的中注册的目标地址信息。

已连接UDP

通过调用connect函数实现向UDP中注册目标IP和端口号,达到每次发送数据时以节省以上第一步和第三步的时间开销。

所以当使用UDP发送多个数据时建立已连接的UDP性能较佳。

建立已连接的UDP也可以直接write()/read()进行通信

流半关闭

//Linux
#include <sys/socket.h>

int shutdown(int sock,int howto);
//成功0,失败-1
sock: 需要断开的套接字描述符
howto: 断开方式
        SHUT_RD: 断开输入流 (不可读)
        SHUT_WR:断开输出流 (不可写)
        SHUT_RDWR: 同时断开I/O流

//windows下
#include <winsock2.h>
int shutdown(SOCKET sock,int howto);
//成功0,失败SOCKET_ERROR
howto: SD_RECEIVE
       SD_SEND
       SD_BOTH

获取域名和IP地址

#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);

struct hostent
{
    char * h_name;         //官方域名
    char ** h_aliases;     //其它域名
    int h_addrtype;        //地址族
    int h_length;          //IP地址长度
    char** h_addr_list;    //域名对应的IP地址
};

示例:
    struct hostent* host = gethostbyname("www.baidu.com");
    if(!host)
        return;

    cout << host->h_name << endl;
    for(int i=0; host->h_aliases[i]; i++)
        cout << "aliase: " << i+1 << " " << host->h_aliases[i] << endl;

    for(int i=0;host->h_addr_list[i];i++)
    {
        cout << "ip: " << i+1 << " " << inet_ntoa(*(struct in_addr*)host->h_addr_list[i]) << endl;
    }

输出结果:
www.a.shifen.com
aliase: 1 www.baidu.com
ip: 1 36.152.44.95
ip: 2 36.152.44.96


#include <netdb.h>
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);

示例:
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    struct hostent* host = gethostbyaddr(&addr.sin_addr,4,AF_INET);
    ...

套接字可选项

#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
//成功0,失败-1
sock         查看的套接字
level        可选项的协议层
optname      可选项名
optval       保存查看结果的缓冲地址值
oplen        第四个参数传递的缓冲大小

int setsockopt(int sock,int level,int optname, const void* optval, socklen_t optlen);
//同上 用于设置

可选项:
SO_TYPE 套接字类型 SOCK_STREAM 1 SOCK_DGRAM 2
SO_SNDBUF 发送缓冲大小
SO_RCVBUF 接收缓冲大小
SO_REUSEADDR 使超时等待的端口可用 默认为false
SO_NODELAY 禁用Nagel算法(防止数据包过多而发送网络过载,收到前一个数据包的ACK消息时,Nagle算法才发送下一数据) 默认为0

处理僵尸进程

//方法一
#include <sys/wait.h>
pid_t wait(int * statloc);
//成功返回终止的子进程ID,失败时返回-1

宏:
WIFEXITED 子进程正常终止时返回true
WEXITSTATUS 返回子进程的返回值


示例:
int status
wait(&status); //在父进程中阻塞等待,子进程退出
if(WIFEXITED(status)) //判断正常终止
    cout << "child exit code: " << WEXITSTATUS(status) << endl;

//方法二
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
//成功返回终止的子进程ID,失败时返回-1, 子进程没有结束返回0

pid:        等待终止的目标子进程ID,若-1则与wait等同
statloc:    
options:    常用常量WNOHANG, 即使没有终止的子进程也不会进入阻塞状态,而是返回0并退出

示例:
while(!waitpid(-1,&status,WHOHANG)) //非阻塞
{
    sleep(3);
}

//方法三 最优版
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);

常用信号:
SIGALRM: alarm函数注册的时间超时
SIGINT:    输入CTRL+C
SIGCHLD:    子进程终止

#include <unistd.h>
//注册超时
unsigned int alarm(unsigned int seconds); 

#include <signal.h>
//升级版
int sigaction(int signo, const struct sigaction *act, struct sigaction* oldact);
//成功0,失败-1

struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
};

示例:
void childproc(int sig)
{
    int status;
    pid_t = waitpid(-1,&status, WNOHANG);
    if(WIFEXITED(status))
    {
        cout << "child exit" << endl;
    }
}

void main()
{
    pid_t pid;
    struct sigaction act;
    act.sa_handler = childproc;
    sigemptyset(&act,sa_mask); //sa_mask所有位初始为0
    act.sa_flags=0;
    sigaction(SIGCHLD, &act, 0);

    fork();
    ...
}

SELECT模型

调用顺序

  1. 设置文件描述符
  2. 指定监听范围
  3. 设置超时
  4. 调用select函数
  5. 查看调用结果
FD_ZERO(fd_set *fdset): 将fd_set变量的所有位初始化0
FD_SET(int fd, fd_set* fdset); 在参数fdset指向的变量中注册文件描述符fd的信息
FD_CLR(int fd,fd_set* fdset): 从参数fdset指向的变量中清除文件描述符fd的信息
FD_ISSET(int fd, fd_set* fdset): 若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真”

#include <sys/select.h>
#include <sys/time.h>
int select(  int maxfd, 
             fd_set* readset,
             fd_set* writeset,
             fd_set* exceptset,
             const strcut timeval* timeout);
//成功返回大于0,失败返回-1,超时返回0
maxfd         监视对象文件描述符数量
readset       是否存在待读取数据
writeset      是否可传输无阻塞数据
exceptset     是否发生异常
timeout       等待超时

示例:

#include <string.h>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
using namespace std;
void main()
{
    int serv_sock,client_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in client_addr;

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock == -1)
    {
        cout << "create socket error" << endl;
        return;
    }

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7899);

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
    {
        cout << "bind error" << endl;
        return ;
    }

    if(listen(serv_sock,5) == -1)
    {
        cout << "listen error" << endl;
        return;
    }

    fd_set reads,reads_copy;
    int fd_max,fd_num;
    timeval timeout;
    int const BUFSIZE = 50;
    char buf[BUFSIZE];

    FD_ZERO(&reads);
    FD_SET(serv_sock,&reads);
    fd_max = serv_sock;

    cout << serv_sock << endl;
    while(true)
    {
        reads_copy = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        fd_num=select(fd_max+1,&reads_copy,0,0,&timeout);

        if(fd_num == -1)
            break;

        if(fd_num == 0)
            continue;

        for(int i=0;i<fd_max+1;i++)
        {
            if(FD_ISSET(i,&reads_copy))
            {
                if(i == serv_sock)
                {
                    socklen_t adr_sz = sizeof(client_addr);
                    client_sock = accept(serv_sock,(struct sockaddr*)&client_addr,&adr_sz);
                    cout << "connected client: " << client_sock << endl;
                    FD_SET(client_sock,&reads);
                    if(fd_max < client_sock)
                        fd_max = client_sock;
                }
                else
                {
                   ssize_t len = read(i,buf,BUFSIZE);
                   if(len == 0)
                   {
                       FD_CLR(i,&reads);
                       close(i);
                       cout << "closed client: " << i << endl;
                   }
                   else
                   {
                       buf[len] = '\0';
                       cout << "recv: " << buf << endl;
                       write(i,buf,len);
                   }
                }
            }

        }
    }

    close(serv_sock);
}

SEND() & RECV()

#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);

flags:
    MSG_OOB            用于传输外带数据
    MSG_PEEK           验证输入缓冲中是否存在接收的数据
    MSG_DONTROUTE      数据传输过程中不参照路由表,在本地网络中寻找目的地
    MSG_DONTWAIT       调用I/O函数时不阻塞,用于使用非阻塞I/O
    MSG_WAITALL        防止函数返回,直到接受到全部请求的字节数

其中MSG_OOB外带数据,Linux下可采用信号接收,Windows下采用select模型的第四个参数异常集合来接收(“异常”是不同寻常的程序执行流,因此,收到Out-of-band数据也属于异常)

READV() & WRITEV()

对数据进行整合传输,以减少I/O调用次数

#include <sys/uio.h>

ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);
ssize_t readv(int filedes, const struct iovec* iov, int iovcnt);
//成功返回发送的字节数,失败-1

filedes:    数据传输对象的套接字文件描述符,不仅限套接字
iov:         结构体数组的地址值
iovcnt:      数组长度

struct iovec
{
    void* iov_base; //缓冲地址
    size_t iov_len; //缓冲地址
}

示例:
struct iovec vec[2];
char buf1[]="123456";
char buf2[]="ABCDEF";
vec[0].iov_base=buf1;
vec[0].iov_len=3;
vec[1].iov_base=buf2;
vec[1].iov_len=4;
int len = writev(sock,vec,2); // len=7

多播

  1. 多播服务器针对 特定多播组,只发送1次数据,该组内所有客户端都会收到数据
  2. 多播组可在IP地址范围内任意增加
  3. 加入特定组即可接收发往该多播组的数据

多播组是D类IP地址(224.0.0.0~239.255.255.255)

TTL(Time to Live),决定数据包传递距离,每经过一个路由器则减一。

设置TTL方法,使用选项名IP_MULTICAST_TTL

int time_to_live = 64;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_to_live);

设置主机加入多播组,使用选项名IP_ADD_MEMBERSHIP
struct ip_mreq join_addr;
join_addr.imr_multiaddr.s_addr="多播组的IP地址";
join_addr.imr_interface.s_addr="加入组的主机地址";
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMEBERSHIP, (void*)&join_addr);

struct ip_mreq
{
    struct in_addr imr_multiaddr; //组的IP地址
    struct in_addr imr_interface; //加入该组的套接字所属主机的IP地址
};

广播

区别:

  • 多播即使在跨越不同网络的情况下,只要加入多播组就能接收数据。
  • 广播只能向同一网络中的主机传输数据。

分两种情况:

直接广播:例如本机所在地址192.168.1.xxx 向192.168.2.255发送广播,192.168.2中的所有主机都能收到。

本地广播:例如本机所在地址192.168.1.xxx 向255.255.255.255发送广播,192.168.1中的所有主机都能收到。且限定255.255.255.255

使用UDP广播需要设置选项SO_BROADCAST,因为默认创建的套接字会阻止广播

int so_brd=1;
setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(void*)&so_brd,sizeof(so_brd));

标准I/O函数

不带缓冲的I/O
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

带缓冲的I/O
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);

当以下情况大量时,使用带缓冲的I/O是能够提升性能
1.传输的数据量
2.数据向输出缓冲移动的次数


#include <stdio.h>
//文件描述符转FILE结构体指针
FILE* fdopen(int fildes, const char* mode);

//FILE结构体指针转文件描述符
int fileno(FILE* stream);

I/O流分离

//创建读指针
FILE* readfp = fdopen(sock,"r");
//创建写指针
FILE* writefp = fdopen(sock,"w");
fclose(readfp); (或)fclose(writefp);

以上两个close任意关闭哪一个都会发送EOF关闭sock,怎么实现半关闭呢?答案是复制文件描述符。原理就是销毁所有文件描述符后才能销毁套接字。

#include <unistd.h>
int dup(int fildes);
//返回复制的文件描述符
int dup2(int fildes,int fildes2);
//返回复制的文件描述符,但返回值可以由fildes2指定

EPOLL模型(仅Linux)

select模型的优点:

  • 支持跨平台,具有很好的兼容性。
  • 服务端接入者少时,性能较优。

select模型的缺点:

  • 调用select函数后常见的针对所有文件描述符的循环语句。
  • 每次调用select函数时都要传递监视对象信息

epoll则针对select缺点的克服:

  • 无需编写以监视变化为目的的针对所有文件描述符的循环语句。
  • 调用无需每次传递监视对象信息。

epoll_create: 创建保存epoll文件描述符的空间。

epoll_ctl: 向空间注册并注销文件描述符。

epoll_wait: 等待文件描述符发生变化。

//监听事件变化的结构体
struct epoll_event
{
    __uint32_t       events;
    epoll_data_t     data;
}

events的常用事件类型
EPOLLIN: 需要读取数据的情况
EPOLLOUT: 输出缓冲为空,可以立即发送数据的情况
EPOLLPRI: 收到OOB数据的情况
EPOLLLRDHUP: 断开连接或半关闭的情况,这在边缘触发方式下非常有用。
EPOLLERR: 发送错误的情况
EPOLLET: 以边缘触发额方式得到事件通知。
EPOLLONESHOT: 发生一次事件后,相应描述符不再接收事件通知。如果需要则使用EPOLL_CTL_MOD再次设置事件

struct union epoll_data
{
    void*       ptr;
    int         fd;
    __uint32_t  u32;
    __uint64_t  u64;
} epoll_data_t;

#include <sys/epoll.h>
int epoll_create(int size);
//成功返回epoll文件描述符,失败返回-1.
size:     epoll实例的大小,仅是给系统提供建议非强制

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
//成功0,失败-1
efpd    用于注册监视对象的epoll例程的文件描述符。
op      用于指定监视对象的添加、删除或更改等操作。
    EPOLL_CTL_ADD 文件描述符添加到例程   
    EPOLL_CTL_DEL 从epoll例程中删除文件描述符
    EPOLL_CTL_MOD 更改注册的文件描述符的关注事件发生情况
fd      需要注册的监视对象文件描述符。
event   监视对象的事件类型。

int epoll_wait(int epfd, struct epoll_evet* events, int maxevents, int timeout);
//成功时返回发生事件的文件描述符数,失败时返回-1.
epfd: 表示事件发生监视范围的epoll例程的文件描述符。
events: 保存发生事件的文件描述符集合的结构体地址值。
maxevents: 第二个参数中可以保存的最大事件数。
timeout: 以1/1000秒为单位的等待事件。


示例:
#include "useselect.h"
#include <string.h>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
using namespace std;

void main()
{
    int serv_sock,client_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in client_addr;

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock == -1)
    {
        cout << "create socket error" << endl;
        return;
    }

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7899);

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
    {
        cout << "bind error" << endl;
        return ;
    }

    if(listen(serv_sock,5) == -1)
    {
        cout << "listen error" << endl;
        return;
    }

    struct epoll_event* ep_events;
    struct epoll_event event;
    int epfd, event_cnt;
    int const EPOLLSIZE = 50;
    int const BUFSIZE = 50;
    char buf[BUFSIZE];

    epfd = epoll_create(EPOLLSIZE);
    ep_events = new epoll_event[EPOLLSIZE];

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd,EPOLL_CTL_ADD,serv_sock,&event);

    while(true)
    {
        event_cnt = epoll_wait(epfd,ep_events,EPOLLSIZE,-1);
        if(event_cnt == -1)
        {
            cout << "epoll_wait error" << endl;
            break;
        }

        for(int i=0;i<event_cnt;i++)
        {
            if(ep_events[i].data.fd == serv_sock)
            {
                socklen_t adr_sz = sizeof(client_addr);
                client_sock = accept(serv_sock,(struct sockaddr*)&client_addr,&adr_sz);
                cout << "connected client: " << client_sock << endl;

                event.events = EPOLLIN;
                event.data.fd = client_sock;
                epoll_ctl(epfd,EPOLL_CTL_ADD,client_sock,&event);
            }
            else
            {
                ssize_t len = read(ep_events[i].data.fd,buf,BUFSIZE);
                if(len == 0)
                {
                    epoll_ctl(epfd,EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
                    close(ep_events[i].data.fd);
                    cout << "closed client: " <<  ep_events[i].data.fd << endl;
                }
                else
                {
                    buf[len] = '\0';
                    cout << "recv: " << buf << endl;
                    write(ep_events[i].data.fd,buf,len);
                }
            }
        }
    }


    close(serv_sock);
    close(epfd);
}

条件触发和边缘触发

条件触发:只要输入缓冲中有数据就会一直注册该事件。event.events=EPOLLIN

边缘触发: 出入缓冲收到数据时仅注册一次该事件。 event.events=EPOLLIN|EPOLLLET

#include <fcntl.h>
int fcntl(int filedes, int cmd, ...);
filedes: 属性更改目标的文件描述符
cmd: 调用目的

//示例 将套接字改为非阻塞模式
int nflag = fcntl(sock, F_GETFL, 0); //获取当前属性
fcntl(sock, F_SETFL, nflag|O_NONBLOCK); //设置非阻塞

Linux线程相关函数

#include <pthread.h>

//创建线程
int pthread_create(pthread_t* restrict thread, 
            const pthread_attr_t* restrict attr,
            void* (*start_rountine)(void *),
            void* restrict arg);

int pthread_join(pthread_t thread, void** status);
int pthread_detach(pthread_t thread);

//互斥量
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

//信号量
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
int sem_post(sem_t* sem);
int sem_wait(sem_t* sem);

Window线程相关函数

#include <windows.h>
HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    SIZE_T dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId
);

//标准c创建线程
#include <process.h>
uintpter_t _beginthreadex(
    void* security,
    unsigned stack_size,
    unsigned (* start_address)(void*),
    void* arglist,
    unsigned* thredaddr
);

CreateThread创建的线程使用c/c++标准函数时会不稳定????所以用_beginthreadex创建

#include <windows.h>
//等待线程终止
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMiliseconds);

//用户模式的同步
#include <windows.h>
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

//内核模式的同步,可以到达不同进程间的同步
#include <windows.h>
HANDLE CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwner,
    LPCTSTR lpName
);
BOOL CloseHandle(HANDLE hObject);
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); //锁
BOOL ReleaseMutex(HANDLE hMutex);    //解锁

//信号量的同步
#include <windows.h>
HANDLE CreateSemaphore(
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    LONG lInitialCount,
    LONG lMaximumCount,
    LPCTSTR lpName
);
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

//事件对象的同步
HANDLE CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,
    BOOL bManualReset,
    BOOL bInitialState,
    LPCTSTR lpName
);
BOOL ResetEvent(HANDLE hEvent);
BOOL SetEvent(HANDLE hEvent);

WSAEventSelect异步模型

#include <winsock2.h>
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
s: 监视对象的套接字句柄
hEventObject: 传递事件对象句柄以验证事件发生与否
lNetworkEvents: 希望监视的事件类型信息
    FD_READ: 是否存在需要接收的数据
    FD_WRITE: 是否以非阻塞方式传输数据
    FD_OOB: 是否收到外带数据
    FD_ACCEPT: 是否有新的连接请求
    FD_CLOSE: 是否有断开连接的请求

创建manual-reset模式事件对象的其它方法
WSAEVENT WSACreateEvent(void);
BOOL WSACloseEvent(WSAEVENT hEvent);

//等待事件发生
DWORD WSAWaitForMultipleEvents(
    DWORD cEvents,
    const WSAEVENT* lphEvents,
    BOOL fWaitAll,
    DWORD dwTimeout,
    BOOL fAlertable
);

//区分事件类型
int WSAEnumNetWorkEvents(
    SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents
);

typedef struct _WSANETWORKEVENTS
{
    long lNetworkEvents;
    int iErrorCode[FD_MAX_EVENTS];
};

示例:

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib,"WS2_32")

using namespace std;

int main()
{
    WSADATA wsadata;
	WSAStartup(MAKEWORD(2, 2), &wsadata);

	SOCKET clientSock;
	SOCKET servSock = socket(PF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN servAddr, clientAddr;
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(7788);
	int ret = ::bind(servSock, (sockaddr*)&servAddr, sizeof(servAddr));
	::listen(servSock, 5);

	SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];
	WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
	int nSockNum = 0;
	int nPosinfo,nStartIndex;

	WSAEVENT newEvent = WSACreateEvent();
	if (WSAEventSelect(servSock, newEvent, FD_ACCEPT) == SOCKET_ERROR)
		cout << "WSAEventSelect error" << endl;

	hSockArr[nSockNum] = servSock;
	hEventArr[nSockNum] = newEvent;
	nSockNum++;

	while (true)
	{
		nPosinfo = WSAWaitForMultipleEvents(nSockNum, hEventArr, false, WSA_INFINITE, false);
		nStartIndex = nPosinfo - WSA_WAIT_EVENT_0;

		for (int i = nStartIndex; i < nSockNum; i++)
		{
			int signedEventIndex = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
			if (signedEventIndex == WSA_WAIT_FAILED || signedEventIndex == WSA_WAIT_TIMEOUT)
				continue;

			signedEventIndex = i;
			WSANETWORKEVENTS netEvents;
			WSAEnumNetworkEvents(hSockArr[signedEventIndex], hEventArr[signedEventIndex], &netEvents);
			//请求连接
			if (netEvents.lNetworkEvents & FD_ACCEPT)
			{
				if (netEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
				{
					cout << "accept error" << endl;
					break;
				}

				int clientaddrlen = sizeof(clientAddr);
				clientSock = accept(hSockArr[signedEventIndex], (SOCKADDR*)&clientAddr, &clientaddrlen);
				newEvent = WSACreateEvent();
				WSAEventSelect(clientSock, newEvent, FD_READ | FD_CLOSE);

				hEventArr[nSockNum] = newEvent;
				hSockArr[nSockNum] = clientSock;
				nSockNum++;
				cout << "connected new client:" << clientSock << endl;
			}
			//接收数据
			if (netEvents.lNetworkEvents & FD_READ)
			{
				if (netEvents.iErrorCode[FD_READ_BIT] != 0)
				{
					cout << "READ error" << endl;
					break;
				}

				char buf[30];
				int strlen = recv(hSockArr[signedEventIndex], buf, 30, 0);
				send(hSockArr[signedEventIndex], buf, strlen, 0);
			}
			//断开连接
			if (netEvents.lNetworkEvents & FD_CLOSE)
			{
				if (netEvents.iErrorCode[FD_CLOSE_BIT] != 0)
				{
					cout << "CLOSE error" << endl;
					break;
				}

				WSACloseEvent(hEventArr[signedEventIndex]);
				closesocket(hSockArr[signedEventIndex]);
				cout << "close client: " << hSockArr[signedEventIndex] << endl;
			}
		}
	}

	WSACleanup();
	return 0;
}

重叠I/O

#include <winsock2.h>
//创建重叠I/O套接字
SOCKET WSASocket(
int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags
);

//重叠I/O发送
int WSASend(SOCKET s, 
    LPWSABUF lpBuffers, 
    DWORD dwBufferCount, 
    LPDWORD lpNumberOfBytesSent, 
    DWORD dwFlags, 
    LPWSAOVERLAPPED lpOverlapped, 
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

typedef struct __WSABUF
{
    u_long len; //待传输数据的大小
    char FAR* buf; //缓冲地址值
}WSABUF,*LPWSABUF;

typedef struct __WSAOVERLAPPE
{
    DWORD Internal;
    DWORD InternalHigh;
    DWORD Offset;
    DWORD OffsetHigh;
    WSAEVENT hEvent;
}WSAOVERLAPPE,* LPWSAOVERLAPPED;

//获取数据传输结果
BOOL WSAGetOverlappedResult(
SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags
);

//接收
int WSARecv(
SOCKET s,LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);


示例:
接收端:
    //windows初始化SOCKET库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 0);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.0初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0)
	{
		WSACleanup();
		return 0;
	}

	//创建重叠套接字
	SOCKET sock;
	sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(7788);
	int ret = ::bind(sock, (SOCKADDR*)&myaddr, sizeof(myaddr));
	cout << "bind: " << ret << endl;

	//开始监听
	ret = ::listen(sock, 1);
	cout << "listen: " << ret << endl;

	//等待接收链接
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	SOCKET sockconn = accept(sock, (SOCKADDR*)&addrClient, &len);

	WSAEVENT evObj = WSACreateEvent();
	WSAOVERLAPPED overlapped;
	memset(&overlapped, 0, sizeof(overlapped));
	overlapped.hEvent = evObj;

	WSABUF dataBuf;
	const int BUFSIZE = 50;
	char buf[BUFSIZE];
	unsigned long recvBytes = 0, flags = 0;
	dataBuf.buf = buf;
	dataBuf.len = BUFSIZE;
	if (WSARecv(sockconn, &dataBuf, 1, &recvBytes, &flags, &overlapped, NULL) == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSA_IO_PENDING)
		{
			cout << "Background data receive" << endl;
			WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
			WSAGetOverlappedResult(sockconn, &overlapped, &recvBytes, FALSE, NULL);
		}
	}

	cout << "buf: " << buf << endl;
	WSACloseEvent(evObj);
	closesocket(sockconn);
	closesocket(sock);
	WSACleanup();
	return 0;

发送端:
    WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 0);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.0初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0)
	{
		WSACleanup();
		return 0;
	}

	//创建重叠套接字
	SOCKET sock;
	sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	myaddr.sin_port = htons(7788);
	
	if (connect(sock, (SOCKADDR*)&myaddr, sizeof(myaddr)) == SOCKET_ERROR)
	{
		cout << "connect error" << endl;
		return 0;
	}

	WSAEVENT evObj = WSACreateEvent();
	WSAOVERLAPPED overlapped;
	memset(&overlapped, 0, sizeof(overlapped));
	overlapped.hEvent = evObj;

	WSABUF dataBuf;
	const int BUFSIZE = 50;
	char buf[] = "hello!!!";
	unsigned long sendBytes = 0, flags = 0;
	dataBuf.buf = buf;
	dataBuf.len = strlen(buf) + 1;
	if (WSASend(sock, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSA_IO_PENDING)
		{
			cout << "Background data send" << endl;
			WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
			WSAGetOverlappedResult(sock, &overlapped, &sendBytes, FALSE, NULL);
		}
	}

	cout << "send data size: " << sendBytes << endl;
	WSACloseEvent(evObj);
	closesocket(sock);
	WSACleanup();
	return 0;

升级版接收端(使用lpCompletionRoutine):
const int BUF_SIZE = 50;
unsigned long recvBytes = 0, flags = 0;
char buf[BUF_SIZE];
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
	if (dwError != 0)
	{
		cout << "CompRoutine error" << endl;
	}
	else
	{
		recvBytes = szRecvBytes;
		cout << "received message: " << buf << endl;
	}
}

int main()
{
	//windows初始化SOCKET库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 0);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.0初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0)
	{
		WSACleanup();
		return 0;
	}

	//创建重叠套接字
	SOCKET sock;
	sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(7788);
	int ret = ::bind(sock, (SOCKADDR*)&myaddr, sizeof(myaddr));
	cout << "bind: " << ret << endl;

	//开始监听
	ret = ::listen(sock, 1);
	cout << "listen: " << ret << endl;

	//等待接收链接
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	SOCKET sockconn = accept(sock, (SOCKADDR*)&addrClient, &len);

	WSAEVENT evObj = WSACreateEvent();
	WSAOVERLAPPED overlapped;
	memset(&overlapped, 0, sizeof(overlapped));
	overlapped.hEvent = evObj;

	WSABUF dataBuf;
	dataBuf.buf = buf;
	dataBuf.len = BUF_SIZE;
	if (WSARecv(sockconn, &dataBuf, 1, &recvBytes, &flags, &overlapped, CompRoutine) == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSA_IO_PENDING)
		{
			cout << "Background data receive" << endl;
		}
	}

	DWORD idx = WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE);
	if (idx == WAIT_IO_COMPLETION)
		cout << "overlapped I/O Compelted" << endl;
	else
		cout << "WSARecv error" << endl;

	WSACloseEvent(evObj);
	closesocket(sockconn);
	closesocket(sock);
	WSACleanup();
	return 0;
}

纯重叠I/O实现的回声服务端

#include <winsock2.h>
//修改套接字模式
int ioctlsocket(
  SOCKET s,
  long cmd,
  u_long* argp
);

示例:
const int BUF_SIZE = 50;
void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(const char* message)
{
	cout << "ErrorHandling" << message << endl;
}

typedef struct
{
	SOCKET hClntSock;
	char buf[BUF_SIZE];
	WSABUF wsabuf;
} PER_IO_DATA, *LPPER_IO_DATA;

int main()
{
	//windows初始化SOCKET库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 0);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.0初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0)
	{
		WSACleanup();
		return 0;
	}

	//创建重叠套接字
	SOCKET sock;
	sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	unsigned long mode = 1;
	ioctlsocket(sock, FIONBIO, &mode);

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(7788);
	int ret = ::bind(sock, (SOCKADDR*)&myaddr, sizeof(myaddr));
	cout << "bind: " << ret << endl;

	//开始监听
	ret = ::listen(sock, 1);
	cout << "listen: " << ret << endl;

	//等待接收链接
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	SOCKET sockconn;
	while (1)
	{
		SleepEx(100, TRUE);
		sockconn = accept(sock, (SOCKADDR*)&addrClient, &len);
		if (sockconn == INVALID_SOCKET)
		{
			if (WSAGetLastError() == WSAEWOULDBLOCK)
				continue;
			else
				ErrorHandling("accept() error");
		}

		cout << "client conneted..." << endl;

		LPWSAOVERLAPPED lpOvLp;
		lpOvLp = new WSAOVERLAPPED();
		memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));

		LPPER_IO_DATA hbInfo = new PER_IO_DATA();
		hbInfo->hClntSock = sockconn;
		hbInfo->wsabuf.buf = hbInfo->buf;
		hbInfo->wsabuf.len = BUF_SIZE;

		lpOvLp->hEvent = (HANDLE)hbInfo;
		WSARecv(sockconn, &hbInfo->wsabuf, 1, &recvBytes, &flags, lpOvLp, ReadCompRoutine);
	}

	closesocket(sockconn);
	closesocket(sock);
	WSACleanup();
	return 0;
}

void CALLBACK ReadCompRoutine(DWORD dwErro, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
	LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
	SOCKET hSock = hbInfo->hClntSock;
	LPWSABUF bufInfo = &(hbInfo->wsabuf);
	DWORD sentBytes;

	if (szRecvBytes == 0)
	{
		closesocket(hSock);
		delete lpOverlapped->hEvent;
		delete lpOverlapped;
		cout << "client disconnected..." << hSock << endl;
	}
	else
	{
		bufInfo->len = szRecvBytes;
		WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
	}
}
void CALLBACK WriteCompRoutine(DWORD dwErro, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
	LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
	SOCKET hSock = hbInfo->hClntSock;
	LPWSABUF bufInfo = &(hbInfo->wsabuf);
	DWORD recvByes;
	DWORD flagInfo = 0;
	WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}

IOCP模型

#include <windows.h>
//创建“完成端口”
HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);
//连接完成端口对象和套接字 (同上调用参数不同)

//确认完成端口已完成的I/O和线程I/O处理
BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,
    LPDWORD lpNumberOfBytes,
    PULONG_PTR lpCompletionKey,
    LPOVERLAPPED* lpOverlapped,
    DWORD dwMilliseconds
)

示例:
#define READ 3
#define WRITE 5
DWORD recvBytes = 0, flags = 0;

typedef struct
{
	SOCKET hClntSock;
	SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct
{
	OVERLAPPED overlapped;
	WSABUF wsabuf;
	char buffer[BUF_SIZE];
	int rwMode;
} PER_IO_DATA2,*LPPER_IO_DATA2;

UINT WINAPI EchoThreadMain(LPVOID pComport)
{
	HANDLE hComPort = (HANDLE)pComport;
	SOCKET sock;
	DWORD bytesTrans;
	LPPER_HANDLE_DATA handleInfo;
	LPPER_IO_DATA2 ioInfo;

	while (true)
	{
		GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
		sock = handleInfo->hClntSock;

		if (ioInfo->rwMode == READ)
		{
			if (bytesTrans == 0)
			{
				closesocket(sock);
				delete handleInfo;
				delete ioInfo;
				continue;
			}

			cout << "received message!" << endl;
			memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
			ioInfo->wsabuf.len = bytesTrans;
			ioInfo->rwMode = WRITE;
			WSASend(sock, &ioInfo->wsabuf, 1, NULL, 0, &ioInfo->overlapped, NULL);

			ioInfo = new PER_IO_DATA2;
			memset(&ioInfo->overlapped, 0, sizeof(OVERLAPPED));
			ioInfo->wsabuf.len = BUF_SIZE;
			ioInfo->wsabuf.buf = ioInfo->buffer;
			ioInfo->rwMode = READ;
			WSARecv(sock, &ioInfo->wsabuf, 1, NULL, &flags, &ioInfo->overlapped, NULL);
		}
		else
		{
			cout << "message sent!" << endl;
			delete ioInfo;
		}
	}
	return 0;
}

int main()
{
	//windows初始化SOCKET库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 0);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.0初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0)
	{
		WSACleanup();
		return 0;
	}

	HANDLE hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	for (int i = 0; i < sysInfo.dwNumberOfProcessors; i++)
		_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);


	//创建套接字
	SOCKET sock;
	sock = WSASocket(AF_INET, SOCK_STREAM, 0,NULL,0,WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(7788);
	int ret = ::bind(sock, (SOCKADDR*)&myaddr, sizeof(myaddr));
	cout << "bind: " << ret << endl;

	//开始监听
	ret = ::listen(sock, 1);
	cout << "listen: " << ret << endl;

	//等待接收链接
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	int number = 0;
	while (true)
	{
		cout << "等待客户端连接..." << endl;
		SOCKET sockconn = accept(sock, (SOCKADDR*)&addrClient, &len);
		cout << "新连接:" << inet_ntoa(addrClient.sin_addr) << " port:" << ntohs(addrClient.sin_port) << " number:" << ++number << endl;

		LPPER_HANDLE_DATA handleinfo = new PER_HANDLE_DATA();
		handleinfo->hClntSock = sockconn;
		memcpy(&handleinfo->clntAdr, &addrClient, len);

		CreateIoCompletionPort((HANDLE)sockconn, hComPort, (DWORD)handleinfo, 0);

		LPPER_IO_DATA2 ioInfo = new PER_IO_DATA2;
		memset(&ioInfo->overlapped, 0, sizeof(OVERLAPPED));
		ioInfo->wsabuf.len = BUF_SIZE;
		ioInfo->wsabuf.buf = ioInfo->buffer;
		ioInfo->rwMode = READ;
		WSARecv(handleinfo->hClntSock, &ioInfo->wsabuf, 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
	}

	return 0;
}

简易HTTP服务端

//HTTP服务端
const int BUFSIZE = 2048;
const int BUFSMALL = 100;
void SendErrorMSG(SOCKET sock)
{
	char protocal[] = "HTTP/1.0 400 Bad Request\r\n";
	char servName[] = "Server:simple web server\r\n";
	char cntLen[] = "Content-length:2048\r\n";
	char cntType[] = "Content-type:text/html\r\n\r\n";
	char content[] = "<html><head><title>Network</title></head><body><br>发生错误!!!!</body></html>";
	send(sock, protocal, strlen(protocal), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntLen, strlen(cntLen), 0);
	send(sock, content, strlen(content), 0);
	send(sock, content, strlen(content), 0);
	closesocket(sock);
}

const char* ContentType(char* file)
{
	char extension[BUFSMALL];
	char filename[BUFSMALL];
	strcpy(filename, file);
	strtok(filename, ".");
	strcpy(extension, strtok(NULL, "."));
	if (!strcmp(extension, "html") || strcmp(extension, "htm"))
		return "text/html";
	else
		return "text/plain";
}

void SendData(SOCKET sock, char* ct, char* filename)
{
	char protocol[] = "HTTP/1.0 200 OK\r\n";
	char servName[] = "Server:simple web server\r\n";
	char cntlen[] = "Content-length:2048\r\n";
	char cntType[BUFSMALL];
	char buf[BUFSIZE];
	FILE* sendFile;
	sprintf(cntType, "Content-type:%2\r\n\r\n", ct);
	if ( (sendFile = fopen(filename, "r")) == NULL)
	{
		SendErrorMSG(sock);
		return;
	}

	send(sock, protocol, strlen(protocol), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntlen, strlen(cntlen), 0);
	send(sock, cntType, strlen(cntType), 0);
	while(fgets(buf,BUFSIZE,sendFile) != NULL)
		send(sock, buf, strlen(buf), 0);

	closesocket(sock);
}

unsigned WINAPI RequestHandler(LPVOID arg)
{
	SOCKET hClientSock = (SOCKET)arg;
	char buf[BUFSIZE];
	char method[BUFSMALL];
	char ct[BUFSMALL];
	char filename[BUFSMALL];
	recv(hClientSock, buf, BUFSIZE, 0);
	if (strstr(buf, "HTTP/") == NULL)
	{
		SendErrorMSG(hClientSock);
		closesocket(hClientSock);
		return 1;
	}

	strcpy(method, strtok(buf, " /"));
	if (strcmp(method, "GET"))
		SendErrorMSG(hClientSock);

	strcpy(filename, strtok(NULL, " /"));
	strcpy(ct, ContentType(filename));
	SendData(hClientSock, ct, filename);
}

int main()
{
	//windows初始化SOCKET库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket2.2初始化失败" << endl;
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	//创建套接字
	SOCKET sock;
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock == INVALID_SOCKET)
	{
		cout << "socket 创建失败" << endl;
		return 0;
	}

	//绑定
	SOCKADDR_IN myaddr;
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(7788);
	int ret = ::bind(sock, (SOCKADDR*)&myaddr, sizeof(myaddr));
	cout << "bind: " << ret << endl;

	//开始监听
	ret = ::listen(sock, 1);
	cout << "listen: " << ret << endl;

	//等待接收链接
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	int number = 0;
	while (true)
	{
		cout << "等待客户端连接..." << endl;
		SOCKET sockconn = accept(sock, (SOCKADDR*)&addrClient, &len);
		cout << "新连接:" << inet_ntoa(addrClient.sin_addr) << " port:" << ntohs(addrClient.sin_port) << " number:" << ++number << endl;

		DWORD dwThreadId;
		HANDLE nThread = (HANDLE)_beginthreadex(NULL, 0, RequestHandler, (LPVOID)sockconn, 0, (unsigned*)&dwThreadId);
	}

	closesocket(sock);
	WSACleanup();

	return 0;
}


//index.html
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio">
<TITLE></TITLE>
</HEAD>
<BODY>
HELLO WORLD!!!
</BODY>
</HTML>

运行服务端在浏览器中输入http://127.0.0.1:7788/index.html即可

PS:做此笔记以便用到时参考。有需要的朋友还请参阅原书《TCP/IP网络编程》!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章