《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網絡編程》!

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