Winsock(5) TCP服务端向客户端发送Hello World

7 篇文章 0 订阅
7 篇文章 0 订阅

本章将详解send()/WSASend() 、 recv()/WSARecv() 和 函数,然后你就可以编写一个可运行的通讯程序了

程序包括一个服务端和一个客户端,服务端向客户端发送一个Hello World!
对,你没看错,所有程序的开端,Hello World!

程序运行结果如下所示

服务端运行结果
服务端运行结果
客户端运行结果
客户端运行结果

send()/WSASend():
int send(
    Socket s,    //即将发送数据的服务端进程
    const char FAR * buf;    //待发送数据指针
    int len;    //待发送数据长度
    int flags    //标志位
);

flags 标志位可选:0 | MSG_DONTROUTE | MSG_OOB
可以用 OR 运算符连接
通常用0,后面两个不常用。

函数运行正确返回发送的字节数,错误时返回SOCKET_ERROR,
常见错误码:

  • WSAECONNABORTED: 超时,协议错误等
  • WSAECONNRESET: 服务器关闭、重启
  • WSAEWOULDBLOCK: 特定方法无法被完成,使用了非阻塞、异步socket
  • WSAETIMEOUT: 超时,网络不通

当send()返回错误时该socket应该立即关闭,因为不可用了。

