Socket通信-accept+多线程

    偶然的机会,重新写了一下windows下socket通信的比较基础的代码,
    太久没有接触socket以及多线程,查了不少博客,但是发现大部分内容比较陈旧,
    所以决定写一篇博客,顺便自己总结一下。

内容简介

  • 网络通信基本函数介绍
  • C++11多线程简介
  • socket通信tcp版本
  • socket通信udp版本

网络通信基本函数介绍

tcp连接模式下
客户端流程
1.创建socket(套接字)
    socket(int af, int type, int protocol)
    第一个参数用来说明网络协议类型,tcp/ip协议只能用AF_INET
    第二个参数:socket通讯类型(tcp,udp等)
    第三个参数:与选择的通讯类型有关
2.向服务器发起连接
    SOCKADDR_IN 声明套接字类型
        sin_family 协议家族
        sin_port 通信端口
        sin_addr 网络ip

    htons 将整型类型转换成网络字节序
    htonl 将长整型转换成网络字节序

    inet_pton(int af, char * str, pvoid addrbuf) 将点分十进制ip地址转换成网络字节
        第一个参数:协议家族
        第二个参数:点分十进制ip地址,例如:"127.0.0.1"
        第三个参数:sin_addr

    inte_ntop (int af, pvoid addrbuf, char *str, size_t len) 将网络字节序转换成点分十进制ip地址
        第一个参数:协议家族
        第二个参数:sin_addr
        第三个参数:存储ip地址的字符串
        第四个参数:字节单位长度

    connect(socket s, const sockaddr *name, int namelen)
        第一个参数:创建的套接字
        第二个参数:要链接的套接字地址
        第三个参数:单位长度

3.client与server通信
    send(socket s, char * str, int len, int flag)
        第一个参数:本机创建的套接字
        第二个参数:要发送的字符串
        第三个参数:发送字符串长度
        第四个参数:会对函数行为产生影响,一般设置为0

    recv(socket s, char * buf, int len,int flag)
        第一个参数:本机创建的套接字
        第二个参数:接受消息的字符串
        第三个参数:允许接收字符串的最大长度
        第四个参数:会对函数行为产生影响,一般设置为0

4.释放套接字
    closesocket 释放套接字
服务端流程
1.创建套接字
2.将套接字与本地的ip和端口绑定
    bind(socket s, const sockaddr *name, int namelen)
        第一个参数:创建的套接字
        第二个参数:要绑定的信息
        第三个参数:套接字类型单位长度
3.设置为监听状态
    listen(socket s, int num)
        第一个参数:要监听的socket(套接字)
        第二个参数:等待连接队列的最大长度
4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字
    accept(int socket, sockaddr *name, int *addrlen)
        第一个参数,是一个已设为监听模式的socket的描述符。
        第二个参数,是一个返回值,它指向一个struct sockaddr类型的结构体的变量,保存了发起连接的客户端得IP地址信息和端口信息。
        第三个参数,也是一个返回值,指向整型的变量,保存了返回的地址信息的长度。
        accept函数返回值是一个客户端和服务器连接的SOCKET类型的描述符,在服务器端标识着这个客户端。
5.相互通信
6.断开连接
udp通信
客户端
1.创建套接字
2.绑定服务器ip和port已经协议家族
3.相互通信
    sendto(socket s, const char * buf, int len, int flags, const sockaddr *to,int tolen)
        第一个参数:创建的套接字
        第二个参数:要发送的字符串
        第三个参数:要发送的字符串长度
        第四个参数:影响函数的行为,一般为0
        第五个参数:远端套接字信息
        第六个参数:套接字单位长度

    recvfrom(socket s, char * buf, int len, int flags, const sockaddr *to,int tolen)
        第一个参数:创建的套接字
        第二个参数:存储接收的字符串
        第三个参数:可接受的最大字符串长度
        第四个参数:影响函数的行为,一般为0
        第五个参数:远端套接字信息
        第六个参数:套接字单位长度
4.释放套接字
服务端
1.创建套接字
2.绑定本地的端口,ip,协议家族信息
3.通信
4.释放连接

C++11多线程简介

我们在socket通信过程中经常会有多个客户端向服务端发起请求的问题,在tcp模式下,会发生阻塞,解决的办法有accetp+多线程或者select+accept模式,这篇博客主要对前者就行详解,在查阅资料的过程中,我发现网上好多的博客介绍的多线程机制都是多年以前的方式,使用起来特别麻烦,比如pthread这种库,其实C++11以后,官方就已经将线程库封装了起来,我们只需要声明一下即可,该库thread

#include<thread>    
定义 std::thread xxx;
构造 std::thread(func, ...)
等待线程结束 xxx.join()   

socket通信tcp版本

服务器端
// ServerTcp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
    char recvBuf[128];
    char sendBuf[128];
    char tempBuf[256];
    int sockConn = arg;
    while (true)
    {
        // 从客户端接收消息
        printf("wait receive client message :\n");
        recv(sockConn, recvBuf, 128, 0);

        // 解析客户端地址信息
        char ipClient[16];
        inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
        printf("%s said: %s\n", ipClient, recvBuf);

        // 向客户端发送消息
        gets_s(sendBuf);
        send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
    }
}

int main()
{
    /**********************************************************************/
    /*
    WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。
    它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。
    应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
    */
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return 0; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return 0;
    }
    /***********************************************************************/

    // 申请存储线程的数组
    std::thread threads[10];
    int thread_num = 0;

    /***********************************************************************/
    /*                        socket通讯                                   */
    // 申请套接字
    SOCKET Svr = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    SOCKADDR_IN addr;

    // 要绑定的基础信息
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6002);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // 进行绑定
    int len = sizeof(sockaddr);
    bind(Svr, (struct sockaddr*)&addr, len);

    // 监听套接字
    int ret = listen(Svr, 10);
    if (ret == SOCKET_ERROR)
    {
        printf("侦听失败\n");
        closesocket(Svr);
    }

    // 存储请求连接的套接字信息
    SOCKADDR_IN addrClient;

    while (true)
    {
        // 接受连接,返回一个socket
        SOCKET sockConn = accept(Svr, (struct sockaddr*)&addrClient, &len);
        if (sockConn == INVALID_SOCKET)
        {
            //printf("无效socket\n");
            continue;
        }

        // 将通讯细节放在线程里处理
        threads[thread_num] = std::thread(func, sockConn, addrClient, thread_num);
        thread_num++;

        if (thread_num == 5)
        {
            printf("线程池达到数量上限");
        }
    }

    // 等待线程结束
    for (int i = 0; i < thread_num; i++)
        threads[i].join();

    // 释放套接字
    closesocket(Svr);
    WSACleanup();
    return 0;
}

客户端
// ClientTcp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void new_client(int pos)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return ; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return ;
    }

    SOCKET Client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    SOCKADDR_IN Server;

    // 要连接的基础信息
    Server.sin_family = AF_INET;
    Server.sin_port = htons(6002);
    inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //点分十进制地址转换成网络字节序

    // 向服务端发起连接
    int ret = connect(Client, (struct sockaddr*)&Server, sizeof(Server));

    if (ret == SOCKET_ERROR)
    {
        printf("连接失败\n");
        closesocket(Client);
        WSACleanup();
        return;
    }

    char recvBuf[128];
    char sendBuf[128];

    while (true)
    {
        printf("please input message:\n");
        gets_s(sendBuf);
        sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
        send(Client, sendBuf, strlen(sendBuf) + 1, 0);

        recv(Client, recvBuf, 128, 0);
        char ipServer[16];
        inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
        printf("%s said: %s\n", ipServer, recvBuf);
    }

    closesocket(Client);
    WSACleanup();
}

int main()
{
    std::thread threads[10];
    int client_num = 1;
    for (int i = 0; i < client_num; i++)
        threads[i] = std::thread(new_client, i);

    for (int i = 0; i < client_num; i++)
        threads[i].join();

    return 0;
}

socket通信udp版本

服务端
// ServerUdp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"


#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
    char recvBuf[128];
    char sendBuf[128];
    int sockConn = arg;
    int len = sizeof(sockaddr);
    while (true)
    {
        // 从客户端接收消息
        printf("wait receive client message :\n");
        recvfrom(sockConn, recvBuf, 128, 0, (struct sockaddr*)&arg, &len);

        // 解析客户端地址信息
        char ipClient[16];
        inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
        printf("%s said: %s\n", ipClient, recvBuf);

        // 向客户端发送消息
        gets_s(sendBuf);
        sendto(sockConn, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&arg, len);
    }
}

int main()
{
    /**********************************************************************/
    /*
    WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。
    它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。
    应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
    */
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return 0; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return 0;
    }
    /***********************************************************************/

    // 申请存储线程的数组
    std::thread threads[10];
    int thread_num = 0;

    /***********************************************************************/
    /*                        socket通讯                                   */
    // 申请套接字
    SOCKET Svr = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN addr;

    // 要绑定的基础信息
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6002);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // 进行绑定
    int len = sizeof(sockaddr);
    bind(Svr, (struct sockaddr*)&addr, len);

    // 存储请求连接的套接字信息
    SOCKADDR_IN addrClient;

    // 将通讯细节放在线程里处理
    threads[thread_num] = std::thread(func, Svr, addrClient, thread_num);
    thread_num++;

    // 等待线程结束
    for (int i = 0; i < thread_num; i++)
        threads[i].join();

    // 释放套接字
    closesocket(Svr);
    WSACleanup();
    return 0;
}
客户端
// ClientUdp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void new_client(int pos)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return;
    }

    SOCKET Client = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN Server;

    // 要连接的基础信息
    Server.sin_family = AF_INET;
    Server.sin_port = htons(6002);
    inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //点分十进制地址转换成网络字节序

    char recvBuf[128];
    char sendBuf[128];
    int len = sizeof(sockaddr);
    while (true)
    {
        printf("please input message:\n");
        gets_s(sendBuf);
        sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
        sendto(Client, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&Server, len);

        recvfrom(Client, recvBuf, 128, 0, (struct sockaddr*)&Server, &len);
        char ipServer[16];
        inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
        printf("%s said: %s\n", ipServer, recvBuf);
    }

    closesocket(Client);
    WSACleanup();
}

int main()
{
    std::thread threads[10];
    int client_num = 1;
    for (int i = 0; i < client_num; i++)
        threads[i] = std::thread(new_client, i);

    for (int i = 0; i < client_num; i++)
        threads[i].join();

    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值