多客户端socket服务简单例子,多线程实现。

对于socket通讯,网上有很多教程,我就简单的说一下socket通讯步骤,便于理解下面的代码。

服务器端:

首先,应该初始化套接字库,初始化套接字socket()

然后,绑定socket套接字到一个端口上,bind()

接着,将套接字设为监听状态listen()

此时,可以用accept()接受连接请求,它也会返回一个socket套接字,这是对每个连接的套接字。

现在,可以使用rec(),send()进行消息的收发

最后,结束连接,关闭套接字closesocket(),对于当前连接的套接字和服务端的总套接字要分别关闭。清理套接字库。

 

客户端:

加载套接字库,创建套接字socket();

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

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

关闭套接字closesocket(),关闭加载的套接字库。

2007628073341_2

网络通信中,大多数情况应该有多个客户端,多个客户端同时连接服务端。可以有多种解决方案,比如IOCP模型。

这里我采用最简单的创建多线程,为每个连接创建一个线程,结束连接时关闭此线程。

按照网上说法,创建线程最好使用_beginthreadex而不要使用createthread。

服务端代码:

#include <stdio.h>
#include <winsock2.h>
#include <process.h>
#include <windows.h>
#include <assert.h>
#pragma comment(lib,"ws2_32.lib")

//参数结构体,用来把多个参数传入线程函数,
struct args{
    SOCKET sClient;             //当前连接的socket套接字
    sockaddr_in remoteAddr;     //当前连接的端口号
};

//每个连接的线程
unsigned int __stdcall createlink(void* arg){
    args ap = *((args*)arg);        //这里将传入的无类型指针转换成args*类型,再取其值所表示的结构体,赋值给ap
    printf("接受到一个连接:%s:%d\r\n", inet_ntoa(ap.remoteAddr.sin_addr), ap.remoteAddr.sin_port);
    char revData[255] = {};
    while (1)
    {
        //接收数据
        int ret = recv(ap.sClient, revData, 255, 0);
        if(ret > 0)
        {
           revData[ret] = 0x00;
           printf(revData);
           if (revData[0] == '#')
           {
                printf("END\n");
                break;
           //如果接收到的字符串首字符为'#',跳出收发消息循环
           }
       }
        //发送数据
        //char * sendData = "服务端:你好,TCP客户端!\n";
        char sendData[20];
        sprintf(sendData,"ser: hello! %d!\n",ap.remoteAddr.sin_port);
        send(ap.sClient, sendData, strlen(sendData), 0);
    }
    closesocket(ap.sClient);    //结束连接前关闭当前连接的套接字
    return 0;
}


int main(int argc, char* argv[])
{
    //初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0)
    {
        return 0;
    }
    //创建套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }
    //绑定IP和端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error !");
    }
    //开始监听
    if(listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error !");
        return 0;
    }
    //循环接收数据
    printf("等待连接...\n");
    int thrdnum = 0;
    HANDLE handle[5];   //连接线程数组,最大5个连接
    while(1){
        //accept()获得接入连接,返回连接的套接字
        sockaddr_in remoteAddr;
        int nAddrlen = sizeof(remoteAddr);
        SOCKET sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if(sClient == INVALID_SOCKET)
        {
            printf("accept error !");
        }
        args arg;
        arg.sClient = sClient;
        arg.remoteAddr = remoteAddr;
        //为连接创建线程,传入连接套接字,端口号
		handle[thrdnum++] = (HANDLE)_beginthreadex(NULL, 0, createlink, &arg, 0, NULL);
    }
    WaitForMultipleObjects(thrdnum, handle, TRUE, INFINITE);    //等待所有线程都结束
    closesocket(slisten);   //关闭服务端的套接字
    WSACleanup();
    getchar();
    return 0;
}

这里有一处需要特别注意,就是把args结构体作为参数传入线程参数,这里有两种实现:

一种是我现在使用的方法,在main中,while(1)表示一直等待连接接入,每次连接,就会把sClient结构体指针传进去。这里传进去的是一个void * 类型,传进去后转换成args*类型,再把内容赋值给新的args结构体ap。

另一种办法是把 main中声明args arg改成args arg = new args;然后传入arg,这样相当于为每个连接new一个结构体,不过要记得在结束连接时释放内存。

我之前采用了传的结构体指针,内部却忘了把它赋值给一个新的结构体,导致每次连接的信息会覆盖旧连接,导致旧连接卡死。

目前的程序没有写如何结束服务端的死循环,暂时就这样吧。

 

客户端代码:

#include <cstdio>
#include <winsock2.h>
#include<ctype.h>
//#pragma  comment(lib,"ws2_32.lib")

int communicate(SOCKET sclient)
{
    //通讯前进行一次应答,获得端口信息
    char remote_num[7] = {};
    int digit_locate =0;
    char recData[255] = {};
    int ret = recv(sclient, recData, 255, 0);
    if(ret > 0)
    {
        recData[ret] = 0x00;
        printf(recData);
        for(int i = 0 ;recData[i] != ''; i++)
            if(isdigit(recData[i]))
               remote_num[digit_locate++]=recData[i];
    }

    char inputd[200] = {};
    //进入收发循环
    while(1) {
        char sendData[255] = {};
        sprintf(sendData,"%s: %s!\n", remote_num, inputd);
        //发送信息
        send(sclient, sendData, strlen(sendData), 0);
        printf(sendData);
        //接收信息
        int ret = recv(sclient, recData, 255, 0);
        if(ret > 0)
        {
            recData[ret] = 0x00;
            printf(recData);
        }
        printf("please input:   ");
        scanf("%s",inputd);
        //如果输入的首字母为'#'或'*',跳出循环
        if(inputd[0] == '#' || inputd[0] == '*')
            break;
    }
    send(sclient, inputd, strlen(inputd), 0);
    return 0;
}

int main(int argc, char* argv[])
{
    //初始化套接字
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }
    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");
        return 0;
    }
    //连接服务器
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }
    //进行通讯函数
    communicate(sclient);
    //关闭套接字,结束客户端
    closesocket(sclient);
    WSACleanup();
    printf("END\n");
    getchar();
    return 0;
}

 

如果文章对你有帮助,欢迎点赞或打赏(金额不限)。你的打赏将全部用于支付网站服务器费用和提高网站文章质量,谢谢支持。

版权声明:

本文由 原创,商业转载请联系作者获得授权。
非商业转载请注明作者 雅乐网 ,并附带本文链接:
https://www.yalewoo.com/an_example_for_c_socket_programming_with_multithreading.html

上一篇:

下一篇:

我要评论

验证码*: 5 + 2 =