socket作用:运行在计算机中的两个程序通过socket建立起一个通道,数据在通道中传输。socket提供了流(stream),是基于TCP协议,是一个有序、可靠、双向字节流的通道,传输数据不会丢失、不会重复、顺序也不会错乱。
TCP与UDP的区别:
1.TCP基于连接与UDP无连接
2.对系统资源的要求(TCP 较多,UDP 少)
3.UDP 程序结构较简单
4.流模式(TCP)与数据报模式(UDP)
TCP 保证数据正确性,UDP可能丢包
TCP 保证数据顺序,UDP 不保证
具体编程时的区别
1.socket的参数不同
2.UDP Server不需要调用 listen 和accept
3.UDP 收发数据用 sendto/recvfrom函数
4.TCp:地址信息在 connect/accept 时确定,UDP :在sendtorecvfrom函数中每次均 需指定地址信息5.UDP : shutdown 函数无效
socket中的一些名词解释:
多重协议支持: 通过SPI接口支持其他协议;
多重命名空间: 根据服务与主机名选择协议
重叠I/0模式: 增强I/0香叶量与提高性能
分散与聚合:从多个缓冲区发送与接收数据
有条件接受:有选择性地决定是否接受连接
套接字共享:多个进程共享一个套接字句柄
socket简单的通信流程
c++网络编程有两个版本的头文件,第一版
#include//第一版
#include//第二版
#pragma comment(lib,"wsock32.lib")//第一版 网络库
#pragma comment(lib,"ws2_32.lib")//第二版
该函数的作用是用进程启动网络库(Winsock DLL),函数的原型
int WSAAPI WSAStartup([in] WORD wVersionRequested,[out] LPWSADATA lpWSAData
);
WORD类型实际上是一个 unsigned short 类型,参数wVersionRequested需要指定网络库的版本号,参数lqWSAData是一个结构体类型,返回的各种版本信息会存入结构体中的成员。
函数如果执行成功会返回一个0,失败会返回错误信息,返回的错误信息有一些几类
错误代码 | 含义 |
---|---|
WSASYSNOTREADY | 基础网络子系统尚未准备好进行网络通信 |
WSAVERNOTSUPPORTED | 此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本。 |
WSAEINPROGRESS | 正在执行阻止 Windows 套接字 1.1 操作。 |
WSAEPROCLIM | 已达到 Windows 套接字实现支持的任务数的限制。 |
WSAEFAULT | lpWSAData 参数不是有效的指针。 |
代码样例
#include
#include //网络头文件 window socket 第二版
#pragma comment(lib,"ws2_32.lib")//包含网络库的名字, ws2_32.lib是网络库 名字 //不区分大小写
//#pragma comment(lib, "wsock32.lib") //第一版的库int main()
{//WSAstartup函数有两个参数,一个是 WORD 类型,一个是 LPWSADATA 结构体类型 在文档中 LP 开头的变量表示需要的地址变量WORD wdVersion = MAKEWORD(2, 2);//选定版本号 这里表示2.2版本//LPWSADATA和WSADATA*等价//LPWSADATA lwq = malloc(sizeof(WSADATA));//创建一个堆区WSADATA wdSockMsg;int nRes = WSAStartup(wdVersion, &wdSockMsg);//有返回值,启动成功返回0if (nRes != 0){//这里是5个错误码switch (nRes){case WSASYSNOTREADY:printf("基础网络子系统尚未准备好进行网络通信");break;case WSAVERNOTSUPPORTED:printf("此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本");break;case WSAEINPROGRESS:printf("正在执行阻止 Windows 套接字 1.1 操作。 ");break;case WSAEPROCLIM:printf("已达到 Windows 套接字实现支持的任务数的限制。");break;case WSAEFAULT:printf("lpWSAData 参数不是有效的指针");break;}}//校验版本//HIBYTE表示高位:是主版本,LOBYTE表示地位:是副版本if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion)){//版本不对WSACleanup();//关闭网络库函数return 0;}system("pause");return 0;
}
该函数创建一个绑定到特定传输服务提供者的套接字,函数原型
SOCKET WSAAPI socket([in] int af,[in] int type,[in] int protocol
);
参数1 af:是地址的类型参数
参数名称 | 含义 | 例子 |
---|---|---|
AF_INET 2 | 表示IPV4的地址,4个字节,32位的地址 | 192.168.179.132 |
AF_INET6 23 | 表示IPV6的地址,16个字节,128位地址 | fh40::4fd5:cgya:85b:84a5%20 |
AF_BTH 32 | 蓝牙地址系列 | 6D:2D:BC:AA:8G:11 |
参数2 type:套接字类型
类型 | 含义 |
---|---|
SOCK_STREAM 1 | 一种套接字类型,使用OOB数据传输机制提供有序、可靠、双向、基于连接的字节流。这种套接字类型为Internet地址族(AF_INET或AF_INET6)使用传输控制协议(TCP)。 |
SOCK_DGRAM 2 | 一种支持数据报的套接字类型,数据报是固定(通常是小的)最大长度的无连接、不可靠的缓冲区。这种套接字类型使用用户数据报协议(UDP)作为Internet地址族(AF_INET或AF_INET6)。 |
SOCK_RAW 3 | 一种套接字类型,提供一个原始套接字,允许应用程序操作下一个上层协议头。要操作IPv4报头,必须在套接字上设置IP_HDRINCL套接字选项。要操作IPv6报头,必须在套接字上设置IPV6_HDRINCL套接字选项。 |
SOCK_RDM 4 | 提供可靠消息数据报的套接字类型。这种类型的一个例子是Windows中的实用通用多播(Pragmatic General Multicast, PGM)多播协议实现,通常称为可靠的多播编程。
|
SOCK_SEQPACKET 5 | 一种套接字类型,提供基于数据报的伪流包。 |
参数3 protocol:协议的类型
类型 | 含义 |
---|---|
IPPROTO_TCP 6 | 传输控制协议(TCP)。当af参数为AF_INET或AF_INET6,类型参数为SOCK_STREAM时,这个值是可能的。 |
IPPROTO_UDP 17 | 用户数据报协议(UDP)。当af参数为AF_INET或AF_INET6,类型参数为SOCK_DGRAM时,这个值是可能的。 |
IPPROTO_IGMP 2 | Internet组管理协议(IGMP)。当af参数为AF_UNSPEC、AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这个值是可能的。 |
IPPROTO_ICMP 1 | Internet控制报文协议(ICMP)。当af参数为AF_UNSPEC、AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这个值是可能的。 |
返回值
socket函数如果成功返回一个可用的socket,如果失败会返回 INVALID_SOCKET ,表示失败,可以调用函数 WSAGetLastError() 获取错误码。
注意:在最后一定要调用函数 closesocket() 函数销毁socket。
代码样例
//SOCKET的使用SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//三个参数 如果成功会返回一个可以的socket,//在最后一定要销毁socket套接字if (INVALID_SOCKET == socketServer){int a = WSAGetLastError();//当socket函数绑定传输协议套接字失败,会调用该函数,返回错误码。WSACleanup();return 0;}closesocket(socketServer);//销毁套接字的函数
该函数将本地地址与套接字相关联。函数原型
int WSAAPI bind([in] SOCKET s,[in] const sockaddr *name,[in] int namelen
);
参数1 SOCKET:是socket()函数返回的值
参数2 name:是ip地址
参数3 namelen:空间大小
参数2 name 是 sockaddr类型,这是网络库中内置的结构体类型,如下
typedef struct sockaddr_in {short sin_family;u_short sin_port;struct in_addr sin_addr;char sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;
sin_family是一个与socker函数中第一个参数一样的变量
sin_port表示指定的端口号
sin_addr表示指定的ip地址
返回值
函数执行成功会返回0,失败会返回一个SOCKET_ERROR,实际上是一个-1
bind函数使用实例
//bing函数的使用
struct sockaddr_in si;//定义一个结构体
si.sin_family = AF_INET;//需要和socket函数中的参数一致
si.sin_port = htons("12345");//指定的端口号
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定ip地址
bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in));
该函数将套接字置于侦听传入连接的状态(就是等待客户端连接),函数原型
int WSAAPI listen([in] SOCKET s,[in] int backlog
);
参数1 s:是socket函数的返回值
参数2 backlog:挂起的连接队列的最大长度
返回值:连接成功返回0,失败返回SOCKET_ERROR
listen函数使用实例
//listen()函数的使用
//listen(socketServer, SOMAXCONN);
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{//出错了int a = WSAGetLastError();//释放closesocket(socketServer);//释放网络库WSACleanup();
}
该函数允许在套接字上尝试传入连接,即把接收到的客户端信息创建成socket,函数原型
SOCKET WSAAPI accept([in] SOCKET s,[out] sockaddr *addr,[in, out] int *addrlen
);
参数1 s:是socket函数的返回值
参数2 addr:是sockaddr定义的结构体对象
参数3 addrlen:第一个参数的空间长度
返回值:连接成功返回给客户端包好的socket,失败返回 INVALID_SOCKET,表示无效
注意:该函数在没有客户端连接时,会一直阻塞,直到有客户端连接
accept函数代码样例
//accept()函数的使用//创建客户端连接struct sockaddr_in clientMsg;//定义sockaddr_in的结构体类型int len = sizeof(clientMsg);//作为参数传入到accept函数//accept(socketServer,NULL,NULL);//可以这样SOCKET socketClient = accept(socketServer, (struct sockaddr*)&clientMsg, &len);//成功返回一个客户端socketif (INVALID_SOCKET == socketClient){//出错了int a = WSAGetLastError();//释放closesocket(socketServer);//释放网络库WSACleanup();}
该函数从连接的套接字或绑定的无连接套接字接收数据,即得到指定客户端发来的消息,本质是由协议本身去做的,也就是socket底层实现,函数原型
int WSAAPI recv([in] SOCKET s,[out] char *buf,[in] int len,[in] int flags
);
参数1 s:是收到信息的socket
参数2 buf:收到信息时,内容的保存空间
参数3 len:每次接收信息的字节大小
参数4 flags:一般为0,表示在系统缓冲区中读完就删除缓冲,如果传入参数 MSG_WAITALL 表示接收到的信息必须是参数3 len的大小才可以执行,否则会被阻塞
返回值:如果执行没有错误,返回的是收到的字节个数,如果连接正常关闭返回值为0,如果出现错误返回值SOCKET_ERROR,使用WSAGetLastError()函数可以拿到错误码
recv函数代码实例
//recv()函数使用char buf[1500] = { 0 };//作为收到信息的存储空间int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小if (res == 0){printf("连接中断,客户端下线\n");}else if (SOCKET_ERROR == res){//出错了int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}else{printf("%d %s\n", res, buf);}
该函数在连接的套接字上发送数据,即把要发送的数据发送给客户端,函数原型
int WSAAPI send([in] SOCKET s,[in] const char *buf,[in] int len,[in] int flags
);
参数1 s:客户端socket
参数2 buf:要发送的字符数组
参数3 len:一次发送的字节个数
参数4 flags:一般为0
返回值:发送成功返回发送的字节个数,发送失败返回SOCKET_ERROR
send函数实例
//send()函数使用if (SOCKET_ERROR == send(socketClient, "abcd\0qwer", sizeof("abcd\0qwer"), 0))//成功会返
回发送出去的字节个数,一次发送出去的字节个数应该在15000以内{//发送不成功printf("发送出错\n");int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}
该函数在客户端中使用,建立与指定套接字的连接,即连接指定ip地址和端口号的服务器,函数原型
int WSAAPI connect([in] SOCKET s,[in] const sockaddr *name,[in] int namelen
);
参数1 s:socket函数返回的值
参数2 name:struct sockaddr定义的结构体对象
参数3 namelen:第二个参数所占用的空间长度
返回值:连接成功返回0,不成功返回SOCKET_ERROR,需要使用WSAGetLastError()获取错误码,在工具中的错误查找,可以找到具体原因
connect函数代码实例
//连接到服务器struct sockaddr_in serverMsg;serverMsg.sin_family = AF_INET;serverMsg.sin_port = htons("12345");serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&serverMsg, sizeof(serverMsg))){int a = WSAGetLastError();//获取错误码printf("连接出错\n");closesocket(socketClient);WSACleanup();return 0;}
客户端与服务器连接后,数据的传送是你一发,我一发的,这样就就造成如果一方没有发送数据,另一方就会死等的情况,如果在等待数据的过程中又有其它的连接请求,会出现无法处理的情况。
服务端代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include
#include
#include //网络头文件 window socket 第二版
#pragma comment(lib,"ws2_32.lib")//包含网络库的名字, ws2_32.lib是网络库 名字 //不区分大小写
//#pragma comment(lib, "wsock32.lib") //第一版的库int main()
{//第一步打开网络库//WSAstartup函数有两个参数,一个是 WORD 类型,一个是 LPWSADATA 结构体类型 在文档中 LP 开头的变量表示需要的地址变量WORD wdVersion = MAKEWORD(2, 2);//选定版本号 这里表示2.2版本//LPWSADATA和WSADATA*等价//LPWSADATA lwq = malloc(sizeof(WSADATA));//创建一个堆区WSADATA wdSockMsg;int nRes = WSAStartup(wdVersion, &wdSockMsg);//有返回值,启动成功返回0if (nRes != 0){//这里是5个错误码switch (nRes){case WSASYSNOTREADY:printf("基础网络子系统尚未准备好进行网络通信");break;case WSAVERNOTSUPPORTED:printf("此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本");break;case WSAEINPROGRESS:printf("正在执行阻止 Windows 套接字 1.1 操作。 ");break;case WSAEPROCLIM:printf("已达到 Windows 套接字实现支持的任务数的限制。");break;case WSAEFAULT:printf("lpWSAData 参数不是有效的指针");break;}printf("打开网络库出错\n");return 0;}//第二步 校验版本if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))//HIBYTE表示高位:表示主版本,LOBYTE表示地位:表示副版本{//版本不对WSACleanup();//关闭网络库函数printf("校验版本出错\n");return 0;}//第三步 创建SOCKET//SOCKET的使用SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//三个参数 如果成功会返回一个可以的socket,//在最后一定要销毁socket套接字if (INVALID_SOCKET == socketServer){int a = WSAGetLastError();//当socket函数绑定传输协议套接字失败,会调用该函数,返回错误码。WSACleanup();printf("创建socket出错\n");return 0;}//struct sockaddr_in si;//si.sin_family = AF_INET;//si.sin_port = htons(27015);//struct in_addr s;//存放Ipv4地址的结构体//inet_pton(AF_INET, "127.0.0.1", (void*)&si.sin_addr.S_un.S_addr);//进行转换//第四步 绑定地址与端口号//bing函数的使用struct sockaddr_in si;//定义一个结构体si.sin_family = AF_INET;//需要和socket函数中的参数一致si.sin_port = htons(12332);//指定的端口号si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定ip地址//bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in));//返回值,成功返回0,不成功返回SOCKET_ERROR实际上是一个-1if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in))){//出错了int a = WSAGetLastError();//释放closesocket(socketServer);//释放网络库WSACleanup();printf("绑定ip和端口号出错\n");return 0;}//第五步 开始监听//listen()函数的使用//listen(socketServer, SOMAXCONN);if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)){//出错了int a = WSAGetLastError();//释放closesocket(socketServer);//释放网络库WSACleanup();printf("监听函数出错\n");return 0;}//第六步 创建客户端socket/接受连接//accept()函数的使用//创建客户端连接struct sockaddr_in clientMsg;//定义sockaddr_in的结构体类型int len = sizeof(clientMsg);//作为参数传入到accept函数//accept(socketServer,NULL,NULL);//可以这样SOCKET socketClient = accept(socketServer, (struct sockaddr*)&clientMsg, &len);//成功返回一个客户端socketif (INVALID_SOCKET == socketClient){//出错了int a = WSAGetLastError();//释放closesocket(socketServer);//释放网络库WSACleanup();printf("连接函数出错\n");return 0;}printf("服务器\n");char buf[1500] = { 0 };//作为收到信息的存储空间int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小if (res == 0){printf("连接中断,客户端下线\n");}else if (SOCKET_ERROR == res){//出错了int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}else{printf("%d %s\n", res, buf);}if (SOCKET_ERROR == send(socketClient, "我是服务器,我收到了你的消息", sizeof("我是服务器,我收到了你的消息"), 0))//成功会返回发送出去的字节个数,一次发送出去的字节个数应该在15000以内{//发送不成功printf("发送出错\n");int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}//循环一下while (1){//第七步 与客户端收发消息//recv()函数使用int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小if (res == 0){printf("连接中断,客户端下线\n");}else if (SOCKET_ERROR == res){//出错了int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}else{printf("%d %s\n", res, buf);}scanf("%s", buf);//send()函数使用if (SOCKET_ERROR == send(socketClient, buf, strlen(buf), 0))//成功会返回发送出去的字节个数,一次发送出去的字节个数应该在15000以内{//发送不成功printf("发送出错\n");int a = WSAGetLastError();//拿到错误码//需要根据实际情况处理}}closesocket(socketServer);//销毁套接字的函数 释放成功返回0closesocket(socketClient);WSACleanup();//清理网络库 释放成功返回0system("pause");return 0;
}
客户端代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include
#include
#include
#pragma comment(lib, "ws2_32.lib")int main()
{//第一步 打开网络库WORD wdVersion = MAKEWORD(2, 2);WSADATA wdSocketMsg;int nRes = WSAStartup(wdVersion, &wdSocketMsg);if (nRes != 0){printf("打开失败");return 0;}//第二部 校验版本if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2){//版本不对WSACleanup();//关闭网络库printf("版本出错\n");return 0;}//第三部 创建socketSOCKET socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//还是服务器的socketif (socketClient == INVALID_SOCKET){//创建了无效的socketint a = WSAGetLastError();//获取错误码printf("创建出错\n");WSACleanup();return 0;}//第四步 连接到服务器struct sockaddr_in serverMsg;serverMsg.sin_family = AF_INET;serverMsg.sin_port = htons(12332);serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&serverMsg, sizeof(serverMsg))){int a = WSAGetLastError();//获取错误码printf("连接出错\n");closesocket(socketClient);WSACleanup();return 0;}printf("客户端\n");if (SOCKET_ERROR == send(socketClient, "我是客户端", sizeof("我是客户端"), 0))//发送成功会返回发送的字节个数{//发送失败printf("发送出错\n");int a = WSAGetLastError();//获取错误码}//循环一下while (1){//第五步 与服务起收发消息char buf[1500] = { 0 };int res = recv(socketClient, buf, 1499, 0);if (res == 0){printf("发送中断,服务器断开连接\n");}else if (res == SOCKET_ERROR){//连接出错int a = WSAGetLastError();//获取错误码printf("接收出错\n");}else{printf("%d %s\n", res, buf);}scanf("%s", buf);if (SOCKET_ERROR == send(socketClient, buf, strlen(buf), 0))//发送成功会返回发送的字节个数{//发送失败printf("发送出错\n");int a = WSAGetLastError();//获取错误码}}closesocket(socketClient);WSACleanup();system("pause");return 0;
}
上一篇:【QT】信号与槽