VS下C++网络编程

网络编程


1. TCP与UDP的比较

TCP是面向连接的,交互双方的进程各自建立一个流式套接字,服务器需要等待客户端向其提出连接申请。一旦接受客户端申请就立刻返回一个新的套接字描述符。通过该描述符调用数据传输函数与客户端进行数据的收发。

UDP是面向无连接的,双方建立的是数据报套接字,服务器和客户端在进行传描数据之前不需要进行连接的申请和建立,可以随时向对方发消息。

 

TCP

优点:可靠、稳定

缺点:速度慢,效率低、占用系统资源高、易被攻击。

适合场景:网络通讯质量要求高(可靠、稳定)

 

UDP

优点:速度快,比TCP稍安全

缺点:不可靠、不稳定

适用场合:网络通讯质量要求不高,速度快。

2. Socket粘包问题

什么时候需要考虑粘包问题

1:如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符;

2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包;

3:如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:

 1)"hellogive me sth abour yourself" 

 2)"Don'tgive me sth abour yourself" 

  那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon't give me sth abouryourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。

 

粘包出现原因:

在流传输中出现,UDP不会出现粘包,因为它有消息保护边界。

1 发送端需要等缓冲区满才发送出去,造成粘包

2 接收方不及时接收缓冲区的包,造成多个包接收

 

解决办法:

为了避免粘包现象,可采取以下几种措施:

一是对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;

二是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;

三是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。

以上提到的三种措施,都有其不足之处。

第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。

第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。

第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合

 

更为简洁的说法:

定包长

包尾加\r\n

包头加包体长度

 

网上说法:

个人比较喜欢的一种做法是给一帧数据加帧头帧尾,然后接收方不断接受并缓存收到的数据,根据帧头帧尾分离出一帧完整的数据,再分离各字段得到数据。

 

如果某个包出错了,怎么不断恢复?

发送消息时,每个消息长度在编程的时候就指定了。如果接收到的数据包有问题,我们可以通过消息长度来不断回复原来的数据包。

3. TCP例子

服务端:

#include <stdio.h>

#include <WinSock2.h>

#pragma comment(lib"ws2_32.lib")

 

int _tmain(int argc_TCHARargv[])

