回声服务器端和客户端的Windows实现

我们经过之前的实践已经知道,通信过程是怎样的。其中我的代码实践收获是对listen,accept函数作用有了更深的了解。在listen函数之前,我们的sockser服务器端套接字是不能被连接的。只有调用listen(sockser,backlog)才可以被连接。这相当于一个门卫的作用,每次客户端的请求都要经过这个门卫处理,将请求置入等候室。
那么,执行的功能就是accept函数了,它的返回值是一个用于数据I/O的套接字,相当于给我们办事的工作人员,我们的read,write函数中传入的第一个参数就是这个套接字,用于传输数据。
而客户端只需要socket(),connect()两个就可以,那么IP地址和端口号在在connect时操作系统会自动分配,端口号随机,IP就是主机的IP。

下面介绍一下回声,就是服务器端将客户端传输来的数据原封不动的传回客户端。
之前的服务器端在接受一个客户请求后就close了,我们的迭代服务器端需要close的是一个客户端,然后重新回到accept,接受另一个客户的请求,这才是符合实际的。当然,这里都是单线程的。

下面我们先上代码:
服务器端:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")
#define buf_size 1024
void main()
{
    WSADATA wsaData;
    int port = 5099;
    int strlen;
    char message[buf_size];

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("Failed to load Winsock");
        return;
    }

    //创建用于监听的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port); //1024以上的端口号
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
    if (retVal == SOCKET_ERROR) {
        printf("Failed bind:%d\n", WSAGetLastError());
        return;
    }

    if (listen(sockSrv, 5) == SOCKET_ERROR) {
        printf("Listen failed:%d", WSAGetLastError());
        return;
    }

    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    for (int i = 0; i < 5; i++)
    {
        //等待客户请求到来    
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
        if (sockConn == SOCKET_ERROR) {
            printf("Accept failed:%d", WSAGetLastError());
            //break;
        }
        else
            printf("connected client %d\n", i + 1);

        //发送数据
        while ((strlen = recv(sockConn, message, buf_size, 0)) != 0)
            send(sockConn, message, strlen, 0);
        closesocket(sockConn);
    }
    closesocket(sockSrv);
    WSACleanup();
    system("pause");
}

客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>

#pragma comment(lib, "ws2_32.lib")
#define buf_size 1024

void main()
{
    //加载套接字
    WSADATA wsaData;
    char message[buf_size];
    int slen;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("Failed to load Winsock");
        return;
    }

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(5099);
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    //创建套接字
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
    if (SOCKET_ERROR == sockClient) {
        printf("Socket() error:%d", WSAGetLastError());
        return;
    }

    //向服务器发出连接请求
    if (connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET) {
        printf("Connect failed:%d", WSAGetLastError());
        return;
    }
    else
    {
        
        printf("Connected.....");
    }
    while(1)
    {
        fputs("input message(Q to quit)", stdout);
        fgets(message, buf_size, stdin);
        if (!strcmp(message, "Q\n"))
            break;
        send(sockClient, message, strlen(message), 0);
        slen = recv(sockClient, message, buf_size - 1, 0);
        message[slen] = 0;
        printf("Message from server:%s", message);

    }

    //关闭套接字
    closesocket(sockClient);
    WSACleanup();
    system("pause");

}

结果如下:
在这里插入图片描述
我们注意的地方是:
recv()成功时返回的是数据的长度。因为C中字符串最后有个/0结束符,我们要在最后加上,因为sizeof是算占用字节数,不算最后结束符。
我们只要修改服务器端循环,然后收到的message,send出去给客户端,就是一个回声了。

通过这个实验,还了解到:
在服务器端,我们只需要声明客户端的SOCKADDR_IN addrclient,其中的内容不需要声明,这是客户端connect时自动分配的。
而在客户端,我们要声明 SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5099);
addrSrv.sin_addr.S_un.S_addr = inet_addr(“127.0.0.1”);
里面的内容都是我们要指定的,这样我们才能知道连接哪一个服务器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值