目錄
WSAStringToAddress & WSAAddressToString
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慢的原因
- 收發數據前後進行的連接設置及清除過程。
- 收發數據過程中爲保證可靠性而添加的流控制
UDP套接字的已連接和未連接
未連接UDP
每次發送數據時(sendto)要經歷三個步驟:
- 向UDP套接字註冊目標IP和端口號
- 傳輸數據。
- 刪除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模型
調用順序
- 設置文件描述符
- 指定監聽範圍
- 設置超時
- 調用select函數
- 查看調用結果
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次數據,該組內所有客戶端都會收到數據
- 多播組可在IP地址範圍內任意增加
- 加入特定組即可接收發往該多播組的數據
多播組是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網絡編程》!