目录
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网络编程》!