《网络多人游戏架构与编程》之伯克利套接字

一、创建、销毁、绑定、发送、接收
 
  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个...
    • 缺点:每个客户端需要1个线程,不利于扩展和管理。
  2. 非阻塞I/O
    • 使用ioctlsocket设置socket为非阻塞模式:int ioctlsocket(SOCKET sock, long cmd, u_long *argp);  //cmd是控制参数;argp是该参数的取值,0阻止开启非阻塞,其他值开启。
    • 优点:每帧检查是否有准备好的待接收数据,有,先处理第一个挂起;没有,进行到下一帧,没有等待。
    • 缺点:轮询的socket数量很大时,效率很低。
  3. 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);
posted @ 2022-05-27 11:06  番茄玛丽  阅读(73)  评论(0编辑  收藏  举报