Socket
目录
- 目录
- 编辑信息
- 说明
- 笔记
- 函数更新
- 参考
编辑信息
Author:chemoontheshy
Create time:2021/07/09
End time:2021/07/09
说明
本文为socket基本知识。主要window系统。
笔记
基础名词:
IP地址:(IP Adrress)
端口:Port
-
协议(Protocol)
- 网络通信的约定,通信的双方必须都遵守才能正常收发数据。
- TCP、 UDP 、 IP
-
数据传输方式
-
SOCK_STREAM
表示面向连接 的数据传输方式。
数据在传输过程中不会消失。
数据是按照顺序传输的。
数据的发送和接收不是同步的。
-
SOCK_DGRAM
- 表示无连接的数据传输方式。
- 强调快速传输而非传输顺序。
- 传输的数据可能丢失也可能损毁。
- 数据的发送和接收时同步的。
-
-
TCP/IP:
- TCP:协议,全称:The Transmission Control Protocol 传输控制协议。TCP会控制你的数据按照顺序到达并且没有错误。TCP用来确保数据的正确性。
- IP:协议,Internert Protocol 网络协议。用于控制数据如何从源头到达目的地。也就是常说的路由。
- TCP用来确保数据的正确性。
-
网络三要素
- MAC
- Media Access Control Address 媒体访问控制地址
- LAN Address 局域网
- Ethernet Address 以太网地址
- Physical Address 物理地址
- IP Internert Protocol 网络协议
- Port
- MAC
socket流程:
-
需要引入WinSock的动态链接库(DLL)。
- vs2019可以在“项目--属性--链接器--输入--附加依赖项”引入
Ws2_32.lib
- vs2019可以在“项目--属性--链接器--输入--附加依赖项”引入
-
初始化 DLL
int main() { //初始化 DLL WSADATA wsaData; WSAtartup(MAKEWORD(2,2),wsaData); ... //终止 DLL 使用 WSACleanup(); return 0; }
-
创建套接字socket()
/// <summary> /// 创建套接字 /// </summary> /// <param name="af">IP地址类型;常用的有AF_INET和AF_INET6,PF_INET等价于AF_INET</param> /// <param name="type">数字传输方式;常用有SOCK_STREAM(流格式套接字/面向连接的套接字)和 /// SOCK_DGRAM(数据报套接字/无连接的套接字)</param> /// <param name="protocol">传输协议;常用的有IPPROTO_TCP和IPPTOTO_UDP,分别表示TCP传输协议和UDP传输协议 由于一般能根据type来自动推断类型,故默认填写0,可以不填写参数,特殊情况不可以:有两种不同的协议支持同一种地址类型和数据类型传输类型</param> /// <returns>正常返回>0,错误返回-1(INVALID_SOCKET)</returns> static SOCKET creatSocket(int af, int type, int protocol=0) { SOCKET sock=socket(af,type,protocol); if(sock == INVALID_SOCKET) { return INVALID_SOCKET; } return sock; }
-
绑定端号bind()和connect()
服务端为bind(),客服端为connect();
/// <summary> /// 将创建的套接字与IP地址、端口绑定。 /// </summary> /// <param name="sock">创建的套接字</param> /// <param name="ip">ip地址</param> /// <param name="port">端口</param> /// <returns>正常返回true,错误返回false</returns> bool STCP::Bind(SOCKET sock, const char* ip, uint16_t port) { sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); //INADDR_ANY 就是指地址位0.0.0.0 addr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) { printf("TCP::bind() failed.\n"); return false; } return true; } /// <summary> /// sockaddr_in结构体 /// </summary> typedef struct sockaddr_in { ADDRESS_FAMILY sin_family; //地址族,地址类型 USHORT sin_port; //16位的端口号 IN_ADDR sin_addr; //32位IP地址 CHAR sin_zero[8]; //不使用,一般填充0 } SOCKADDR_IN, *PSOCKADDR_IN; /// <summary> /// 将创建的套接字与IP地址、端口绑定。 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="ip">sockaddr结构体变量的指针</param> /// <param name="port">端口</param> /// <returns>正常返回>0,错误返回-1(INVALID_SOCKET)</returns> int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows /// <summary> /// 将创建的套接字与IP地址、端口绑定。 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="ip">sockaddr结构体变量的指针</param> /// <param name="port">端口</param> /// <returns>正常返回>0,错误返回-1(INVALID_SOCKET)</returns> int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
-
开始监听listend()和接收数据accpet()
对于服务端程序使用bind()绑定套接字后,还需要使用listen()函数进入被动监听状态,再调用accepet()函数就可以随时响应客户端的请求了。
/// <summary> /// 进入监听状态 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="backlog;"></param> /// <param name="port">端口</param> /// <returns>正常返回>0,错误返回-1(INVALID_SOCKET)</returns> int listen(SOCKET sock, int backlog); //Windows /// <summary> /// 进入监听状态 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="sockaddr">sockaddr结构体变量的指针</param> /// <param name="addrlen">地址长度</param> /// <returns>正常返回>0,错误返回-1(INVALID_SOCKET)</returns> SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
-
发送与接收
/// TCP /// <summary> /// 进入监听状态 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="buf">数据指针头</param> /// <param name="len">数据的长度</param> /// <param name="flags">一般为0或者flags</param> /// <returns>正常返回>0,发送数据的长度,否则-1</returns> int send(SOCKET sock, const char *buf, int len, int flags); //Windows /// <summary> /// 进入监听状态 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="buf">数据指针头</param> /// <param name="len">数据的长度</param> /// <param name="flags">一般为0或者flags</param> /// <returns>正常返回>0,接收到数据的长度,否则-1</returns> int recv(SOCKET sock, char *buf, int len, int flags); //Windows //UDP /// <summary> /// 发送数据 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="buf">保存待传输数据的缓冲区地址</param> /// <param name="len">带传输数据的长度(以字节计)</param> /// <param name="flags">可选项参数,若没有可传递0</param> /// <param name="to">可选项参数,若没有可传递0</param> /// <param name="addrlen">传递给参数 to 的地址值结构体变量的长度</param> /// <returns>正常返回>0,发送数据的长度,否则-1</returns> int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen); //Windows /// <summary> /// 接收数据 /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> /// <param name="buf">保存待传输数据的缓冲区地址</param> /// <param name="nbytes">可接收的最大字节数(不能超过buf缓冲区的大小)</param> /// <param name="flags">可选项参数,若没有可传递0</param> /// <param name="from">存有发送端地址信息的sockaddr结构体变量的地址</param> /// <param name="addrlen">保存参数 from 的结构体变量长度的变量地址值</param> /// <returns>正常返回>0,接收数据的长度,否则-1</returns> int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen); //Windows
-
结束
/// <summary> /// 关闭socket /// </summary> /// <param name="sock">创建的套接字(文件描述符)</param> closesocket(sock);
函数更新
//旧函数,不建议使用
//sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//新函数
inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr);