C++ Socket

2018-04-04  本文已影响587人  期门

Socket网络编程实现过程简单总结

一、   服务器端

1、    加载及释放套接字库

a)       使用函数WSAStartup()绑定相应的套接字库:

    当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。

Ex:

        wVersionRequested = MAKEWORD( 2, 1 );//指定加载2.1版本

        err =WSAStartup( wVersionRequested, &wsaData );

关于Socket版本:不同版本是有区别的,例如1.1版只支持TCP/IP协议,而2.0版可以支持多协议。2.0版有良好的向后兼容性,任何使用1.1版的源代码、二进制文件、应用程序都可以不修改地在2.0规范下使用。此外winsock 2.0支持异步 1.1不支持异步.

b)   使用函数WSACleanup ()绑定相应的套接字库:

应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

c)    以上二者需要库Ws2_32.lib和头文件winsock2.h

#include"winsock2.h"

#pragma comment(lib,”ws2_32.lib”)

2、   创建套接字

socket(domain=AF_INET,type=SOCK_STREAM,proto=IPPROTO_TCP)

三个参数分别是:地址系列,套接字类型,协议号

1)、Domain : Domain参数指定了通信的”域”

AF_UNIX :AF_LOCAL本地通信

AF_INET:IPv4网络通信

AF_INET6:IPv6网络通信

AF_PACKET:链路层通信

2)、Type: Type就是socket的类型,对于AF_INET协议族而言有流套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。

        SOCK_STREAM:流套接字, 提供顺序,可靠,双向,基于连接的字节流。 可以支持带外数据传输机制。例

            如:TCP协议、FTP协议

        SOCK_DGRAM:数据包套接字, 支持数据报(无连接,不可靠的固定最大长度的消息)例如:UDP协议

        SOCK_RAW:原始套接字, 使用原始套接字时候调用,原始套接字也就是链路层协议

        SOCK_SEQPACKET:有序分组套接字,为固定最大长度的数据报提供有序,可靠,双向连接的数据传输路径; 消费者需要利用每个输入系统调用读取整个分组

3)、Protocol:支持的协议

参数解析参考:https://blog.csdn.net/liuxingen/article/details/44995467

3、  绑定服务器套接字地址

int bind(intsockfd, const struct sockaddr *addr,socklen_t addrlen);

服务端套接字绑定自己的IP地址与端口号,客户端那边可以不写,内核会给它分配一个临时的端口。

参数:

1)、sockfd: 服务器或者客户端自己创建的socket

2)、sockaddr: 服务器或者客户端自己的地址信息(协议族、IP、端口号)

sockaddr 与sockaddr_in:

二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。

3)、socklen_t: 服务器或者客户端自己的地址信息的长度

EXP:

seraddr.sin_family = AF_INET; // 设置地址族为IPv4

seraddr.sin_port = htons(SERPORT);    //设置地址的端口号信息

seraddr.sin_addr.s_addr = inet_addr(SERADDR);   // 设置IP地址

ret= bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));

参考:https://blog.csdn.net/zz709196484/article/details/54864770

4、    将套接字设置为监听模式等待连接请求

intlisten(int  sockfd, int  backlog);

用法:函数应该在调用socket和bind这两个函数之后,accept函数之前调用。

作用:让服务器套接字sockfd进入监听状态。

参数:sockfd:套接字,成功返回后进入监听模式,当有新连接并accept后会再建立一个套接字保存新的连接;

  backlog:并发连接数,下面详细介绍此参数:

        1)  当TCP接收一个连接后(三次握手通过)会将此连接存在连接请求队列里面,并对队列个数+1,而backlog为此队列允许的最大个数,超过此值,则直接将新的连接删除,即不在接收新的连接。将这些处于请求队列里面的连接暂记为后备连接,这些都在底层自动完成,底层将连接添加到队列后等待上层来处理(一般是调用accept函数接收连接);

        2)  当上层调用accept函数接收一个连接(处于请求队列里面的后备连接),队列个数会-1;

        3)  那么这样一个加一个减,只要底层提交的速度小于上层接收的速度(一般是这样),很明显backlog就不能限制连接的个数,只能限制后备连接的个数。那为啥要用这个backlog呢?主要用于并发处理,当上层没来的及接收时,底层可以提交多个连接;

        4) backlog的取值范围 ,一般为0-5。

5、   接受连接请求,返回一个新的对应于此次连接的套接字

        accept(intsocket, sockaddr *name, int *addrlen)

        参数socket: 是一个已设为监听模式的服务器端socket的描述符。

        参数sockaddr: 是一个返回值,它指向一个struct sockaddr类型的结构体的变量,保存了发起连接的客户端得IP地址信息和端口信息。

        参数addrlen: 也是一个返回值,指向整型的变量,保存了返回的地址信息的长度。

       accept函数返回值是一个客户端和服务器连接的SOCKET类型的描述符,在服务器端标识着这个客户端。

6、   用5返回的套接字和客户端进行通信(send()/recv());

send(sockets, char * str, int len, int flag)

        第一个参数:本机创建的套接字

        第二个参数:要发送的字符串

        第三个参数:发送字符串长度

        第四个参数:会对函数行为产生影响,一般设置为0

    recv(socket s, char * buf, int len,intflag)

        参数socket:创建的可以传输消息过来的套接字

        参数buf:接受消息的字符串缓存

        参数len:允许接收字符串的缓存的最大长度

        第四个参数:会对函数行为产生影响,一般设置为0