{

    WSADATA wsaData;

    int port = 5099;

    char buf[] = "服务器: 欢迎登录......\n";

 

    // 加载套接字

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)

    {

         printf("加载套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // socket()

    SOCKET sockSrv = socket(AF_INETSOCK_STREAM, 0);

 

    // 初始化IP和端口信息

    SOCKADDR_IN addrSrv;

    addrSrv.sin_family = AF_INET;

    addrSrv.sin_port = htons(port); // 1024以上的端口号

    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

 

    // bind()

    if (bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)

    {

         printf("套接字绑定失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // listen()

    if (listen(sockSrv, 10) == SOCKET_ERROR){

         printf("套接字监听失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // 客户端信息

    SOCKADDR_IN addrClient;

    int len = sizeof(SOCKADDR);

 

    // 开始监听

    printf("服务端启动成功......开始监听...\n");

    while (1)

    {

         // 等待客户请求到来  

         SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);

         if (sockConn == SOCKET_ERROR){

             printf("建立连接失败:%d......\n", WSAGetLastError());

             break;

         }

 

         printf("与客户端建立连接......IP:[%s]\n", inet_ntoa(addrClient.sin_addr));

 

         // 发送数据

         if (send(sockConn, buf, sizeof(buf), 0) == SOCKET_ERROR){

             printf("发送数据失败......\n");

             break;

         }

 

         char recvBuf[100];

         memset(recvBuf, 0, sizeof(recvBuf));

         // 接收数据

         recv(sockConn, recvBuf, sizeof(recvBuf), 0);

         printf("收到数据:%s\n", recvBuf);

 

         closesocket(sockConn);

    }

 

    // 关闭套接字

    closesocket(sockSrv);

    WSACleanup();

    system("pause");

 

    return 0;

}

 

客户端:

#include <stdio.h>

#include <WinSock2.h>

#pragma comment(lib"ws2_32.lib")

 

int _tmain(int argc_TCHARargv[])

{

    WSADATA wsaData;

    int port = 5099;

    char buff[1024];

    memset(buff, 0, sizeof(buff));

 

    // 加载套接字

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)

    {

         printf("加载套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // 初始化IP和端口信息

    SOCKADDR_IN addrSrv;

    addrSrv.sin_family = AF_INET;

    addrSrv.sin_port = htons(port);

    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

 

    // socket()

    SOCKET sockClient = socket(AF_INETSOCK_STREAM, 0);

    if (SOCKET_ERROR == sockClient){

         printf("创建套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // 向服务器发出连接请求

    if (connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)

    {

         printf("连接服务器失败:%d......\n", WSAGetLastError());

         return 1;

    }

    else

    {

         // 接收数据

         recv(sockClient, buff, sizeof(buff), 0);

         printf("收到数据:%s\n", buff);

 

         // 发送数据

         char buf[] = "客户端:请求登录......";

         send(sockClient, buf, sizeof(buf), 0);

    }

 

    // 关闭套接字

    closesocket(sockClient);

    WSACleanup();

 

    return 0;

}

 

旧函数解决方式:

 

4. UDP例子

服务端(接收方):

// UDPReceiverTest.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

#include <stdio.h>

#include <WinSock2.h>

#pragma comment(lib"ws2_32.lib")

 

int _tmain(int argc_TCHARargv[])

{

    WSADATA wsaData;

    int port = 5099;

 

    // 加载套接字

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)

    {

         printf("加载套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // 初始化IP和端口信息

    SOCKADDR_IN addrSrv;

    addrSrv.sin_family = AF_INET;

    addrSrv.sin_port = htons(port);

    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

 

    // socket()

    SOCKET sockClient = socket(AF_INET,SOCK_DGRAM, 0);

    if (SOCKET_ERROR == sockClient){

         printf("创建套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // bind()

    if (bind(sockClient, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)

    {

         printf("套接字绑定失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    SOCKADDR_IN addrClnt;

    int nLen = sizeof(SOCKADDR);

    // 消息

    char szMsg[1024];

    memset(szMsg, 0, sizeof(szMsg));

 

    // 等待客户请求到来

    printf("服务端启动成功......等待客户发送数据...\n");

    while (1)

    {

         // 接收数据

         if (SOCKET_ERROR != recvfrom(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrClnt, &nLen))

         {

             printf("发送方:%s\n", szMsg);

             char szSrvMsg[] = "收到...";

             // 发送数据

             sendto(sockClient, szSrvMsg, sizeof(szSrvMsg), 0, (SOCKADDR*)&addrClnt, nLen);

         }

    }

 

    // 上面为无线循环,以下代码不会执行

    // 关闭套接字

    closesocket(sockClient);

    WSACleanup();

 

    return 0;

}

 

客户端:

 

// UDPSenderTest.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

#include <stdio.h>

#include <WinSock2.h>

#pragma comment(lib"ws2_32.lib")

 

int _tmain(int argc_TCHARargv[])

{

    WSADATA wsaData;

    int port = 5099;

 

    // 加载套接字

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)

    {

         printf("加载套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // socket()

    SOCKET sockClient = socket(AF_INETSOCK_DGRAM, 0);

    if (SOCKET_ERROR == sockClient){

         printf("创建套接字失败:%d......\n", WSAGetLastError());

         return 1;

    }

 

    // 初始化IP和端口信息

    SOCKADDR_IN addrSrv;

    addrSrv.sin_family = AF_INET;

    addrSrv.sin_port = htons(port);

    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

 

    int nLen = sizeof(SOCKADDR);

 

    // 发送数据

    char szMsg[1024];

    memset(szMsg, 0, sizeof(szMsg));

    sendto(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, nLen);

 

    // 发送数据

    while (1)

    {

         // 初始化数据

         char szMsg[1024];

         memset(szMsg, 0, sizeof(szMsg));

         printf("请输入要发送的数据(输入q退出):");

         scanf("%s", &szMsg);

 

         // 退出循环

         if (!strcmp(szMsg, "q") || !strcmp(szMsg, "Q"))

         {

             break;

         }

 

         // 发送数据

         sendto(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, nLen);

 

         // 清空缓存

         memset(szMsg, 0, sizeof(szMsg));

 

         // 接收数据

         if (SOCKET_ERROR != recvfrom(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, &nLen))

         {

             printf("接收方:%s\n", szMsg);

         }

    }

 

    // 关闭套接字

    closesocket(sockClient);

    WSACleanup();

 

    return 0;

}

 

5. TCP和 UDP 注意点

易忽略,出错的地方:socket()

TCP:          SOCKET sockClient = socket(AF_INETSOCK_STREAM, 0);

UDP:        SOCKET sockClient = socket(AF_INETSOCK_DGRAM, 0);

 

TCP不存在数据边界:

收到数据不意味着马上调用read()函数,只要不超过数组容量,则有可能数据填充满缓冲后通过一次read()函数调用读取全部,也有可能分成多次read()函数调用进行读取。如果传输出错就会提供重传服务。(套接字内部有一个由字节数组构成的缓冲)

6. 结构体、图片传输方法

首先通讯双方需要统一结构体,示例:

struct Massage

{

    int nID;

    char strMsg[64];

};

 

发送方:

    // 结构体消息

    Massage stMsg;

    memset(stMsg.strMsg, 0, sizeof(stMsg.strMsg));

    stMsg.nID = 1001;

    strcpy(stMsg.strMsg, "Struct string");

    // ...

    sendto(sockClient, (char*)&stMsg, sizeof(stMsg) + 1, 0, (SOCKADDR*)&addrClnt, nLen);

 

接收方:

   // 结构体

   Massage stMsg;

   memset(stMsg.strMsg, 0, sizeof(stMsg.strMsg));

 

   memcpy(&stMsg, szMsg, sizeof(stMsg) + 1);

 

   printf("接收方:%d\t%s\n", stMsg.nID, stMsg.strMsg);

 

特别注意: sizeof(stMsg) + 1 两者必须保持一致。

 

拓展:发送文件

    // 图片

    struct Photo

    {

         int nSize;

         char buf[256];

    };

    Photo stPhoto;

    memset(stPhoto.buf, 0, sizeof(stPhoto.buf));

 

    // 发送文件

    printf("正在发送文件......\n");

    while (fp1)

    {

         // 读取文件内容到buf中,每次读256字节,返回值表示实际读取的字节数

         int nCount = fread(stPhoto.buf, 1, sizeof(stPhoto.buf), fp1);

 

         stPhoto.nSize = nCount;

 

         //printf("read %d byte\n", nCount);

 

         // 如果读取的字节数不大于0,说明读取出错或文件已经读取完毕

         if (nCount <= 0)

         {

             sprintf(stPhoto.buf, "finish\n");

             sendto(sockClient, (char*)&stPhoto, sizeof(stPhoto), 0, (SOCKADDR*)&addrSrv, nLen);

             printf("文件发送完成......\n");

             break;

         }

 

         sendto(sockClient, (char*)&stPhoto, sizeof(stPhoto), 0, (SOCKADDR*)&addrSrv, nLen);

    }

 

接收文件:

    printf("正在接收文件......\n");

    while (1)

    {

         // 接收图片

         if (SOCKET_ERROR != recvfrom(sockClient, szFileInfo, sizeof(szFileInfo), 0, (SOCKADDR*)&addrClnt, &nLen))

         {

             memcpy(&stPhoto, szFileInfo, sizeof(stPhoto));

 

             if (0 == strncmp(stPhoto.buf, "finish", 6))

             {

                  printf("文件接收完成......\n");

                  break;

             }

            

             int n = fwrite(stPhoto.buf, 1, stPhoto.nSize, fp2);

             //printf("write %d byte\n", n);

         }

    }

 

7. 常见错误

包含<windows.h>和winsock.h后重定义问题:

[解决方案1]

    由以上代码可以看出如果在没有定义WIN32_LEAN_AND_MEAN宏的大前

提下windows.h有可能包含winsock.h 头文件,因此我们得出一个很简单

的解决方法就是在包含<windows.h>之前定义WIN32_LEAN_AND_MEAN宏,如

下所示:

#define WIN32_LEAN_AND_MEAN

#include <windows.h>

[解决方案2]

winsock.h 头文件写在<windows.h>的前面。

转自:https://blog.csdn.net/daoming1112/article/details/54698466

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 (1)基本网络编程实例 Winsock实现网络聊天室【\chap1\ChatRoom(Winsock)】 CSocket实现聊天室【\chap1\ChatRoom(Csocket)】 (2)本地计算机网络编程实例 获取计算机的名称和IP地址【\chap2\Local】 获取计算机的子网掩码【\chap2\ Local】 获取计算机的DNS设置【\chap2\ Local】 获取计算机的网卡地址【\chap2\ Local】 获取计算机安装的协议【\chap2\ Local】 获取计算机提供的服务【\chap2\ Local】 获取计算机的所有网络资源【\chap2\ Local】 修改本地计算机的所有网络设置【\chap2\ Local】 获取计算机TCP/IP协议的所有信息【\chap2\ Local】 (3)局域网网络通信编程实例 获取网上邻居【\chap3\Neighbor】 lIP地址和计算机名之间的转换【\chap3\Neighbor】 l映射网络驱动器【\chap3\Neighbor】 l消息发送程序Net Send【\chap3\Neighbor】 l获取局域网内其他计算机的信息【\chap3\ NeighborInfo】 (4)IE编程实例 简单的浏览器的实现【\chap4\MyBrowser】 删除IE相关历史记录【\chap4\DelHistory】 将应用程序加入到IE工具栏【\chap4\AddToToolBar】 超级链接的实现【\chap4\HyperLink】 禁止IE的弹出窗口【\chap4\StopPopup】 禁止浏览某些网站【\chap4\StopTravel】 IE收藏夹【\chap4\ MyBrowser】 创建桌面快捷方式和活动桌面【\chap4\ShortCut】 (5)基本网络编程实例 点对点文件传输【\chap5\Transfer】 大型文件传输【\chap5\Transfer】 端口扫描程序【\chap5\ MyPortScanner】 Finger编程【\chap5\MyFinger】 Sniff编程【\chap5\MySniff】 Internet文件下载【\chap5\ InternetDownload】 (6)网络通信协议编程 FTP协议【\chap6\FTP】 Email协议【\chap6\Email】 ICMP协议【\chap6\ICMP】 RAS协议【\chap6\RAS】 TAPI协议【\chap6\TAPI】 Telnet协议【\chap6\Telnet】 HTTP协议 【\chap6\HTTP】 (7)Modem /串口通信编程 Modem编程【\chap7\Modem】 MSCOMM控件编程【\chap7\MSCOMM】 串口通信API编程【\chap7\MySerialCom】 (8)代理服务器编程实例 Socks 5协议编程【\chap8\Socks5】 HTTP代理服务器【\chap8\HTTP代理服务】 (9)高级网络通信编程实例 串口通信编程实例【\chap9\SerialPort】 网络流量监控【\chap9\NetTraffic】 网站下载【\chap9\ Snag】 网络五子棋系统【\chap9\FiveChess】 语音聊天【\chap9\ ChatRoom】 远程监控【\chap9\RemoteControl】 赠送实例 类似网络蚂蚁的断点续传程序【\Appendix\NetAnts】 网络多播程序【\Appendix\BroadCast】 界面美观的文字聊天程序【\Appendix\Chat】 语音电话【\Appendix\PhoneCall】

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值