《网络多人游戏架构与编程》之伯克利套接字
一、创建、销毁、绑定、发送、接收
SOCKET socket(int af,int type,int protocol);
af: 协议簇
宏
|
含义 |
AF_UNSPEC
|
未指定
|
AF_TNET
|
IPV4
|
AF_IPX
|
分组交换
|
AF_APPLETALK
|
Appletalk协议
|
AF_INET6
|
IPV6
|
type: 通过socket发送和接收分组的形式
宏
|
含义
|
SOCK_STREAM
|
有序的、可靠的数据流分段
|
SOCK_DGRAM
|
离散的报文
|
SOCK_RAW
|
包头可以由应用层自定义
|
SOCK_SEQPACKET
|
类似SOCK_STREAM,但要整体读取数据包
|
protocol:socket应使用的协议,包括传输层协议、各种实用网络层协议。
宏
|
需要的类型
|
含义
|
IPPROTO_UDP
|
SOCK_DGRAM
|
UDP数据报
|
IPPROTO_TCP
|
SOCK_STREAM
|
TCP报文段
|
IPPRPTO_IP/0
|
Any
|
实用默认协议
|
///////////////创建SOCKET/////////////////
创建1个IPV4 UDP socket : SOCKET udpSocket = socket( AF_INET, SOCK_DGRAM, 0);
创建1个TCP socket:SOCKET tcpSocket = socket( AF_INET, SOCK_STREAM, 0);
///////////停止传输和接收SOCKET//////////
int shutdown(SOCKET sock, int how);
————————————
how: SD_SEND 停止发送;产生FIN数据包,所有数据发送后发送这个数据包,通知另一端关闭socket,然后另一端会回一个FIN数据包,我们就可以关闭socket了。
SD_RECEIVE 停止接收
SD_BOTH 停止发送和接收
///////////////关闭SOCKET/////////////////
关闭不考虑类型:int closesocket (SOCKET sock);
————————————
Tips:关闭前保证所有发送的和接收的数据都已经传输和确认。
///////////////绑定SOCKET/////////////////
返回值:0 成功 ; -1 错误
///////////////UDP发送数据/////////////////
int sendto(SOCKET sock, const char *buf, int len, int flags, const sockaddr *to, int tolen);
buf:指向待发送数据起始地址的指针;
len:待发送数据的大小;避免发送大于1300字节的数据包。
flags:对发送标志进行按位或运算的结果,游戏中通常是0。
to:接收端的sockaddr。
tolen:to指向的sockaddr的大小。对于IPV4,等于sizeof(sockaddr_in)。
返回值:成功:等待发送数据长度; 失败:-1。
///////////////UDP接收数据/////////////////
int recvfrom(SOCKET sock, const char *buf, int len, int flags, const sockaddr *from, int *fromlen);
buf:接收的数据包的缓冲区;
len:buf可以存储的最大字节数。
flags:对接收标志进行按位或运算的结果,游戏中通常是0。
from:指向sockaddr的指针。
fromlen:from指向的sockaddr的大小。
返回值:成功:复制到buf的字节数; 失败:-1。
///////////////TCP启动监听/////////////////
int listen(SOCKET sock, int backlog);
backlog:允许传入的最大连接数。SOMAXCONN表示默认值。
返回值:成功 0;错误 -1。
///////////////TCP接收连接/////////////////
SOCKET accept(SOCKET sock, sockaddr *addr, int *addrlen);
///////////////TCP发起链接/////////////////
SOCKET connect(SOCKET sock, const sockaddr *addr, int *addrlen);
///////////////TCP发送数据/////////////////
int send(SOCKET sock, const char *buf, int len, int flags);
///////////////TCP接收数据/////////////////
int recv(SOCKET sock, char *buf, int len, int flags);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
二、阻塞和非阻塞I/O
三种解决线程阻塞的办法:
-
多线程
-
目的:给每一个可能的阻塞调用生成一个线程:每个客户端1个,监听1个...
-
缺点:每个客户端需要1个线程,不利于扩展和管理。
-
非阻塞I/O
-
使用ioctlsocket设置socket为非阻塞模式:int ioctlsocket(SOCKET sock, long cmd, u_long *argp); //cmd是控制参数;argp是该参数的取值,0阻止开启非阻塞,其他值开启。
-
优点:每帧检查是否有准备好的待接收数据,有,先处理第一个挂起;没有,进行到下一帧,没有等待。
-
缺点:轮询的socket数量很大时,效率很低。
-
select函数
-
优点:可以同时检查多个socket,只要有1个准备好了就开始执行
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
三、平台差异
///////////////Windows头文件/////////////////
Windows平台使用头文件 WinSock2.h ,包含了socket相关的函数声明和数据类型。
————————————
Windows.h是旧版本,和Winsock2.h一起用会造成命名冲突,想要避免冲突就要在Windows.h之前引用Winsock2.h
///////////////Windows激活socket/////////////////
使用WSAStartup激活Windows上的socket库: int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
返回值:0 或 错误代码。
必须先成功启动WSAStartup, 才能正确运行Winsock2中的函数。
————————————
wVersionRequested 是两字节的WORD,低字节表示主版本号,高字节表示Winsock实现的最低版本。
lpWSAData 指向Windows特定的数据结构。
WSAStartup填入被激活的socket库的信息。
///////////////Windows关闭socket/////////////////
int WSACleanup(); //结束所有未完成的socket操作,释放所有socket资源
返回值:错误代码,通常返回-1,要找到错误来源要在返回-1后调用WSAGetLastError()。
————————————
int WSAGetLastError(); 返回当前运行线程最近的错误代码
///////////////Windows数据包地址/////////////////
存放地址信息的数据类型:struct sockaddr {
uint16_t sa_family; //指定地址类型,应与af一致。
char sa_data[14]; //存储真正的地址。
};
创建1个IPV4数据包的地址:struct sockaddr_in {
short sin_family; //指定地址类型,应与af一致。
uint16_t sin_port; //存储地址中16位端口
struct in_addr sin_addr; //存储4字节IPV4地址;in_addr 在不同平台有差异。
char sin_zero[8]; //存储真正的地址。
};
IP地址从字符串表示转换为 in_addr 表示:inet_pton POSIX系统,InetPton Windows系统
src:存储.分隔的地址
dst:指向待赋值的sin_addr字段
返回值:1 成功;0 源字符串错误;-1 其他
将域名解析为IP地址:
///////////////socket字节序/////////////////
原因:TCP/IP和主机可能存在多字节数的字节序上采用不同标准
解决:多字节数赋值必须将 主机字节序 转换为 网络字节序
方法:uint16_t htons( uint16_t hostshort);
uint16_t htonl( uint16_t hostlong);
网络字节序 转换成 主机字节序:
uint16_t ntohs( uint16_t networkshort);
uint16_t ntohl( uint16_t networklong);