socket同步通讯客户端和服务端简单实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hhhuang1991/article/details/79845775

客户端

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

/**
@brief 同步socket客户端流程:初始化,创建socket,连接服务器,发送,接收
*/
#include "stdafx.h"
#include <iostream>
#include <Winsock2.h>

#pragma comment(lib, "ws2_32.lib")
using namespace std;

const char* ip = "127.0.0.1";
static USHORT port = 5000;

int _tmain(int argc, _TCHAR* argv[])
{
    /** 
    变量定义
    */
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    SOCKET s = INVALID_SOCKET;
    sockaddr_in server;  //服务器地址结构体
    char sendbuf[1024], recvbuf[1024];
    int iResult;

    wVersionRequested = MAKEWORD(2, 2);
    err = WSAStartup(wVersionRequested, &wsaData);
    if(-1 == err)
    {
        cerr << "WSAStartup调用失败." << endl;
        goto Error;
    }
    if(HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2)
    {
        cerr << "无法调用指定版本的Winsock2.dll."  << endl;
        goto Error;
    }

    //创建socket
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == s)
    {
        cerr << "SOCKET创建失败,错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }
Reconnect:
    //连接服务端
    server.sin_family = AF_INET;
    server.sin_addr.S_un.S_addr = inet_addr(ip);//inet_addr的作用是将一个IP字符串转换成网络字节序的整数值
    server.sin_port = htons(port);  //htons将主机字节转成网络字节
    err = connect(s, (sockaddr*)&server, sizeof(server));
    if(SOCKET_ERROR == err)
    {
        cerr << ip << "连接失败,错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }
    //发送和接收数据
    memset(sendbuf, 0, 1024);
    cout << "send: ";
    cin.getline(sendbuf, 1024);
    iResult = send(s, sendbuf, strlen(sendbuf), 0);   //第三个参数一定要是实际发送内容的字节
    if (SOCKET_ERROR == iResult)
    {
        cerr << "发送信息失败,错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }
    while(true)
    {
        memset(recvbuf, 0, 1024);
        iResult = recv(s, recvbuf, 1024, 0);
        if (iResult > 0)
        {
            cout << "从服务器接收" << iResult << "个字节:" << recvbuf << endl;
        }else if(0 == iResult)
        {
            //不知道怎么验证这种情况?
            cout << "服务器断开连接." << endl;   
            goto Reconnect;   //重新发送
        }else
        {
            //突然关闭服务器端,会出现这种错误
            cerr << "接收信息失败,错误代码:" << WSAGetLastError() << endl;   
            goto Error;
        }
    }
Error:
    if(INVALID_SOCKET != s)
        closesocket(s);
    WSACleanup();
    getchar();
    return 0;
}

服务端

// synserver.cpp : 定义控制台应用程序的入口点。
//
/**
* @brief 同步socket编程服务端流程:初始化-创建socket-绑定Bind(端口和本地地址)-监听listen
* -接收Accept(客户端的连接)-发送和接收(send/recv)

*/

#include "stdafx.h"
#include <iostream>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
static USHORT port = 5000;
static int backlog = 3;  //socket等待队列一次可以接收的最大连接请求数量
int _tmain(int argc, _TCHAR* argv[])
{
    //变量声明
    WORD iVersionRequested;
    WSADATA wsaData;
    int err;
    SOCKET s = INVALID_SOCKET;
    SOCKADDR_IN server;
    char sendbuf[1024], recvbuf[1024];
    int iResult;
    //初始化
    iVersionRequested = MAKEWORD(2, 2);
    err = WSAStartup(iVersionRequested, &wsaData);
    if(-1 == err)
    {
        cerr << "WSAStartup调用失败." << endl;
        goto Error;
    }
    if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        cerr << "无法调用指定版本的Winsock2.dll." << endl;
        goto Error;
    }
    //创建socket
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(INVALID_SOCKET == s)
    {
        cerr << "SOCKET创建失败.错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }

    //将s绑定到本地地址
    server.sin_family = AF_INET;
    server.sin_addr.S_un.S_addr = INADDR_ANY;  //使用INADDR_ANY任意ip不需要通过inet_addr转换为网络整型ip地址
    server.sin_port = htons(port);
    err = bind(s, (sockaddr*)&server, sizeof(server));
    if(SOCKET_ERROR == err)
    {
        cerr << "SOCKET绑定到本机地址失败. 错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }

    //监听
    err = listen(s, backlog);
    if (SOCKET_ERROR == err)
    {
        cerr << "创建监听队列失败. 错误代码:" << WSAGetLastError() << endl;
        goto Error;
    }

    //接收客户端的连接请求
    while(true)
    {
        SOCKET sClient = INVALID_SOCKET;
        SOCKADDR_IN client;
        int addrlen = sizeof(client);
        sClient = accept(s, (sockaddr*)&client, &addrlen);
        if(SOCKET_ERROR == sClient)
        {
            cerr << "接收客服端连接失败.错误代码:" << WSAGetLastError() << endl;
            goto Error;
        }
        //inet_ntoa将IN_ADDR结构体转换成ip字符串
        cout << "接收来自" << inet_ntoa(client.sin_addr) << "的连接" << endl;
        //发送欢迎信息
        memset(sendbuf, 0, 1024);
        strcpy(sendbuf, "你好,这里是服务器.");
        send(sClient, sendbuf, strlen(sendbuf), 0);

        //接收信息并发送信息
        while(true)
        {
            memset(recvbuf, 0, 1024);
            iResult = recv(sClient, recvbuf, 1024, 0);
            if(iResult > 0)
            {
                cout << "接收" << iResult << "个字节.内容:" << recvbuf << endl;
                //将接收的内容发送给客户端
                memset(sendbuf, 0, 1024);
                strcpy(sendbuf, recvbuf);
                send(sClient, sendbuf, strlen(sendbuf), 0);
            }else if(0 == iResult)
            {
                //不知道怎么验证这种情况?
                cout << "客户端断开连接." << endl;
                break;
            }else
            {
                //突然关闭客户端,会出现这种错误
                cerr << "接收信息失败.错误代码:" << WSAGetLastError() << endl;
                goto Error;
            }
        }
    }

Error:
    if (INVALID_SOCKET == s)
        closesocket(s);
    WSACleanup();
    getchar();
    return 0;
}

  • TCP与UDP区别总结:
    1、TCP面向连接(如打电话要先拨号建立连接),需要三次握手;UDP是无连接的,即发送数据之前不需要建立连接;
    2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付;
    3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的。UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等);
    4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
    5、TCP首部开销20字节;UDP的首部开销小,只有8个字节;
    6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
  • listen函数中的第二个参数backlog解析
    当有多个客户端一起请求的时候,服务端不可能来多少就处理多少,这样如果并发太多,就会因为性能的因素发生拥塞,然后造成雪崩。所以对于监听套接字,一般会有两个队列,未完成连接套接字和已完成连接套接字,当请求到达时,新建套接字会被存放在未完成队列中,3路握手完毕就会被转移到已完成队列里。listen里面的第二个参数backlog就是设置这个未完成连接套接字队列的长度。如果将队列长度设置成10,那么如果有20个请求一起过来,服务端就会先放10个请求进入这个队列,因为长度只有10。然后其他的就直接拒绝。如果三次握手完成了,就会将完成三次握手的请求取出来,放入另一个队列中,这样队列就空出一个位置,其他重发SYN的请求就可以进入队列中。

猜你喜欢

转载自blog.csdn.net/hhhuang1991/article/details/79845775