send和recv实际上分别是write和read函数的基础上扩展了第四个参数:

1)、recv对应的flags有3个选项:

    MSG_PEEK:查看数据,并不从系统缓冲区移走数据

    MSG_WAITALL:等待所有数据,等到所有的信息到达时才返回,使用它时,recv返回一直阻塞,直到指定的条件满足时,或者发生错误

     MSG_OOB:接受或者发送带外数据

2)、send第四个参数flags,有2个选项:

    MSG_DONTROUTE:不查找表,它告诉ip,目的主机在本地网络上,没必要查找表。(一般用在网络诊断和路由程序里面)

    MSG_OOB:接受或者发送带外数据 

功能上是分别将SendBuff拷贝到发送缓冲区中和将接受缓冲区的数据拷贝到ReciveBuf中;

https://blog.csdn.net/tingyun_say/article/details/51907687

https://blog.csdn.net/rankun1/article/details/50488989

二、  客户端

1、加载套接字库,创建套接字(WSAStartup()/socket());

2、向服务器发出连接请求(connect());

3、和服务器进行通信(send()/recv());

4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

三、  代码实现

环境:window7、vs2015

服务器端:

#include<iostream>

#include "winsock2.h"

#pragma comment(lib,"ws2_32.lib")

using namespace std;

int main()

{

    int RetVal;

    WORD SocketVersion=MAKEWORD(2, 2);

    WSADATA wsd;

    if (WSAStartup(SocketVersion, &wsd) != 0)

    {

        cout << "绑定Socket库失败" << endl;

    }

    SOCKET ServerSocket;

    ServerSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    if (ServerSocket == INVALID_SOCKET)

    {

        cout << "创建服务器套接字失败" << endl;

        WSACleanup();

        return -1;

}

SOCKADDR_IN ServerAddr;

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_port = htons(2345);

ServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;

RetVal = bind(ServerSocket, (SOCKADDR *)&ServerAddr, sizeof(SOCKADDR_IN));

if (RetVal == SOCKET_ERROR)

{

    cout << "套接字绑定失败" << endl;

    closesocket(ServerSocket);

    WSACleanup();

    return -1;

}

RetVal = listen(ServerSocket,2);

if (RetVal == SOCKET_ERROR)

{

    cout << "套接字监听失败" << endl;

    closesocket(ServerSocket);

    WSACleanup();

    return -1;

}

SOCKET ClientSocket;

SOCKADDR_IN ClientAddr;

int ClientAddrLen = sizeof(ClientAddr);

ClientSocket = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen);

if (ClientSocket == INVALID_SOCKET)

{

    cout << "接收客户端请求失败" << endl;

    closesocket(ServerSocket);

    WSACleanup();

    return -1;

}

char ReceiveBuff[BUFSIZ];

char SendBuff[BUFSIZ];

while (true)

{

    ZeroMemory(ReceiveBuff, BUFSIZ);

    RetVal = recv(ClientSocket, ReceiveBuff, BUFSIZ, 0);

    if (RetVal == SOCKET_ERROR)

    {

        cout << "接收数据失败" << endl;

        closesocket(ServerSocket);

        closesocket(ClientSocket);

       WSACleanup();

        return  -1;

    }

cout << "接收自客户端数据:" << ReceiveBuff << endl;

cout << "向客户端发送数据:";

cin >> SendBuff;

send(ClientSocket, SendBuff, strlen(SendBuff), 0);

}

closesocket(ServerSocket);

closesocket(ClientSocket);

WSACleanup();

return 0;

}

客户端:

#include<iostream>

#include "winsock2.h"

#pragma comment(lib,"ws2_32.lib")

using namespace std;

int main()

{

    const int BUF_SIZE = 64;

    int RetVal;

    WSADATA Wsd;

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

    {

        cout << "初始化套接字动态库失败" << endl;

        return -1;

    }

    SOCKET ServerScoket;

    ServerScoket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    if (ServerScoket == INVALID_SOCKET)

    {

        cout << "创建套接字失败" << endl;

        WSACleanup();

        return -1;

    }

    SOCKADDR_IN ServerAddr;

    ServerAddr.sin_family = AF_INET;

    ServerAddr.sin_port = htons(2345);

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

    RetVal = connect(ServerScoket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));

    if (RetVal == SOCKET_ERROR)

    {

        cout << "链接服务器失败" << endl;

        closesocket(ServerScoket);

        WSACleanup();

        return -1;

    }

    char SendBuff[BUF_SIZE];

    char RECVBuff[BUF_SIZE];

    while (true)

    {

        ZeroMemory(SendBuff, BUF_SIZE);

        cout << "向服务器发送数据" << endl;

        cin >> SendBuff;

        RetVal = send(ServerScoket, SendBuff, strlen(SendBuff),0);

        if (RetVal == SOCKET_ERROR)

        {

            cout << "发送数据失败" << endl;

            closesocket(ServerScoket);

            WSACleanup();

            return -1;

        }

    ZeroMemory(RECVBuff, BUF_SIZE);

    recv(ServerScoket, RECVBuff, BUF_SIZE, 0);

    cout << endl << "从服务器接收数据:" << RECVBuff << endl;

    }

    closesocket(ServerScoket);

    WSACleanup();

}

上一篇下一篇

猜你喜欢

热点阅读