网络编程 基于tcp/ip协议的C/S模型
创始人
2024-04-30 15:04:24
0

1.socket编程的一些概念

        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简单的通信流程

2.tcp/ip协议的基本知识

3.网络编程代码

        1.头文件

c++网络编程有两个版本的头文件,第一版 ,第二版 ,现在使用一般都是使用第二个版本,网络库也有两个版本,第一版 "wsock32.lib", 第二版 "ws2_32.lib",这两个版本都能用

#include//第一版
#include//第二版
#pragma comment(lib,"wsock32.lib")//第一版 网络库
#pragma comment(lib,"ws2_32.lib")//第二版

        2.WSAStartup()函数的使用

        该函数的作用是用进程启动网络库(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 套接字实现支持的任务数的限制。
WSAEFAULTlpWSAData 参数不是有效的指针。

代码样例

#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;
}

        3.SOCKET函数的使用

        1.socket介绍

         2.socket()函数

该函数创建一个绑定到特定传输服务提供者的套接字,函数原型

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 1Internet控制报文协议(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);//销毁套接字的函数

4.bind()函数使用

该函数将本地地址与套接字相关联。函数原型

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));

5.listen()函数的使用

该函数将套接字置于侦听传入连接的状态(就是等待客户端连接),函数原型

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();
}

6.accept函数使用

该函数允许在套接字上尝试传入连接,即把接收到的客户端信息创建成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();}

7.recv()函数使用

该函数从连接的套接字或绑定的无连接套接字接收数据,即得到指定客户端发来的消息,本质是由协议本身去做的,也就是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);}

8.send()函数使用

该函数在连接的套接字上发送数据,即把要发送的数据发送给客户端,函数原型

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();//拿到错误码//需要根据实际情况处理}

9.connect()函数的使用

该函数在客户端中使用,建立与指定套接字的连接,即连接指定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;}

10.模型缺点

        客户端与服务器连接后,数据的传送是你一发,我一发的,这样就就造成如果一方没有发送数据,另一方就会死等的情况,如果在等待数据的过程中又有其它的连接请求,会出现无法处理的情况。

11.tcp/ip代码

服务端代码

#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;
}

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...