int WSASend(
    SOCKET s,
    LPWSABUF lpBuffers,    //待发送数据指针
    DWORD dwBufferCount,    //待发送数据长度
    LPDWORD lpNumberOfBytesSent,    //发送字节数,函数运行后将设置该值
    DWORD dwFlags,    //标志位,同send()
    LPWSAOVERLAPPED lpOverlapped,    //后面两个参数用于重叠I/O,后面会讲到,此处可以无视
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

函数运行正确返回0,函数运行错误返回值类似send()。

recv()/WSARecv()
int recv
(
    SOCKET s,    //待接收数据的客户端socket
    char FAR * buf,    //准备存储数据的缓冲区
    int len,    //缓冲区长度
    int flags    //标志位
);

标志位flags可取的值为:0 | MSG_PEEK | MSG_OOB
可以用OR运算符连接
通常用0,MSG_PEEK表示数据将被复制进缓冲区,但并不从输入队列中删除。
函数运行正确返回接收的字节数,函数运行错误返回SOCKET_ERROR。
常见错误码:

  • WSAEMSGSIZE:数据过大,超过缓冲区。该错误只会发生在面向消息协议中,不会发生在流式消息中,因为TCP有流量控制机制
int WSARecv(
    SOCKET s,    //待接收数据的客户端socket
    LPWSABUF lpBuffers,    //准备存储数据的缓冲区
    dwBufferCount,    //接收缓冲区大小
    lpNumberOfBytesRecvd,    //接收字节数,函数运行后将设置该值
    LPDWORD lpFlags,    //标志位,一般用0
    LPWSAOVERLAPPED lpOverlapped,    //后面两个参数用于重叠I/O,后面会讲到,此处可以无视
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

另外,还有一对不常用的WSASendDisconnect()/WSARecvDisconnect(),
它们用于发送连接断开数据,运行后会关闭连接。
现在终于可以编写一个完整的 Winsock TCP/IP 通讯程序了!
终于迈出了 Hello World! 的重要一步!

服务端代码
#include <winsock2.h>
#include <iostream>
#define PORT 5000
using namespace std;

int main(void)
{
    WSADATA wsaData;    //Winsock数据结构
    SOCKET ServerSocket;    //服务端socket
    SOCKET AcceptSocket;    //从客户端接收到的socket
    SOCKADDR_IN ServerAddr;    //服务端SOCKADDR地址
    SOCKADDR_IN ClientAddr;    //客户端SOCKADDR地址
    int port=PORT;    //端口号
    int ClientAddrLen;    //客户区地址长度
    char s[]="Hello World!";    //要传输的字符串

    WSAStartup(MAKEWORD(2,2),&wsaData);    //初始化 Winsock 2.2 版本
    ServerSocket=socket(AF_INET,SOCK_STREAM,0);    //创建一个 socket 来监听客户连接
    if(ServerSocket!=INVALID_SOCKET) {
    	cout<<"socket()创建ServerSocket成功!\n";
    }
    else {
    	cout<<"socket()创建ServerSocket失败!\n"<<WSAGetLastError();
    }
    ServerAddr.sin_family=AF_INET;    //填充 SOCKADDR_IN 数据结构
    ServerAddr.sin_port=htons(port);
    ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    if(bind(ServerSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR) {
    	cout<<"bind()绑定周知地址失败!\n"<<WSAGetLastError();
    }
    else {
    	cout<<"bind()绑定周知地址成功!\n";
    }

    if(listen(ServerSocket,5)!=SOCKET_ERROR) {
    	cout<<"listen()监听成功!\n";
    }
    else {
    	cout<<"listen()监听失败!\n";
    }

    ClientAddrLen=sizeof(ClientAddr);    //显示指定ClientAddrLen大小
    while(1)
    {
        AcceptSocket=accept(ServerSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);
        //接受一个到来的连接,注意!最后一个参数需要自己显示指定!

        /*
        这里你通过这些socket可以做两件事
        1.通过ListeningSocket再次调用accept()来接受其他连接
        2.通过NewConnection来发送/接受数据
        当你做完这两件事情时必须要关闭这些socket
        socket的关闭将在后面介绍
        */

        if(INVALID_SOCKET!=AcceptSocket) {
        cout<<"accept()接收客户端连接成功!\n";
        int sendLen=send(AcceptSocket,s,sizeof(s),0);    //发送数据
        if(sendLen==SOCKET_ERROR) {
        	cout<<"send()发送数据失败!\n"<<WSAGetLastError();
        }
        else {
        	cout<<"send()发送数据成功!发送的字节数:"<< sendLen;
        }
        closesocket(AcceptSocket);    //关闭该连接
        break;    //退出循环
        }
    }
    closesocket(ServerSocket);    //关闭ServerSocket
    WSACleanup();    //关闭Winsock

    int nothing;    //与程序无关,为了让控制台不直接关闭
    cin>>nothing;
    return 0;
}
客户端代码
#include <Winsock2.h>
#include <iostream>
#define BUFFER 1024
#define PORT 5000
using namespace std;

int main(void)
{
    WSADATA wsaData;    //Winsock数据结构
    SOCKET ClientSocket;    //客户端socket
    SOCKADDR_IN ServerAddr;    //服务器地址
    int port=PORT;    //端口号
    char buf[BUFFER];    //接收的字符缓冲区
    memset(buf,0,sizeof(buf));    //清空缓存

    WSAStartup(MAKEWORD(2,2),&wsaData);    //初始化 Winsock 2.2 版本
    ClientSocket=socket(AF_INET,SOCK_STREAM,0);    //创建客户端socket
    if(ClientSocket==INVALID_SOCKET) {
        cout<<"socket()创建ClientSocket失败!\n"<<WSAGetLastError();
    }
    else {
    	cout<<"socket()创建ClientSocket成功!\n";
    }

    ServerAddr.sin_family=AF_INET;    //填充 SOCKADDR_IN 数据结构
    ServerAddr.sin_port=htons(port);
    ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    if(connect(ClientSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==INVALID_SOCKET) {
    	cout<<"connect()连接服务端失败!\n"<<WSAGetLastError();
    }
    else {
    	cout<<"connect()连接服务端成功!\n";
    }


    int recvLen=recv(ClientSocket,buf,sizeof(buf),0);
    if(recvLen==0) {
    	cout<<"接收长度为0!\n";
    }
    else if(recvLen==SOCKET_ERROR) {
    	cout<<"recv()接收失败!\n"<<WSAGetLastError();
    }
    else {
    	cout<<"recv()接收成功!\n"<<buf<<" 接收数据字节数为:"<<recvLen;
    }
    closesocket(ClientSocket);    //关闭socket
    WSACleanup();    //关闭Winsock

    int nothing;    //与程序无关,为了让控制台不直接关闭
    cin>>nothing;
    return 0;
}

程序必须先运行服务端,再运行客户端,因为先要让服务端开启监听。

程序需要导入必要的库文件,详情参见 Windows网络编程学习笔记(1)。

Hello World!发送时需要算上最后一个/0结束符,因此大小是13个字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值